UC2-REST 0.2.0.17__tar.gz → 0.2.0.19__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.17 → uc2_rest-0.2.0.19}/PKG-INFO +1 -1
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/PKG-INFO +1 -1
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/SOURCES.txt +1 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/UC2Client.py +6 -2
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/__version__.py +1 -1
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/home.py +2 -2
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/laser.py +2 -32
- uc2_rest-0.2.0.19/uc2rest/message.py +85 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/motor.py +51 -17
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/mserial.py +91 -33
- uc2_rest-0.2.0.19/uc2rest/temperature.py +93 -0
- UC2-REST-0.2.0.17/uc2rest/temperature.py +0 -56
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/LICENSE +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/README.md +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/not-zip-safe +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/requires.txt +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/UC2_REST.egg-info/top_level.txt +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/setup.cfg +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/setup.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/__init__.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/analog.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/camera.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/cmdrecorder.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/config.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/config_.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/digitalout.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/galvo.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/ledmatrix.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/logger.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/modules.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/pid.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/rotator.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/slm.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/state.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/updater.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/utils.py +0 -0
- {UC2-REST-0.2.0.17 → uc2_rest-0.2.0.19}/uc2rest/wifi.py +0 -0
|
@@ -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:
|
|
@@ -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)
|
|
@@ -160,6 +163,7 @@ class UC2Client(object):
|
|
|
160
163
|
# FIXME: this is not working
|
|
161
164
|
url = f"http://{self.host}:{self.port}{path}"
|
|
162
165
|
try:
|
|
166
|
+
if timeout==0: timeout=.2
|
|
163
167
|
r = requests.post(url, json=payload, headers=self.headers, timeout=timeout)
|
|
164
168
|
returnMessage = r.json()
|
|
165
169
|
returnMessage["success"] = r.status_code==200
|
|
@@ -172,7 +176,7 @@ class UC2Client(object):
|
|
|
172
176
|
elif self.is_serial or self.isPyScript:
|
|
173
177
|
if timeout <=0:
|
|
174
178
|
getReturn = False
|
|
175
|
-
return self.serial.post_json(path, payload, getReturn=getReturn, nResponses=nResponses)
|
|
179
|
+
return self.serial.post_json(path, payload, getReturn=getReturn, nResponses=nResponses, timeout=timeout)
|
|
176
180
|
else:
|
|
177
181
|
self.logger.error("No ESP32 device is connected - check IP or Serial port!")
|
|
178
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.19"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|
|
@@ -78,9 +78,9 @@ class Home(object):
|
|
|
78
78
|
{
|
|
79
79
|
"stepperid": axis,
|
|
80
80
|
"timeout":timeout*1000,
|
|
81
|
-
"speed":speed,
|
|
81
|
+
"speed":direction*abs(speed),
|
|
82
82
|
"direction":direction,
|
|
83
|
-
"endposrelease":endposrelease,
|
|
83
|
+
"endposrelease":endposrelease,
|
|
84
84
|
"endstoppolarity":endstoppolarity
|
|
85
85
|
}]
|
|
86
86
|
}}
|
|
@@ -11,7 +11,7 @@ class Laser(object):
|
|
|
11
11
|
def set_laser(self, channel=1, value=0, auto_filterswitch=False,
|
|
12
12
|
filter_axis=-1, filter_position = None,
|
|
13
13
|
despeckleAmplitude = 0.,
|
|
14
|
-
despecklePeriod=10, timeout=20, is_blocking =
|
|
14
|
+
despecklePeriod=10, timeout=20, is_blocking = False):
|
|
15
15
|
if channel not in (0,1,2,3):
|
|
16
16
|
if channel=="R":
|
|
17
17
|
channel = 1
|
|
@@ -46,7 +46,7 @@ class Laser(object):
|
|
|
46
46
|
|
|
47
47
|
}
|
|
48
48
|
#self._parent.logger.debug("Setting Laser "+str(channel)+", value: "+str(value))
|
|
49
|
-
r = self._parent.post_json(path, payload, getReturn=is_blocking)
|
|
49
|
+
r = self._parent.post_json(path, payload, getReturn=is_blocking, timeout=.5)
|
|
50
50
|
return r
|
|
51
51
|
|
|
52
52
|
def set_laserpin(self, laserid=1, laserpin=0):
|
|
@@ -61,33 +61,3 @@ class Laser(object):
|
|
|
61
61
|
r = self._parent.post_json(path, payload)
|
|
62
62
|
return r
|
|
63
63
|
|
|
64
|
-
def get_laserpins(self):
|
|
65
|
-
path = '/laser_get'
|
|
66
|
-
|
|
67
|
-
r = self._parent.get_json(path)
|
|
68
|
-
|
|
69
|
-
if type(r) is dict:
|
|
70
|
-
# cast laser pins
|
|
71
|
-
if "LASER1pin" in r: r["LASER1pin"] = int(r["LASER1pin"])
|
|
72
|
-
else: r["LASER1pin"] = 0
|
|
73
|
-
if r.__contains__("LASER2pin"): r["LASER2pin"] = int(r["LASER2pin"])
|
|
74
|
-
else: r["LASER2pin"] = 0
|
|
75
|
-
if r.__contains__("LASER3pin"): r["LASER3pin"] = int(r["LASER3pin"])
|
|
76
|
-
else: r["LASER3pin"] = 0
|
|
77
|
-
else:
|
|
78
|
-
r={}
|
|
79
|
-
r["LASER1pin"] = 0
|
|
80
|
-
r["LASER2pin"] = 0
|
|
81
|
-
r["LASER3pin"] = 0
|
|
82
|
-
|
|
83
|
-
return r
|
|
84
|
-
|
|
85
|
-
def get_laserpin(self, laserid=1):
|
|
86
|
-
path = '/laser_get'
|
|
87
|
-
|
|
88
|
-
payload = {
|
|
89
|
-
"task": path,
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
r = self._parent.post_json(path, payload,timeout=2)
|
|
93
|
-
return r["LASER"+str(laserid)+"pin"]
|
|
@@ -0,0 +1,85 @@
|
|
|
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):
|
|
13
|
+
self._parent = parent
|
|
14
|
+
|
|
15
|
+
# create a dictionary of callback functions that can be triggered by incoming messages
|
|
16
|
+
self._callbackPerKey = {}
|
|
17
|
+
self.NCallBacks = 10
|
|
18
|
+
for i in range(self.NCallBacks):
|
|
19
|
+
self._callbackPerKey[i] = None
|
|
20
|
+
|
|
21
|
+
# register a callback function for the motor status on the serial loop
|
|
22
|
+
if hasattr(self._parent, "serial"):
|
|
23
|
+
self._parent.serial.register_callback(self._callback_message, pattern="message")
|
|
24
|
+
|
|
25
|
+
def _callback_message(self, data):
|
|
26
|
+
''' cast the json in the form:
|
|
27
|
+
{
|
|
28
|
+
"message": {
|
|
29
|
+
"key": 1,
|
|
30
|
+
"data": 1
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
into seperated actions that follow this pattern:
|
|
34
|
+
data lookup:
|
|
35
|
+
{
|
|
36
|
+
key | meaning | value
|
|
37
|
+
--------------------------------
|
|
38
|
+
1 | snap image | 0
|
|
39
|
+
2 | exposure time | 0....1000000
|
|
40
|
+
3 | gain | 0...100
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
#%%
|
|
45
|
+
import uc2rest
|
|
46
|
+
import time
|
|
47
|
+
|
|
48
|
+
port = "unknown"
|
|
49
|
+
port = "/dev/cu.SLAB_USBtoUART"
|
|
50
|
+
ESP32 = uc2rest.UC2Client(serialport=port, baudrate=500000, DEBUG=True)
|
|
51
|
+
|
|
52
|
+
# register callback function for key/value pair
|
|
53
|
+
def my_callback_key1(value):
|
|
54
|
+
print("Callback: ", value)
|
|
55
|
+
ESP32.message.register_callback(1, my_callback_key1)
|
|
56
|
+
|
|
57
|
+
while True:
|
|
58
|
+
time.sleep(.1)
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
'''
|
|
62
|
+
try:
|
|
63
|
+
# parse the message
|
|
64
|
+
key = data["message"]["key"]
|
|
65
|
+
value = data["message"]["data"]
|
|
66
|
+
# trigger the action
|
|
67
|
+
if self._callbackPerKey[key] is not None:
|
|
68
|
+
self._callbackPerKey[key](value) # we call the function with the value
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print("Error in _callback_message: ", e)
|
|
71
|
+
|
|
72
|
+
def register_callback(self, key, callback):
|
|
73
|
+
''' register a callback function for a specific key '''
|
|
74
|
+
self._callbackPerKey[key] = callback
|
|
75
|
+
|
|
76
|
+
def trigger_message(self, key:int=1, value:int=1):
|
|
77
|
+
# {"task": "/message_act", "message": 1, "key":1, "value":1}
|
|
78
|
+
path = "/heat_act"
|
|
79
|
+
payload = {
|
|
80
|
+
"task": path,
|
|
81
|
+
"key": key,
|
|
82
|
+
"value": value
|
|
83
|
+
}
|
|
84
|
+
r = self._parent.post_json(path, payload, getReturn=False)
|
|
85
|
+
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
|
|
|
@@ -48,14 +38,17 @@ class Motor(object):
|
|
|
48
38
|
self.stepSizeZ = 1
|
|
49
39
|
self.stepSizeA = 1
|
|
50
40
|
|
|
41
|
+
self.DEFAULT_ACCELERATION = 10000
|
|
42
|
+
|
|
51
43
|
self.motorAxisOrder = [0,1,2,3] # motor axis is 1,2,3,0 => X,Y,Z,T # FIXME: Hardcoded
|
|
52
44
|
|
|
53
45
|
# register a callback function for the motor status on the serial loop
|
|
54
|
-
self._parent
|
|
46
|
+
if hasattr(self._parent, "serial"):
|
|
47
|
+
self._parent.serial.register_callback(self._callback_motor_status, pattern="steppers")
|
|
55
48
|
|
|
56
49
|
# move motor to wake them up #FIXME: Should not be necessary!
|
|
57
|
-
self.move_stepper(steps=(1,1,1,1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
58
|
-
self.move_stepper(steps=(-1,-1,-1,-1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
50
|
+
#self.move_stepper(steps=(1,1,1,1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
51
|
+
#self.move_stepper(steps=(-1,-1,-1,-1), speed=(1000,1000,1000,1000), is_absolute=(False,False,False,False))
|
|
59
52
|
|
|
60
53
|
def _callback_motor_status(self, data):
|
|
61
54
|
''' cast the json in the form:
|
|
@@ -77,7 +70,46 @@ class Motor(object):
|
|
|
77
70
|
except Exception as e:
|
|
78
71
|
print("Error in _callback_motor_status: ", e)
|
|
79
72
|
|
|
73
|
+
def setTrigger(self, axis="X", pin=1, offset=0, period=1):
|
|
74
|
+
# {"task": "/motor_act", "setTrig": {"steppers": [{"stepperid": 1, "trigPin": 1, "trigOff":0, "trigPer":1}]}}
|
|
75
|
+
if type(axis) is not int:
|
|
76
|
+
axis = self.xyztTo1230(axis)
|
|
77
|
+
path = "/motor_act"
|
|
78
|
+
payload = {
|
|
79
|
+
"task": path,
|
|
80
|
+
"setTrig":{
|
|
81
|
+
"steppers": [
|
|
82
|
+
{
|
|
83
|
+
"stepperid": axis,
|
|
84
|
+
"trigPin": pin,
|
|
85
|
+
"trigOff": offset,
|
|
86
|
+
"trigPer": period
|
|
87
|
+
}]
|
|
88
|
+
}}
|
|
89
|
+
r = self._parent.post_json(path, payload)
|
|
90
|
+
return r
|
|
91
|
+
|
|
92
|
+
# {"task": "/motor_act", "stagescan": {"nStepsLine": 50, "dStepsLine": 1, "nTriggerLine": 1, "nStepsPixel": 50, "dStepsPixel": 1, "nTriggerPixel": 1, "delayTimeStep": 10, "stopped": 0, "nFrames": 50}}"}}
|
|
93
|
+
def startStageScanning(self, nStepsLine=100, dStepsLine=1, nTriggerLine=1, nStepsPixel=100, dStepsPixel=1, nTriggerPixel=1, delayTimeStep=10, nFrames=5, isBlocking = False):
|
|
94
|
+
path = "/motor_act"
|
|
95
|
+
payload = {
|
|
96
|
+
"task": path,
|
|
97
|
+
"stagescan":{
|
|
98
|
+
"nStepsLine": nStepsLine,
|
|
99
|
+
"dStepsLine": dStepsLine,
|
|
100
|
+
"nTriggerLine": nTriggerLine,
|
|
101
|
+
"nStepsPixel": nStepsPixel,
|
|
102
|
+
"dStepsPixel": dStepsPixel,
|
|
103
|
+
"nTriggerPixel": nTriggerPixel,
|
|
104
|
+
"delayTimeStep": delayTimeStep,
|
|
105
|
+
"stopped": 0,
|
|
106
|
+
"nFrames": nFrames
|
|
107
|
+
}}
|
|
108
|
+
r = self._parent.post_json(path, payload, getReturn=isBlocking)
|
|
109
|
+
return r
|
|
80
110
|
|
|
111
|
+
def stopStageScanning(self):
|
|
112
|
+
self.startStageScanning(stopped=1)
|
|
81
113
|
|
|
82
114
|
def setIsCoreXY(self, isCoreXY = False):
|
|
83
115
|
self.isCoreXY = isCoreXY
|
|
@@ -164,7 +196,7 @@ class Motor(object):
|
|
|
164
196
|
# have to move only one motor to move in XY direction
|
|
165
197
|
return self.move_xyza(steps=(0,steps[0], steps[1], 0), speed=(0,speed[0],speed[1],0), is_blocking=is_blocking, is_absolute=is_absolute, is_enabled=is_enabled, timeout=timeout)
|
|
166
198
|
else:
|
|
167
|
-
if len(speed)!= 2:
|
|
199
|
+
if type(speed)==int or len(speed)!= 2:
|
|
168
200
|
speed = (speed,speed)
|
|
169
201
|
|
|
170
202
|
if len(acceleration)!= 2:
|
|
@@ -339,10 +371,12 @@ class Motor(object):
|
|
|
339
371
|
"position": int(steps[iMotor]),
|
|
340
372
|
"speed": int(speed[iMotor]),
|
|
341
373
|
"isabs": isAbsoluteArray[iMotor],
|
|
342
|
-
"isaccel":
|
|
374
|
+
"isaccel":1,
|
|
343
375
|
"isen": is_enabled}
|
|
344
376
|
if acceleration[iMotor] is not None:
|
|
345
377
|
motorProp["accel"] = int(acceleration[iMotor])
|
|
378
|
+
else:
|
|
379
|
+
motorProp["accel"] = self.DEFAULT_ACCELERATION
|
|
346
380
|
motorPropList.append(motorProp)
|
|
347
381
|
|
|
348
382
|
path = "/motor_act"
|
|
@@ -478,7 +512,7 @@ class Motor(object):
|
|
|
478
512
|
r = self._parent.post_json(path, payload)
|
|
479
513
|
return r
|
|
480
514
|
|
|
481
|
-
def get_position(self, axis=None, timeout
|
|
515
|
+
def get_position(self, axis=None, timeout=.2):
|
|
482
516
|
# pulls all current positions from the stepper controller
|
|
483
517
|
path = "/motor_get"
|
|
484
518
|
payload = {
|
|
@@ -489,7 +523,7 @@ class Motor(object):
|
|
|
489
523
|
_physicalStepSizes = np.array((self.stepSizeA, self.stepSizeX, self.stepSizeY, self.stepSizeZ))
|
|
490
524
|
|
|
491
525
|
# this may be an asynchronous call.. #FIXME!
|
|
492
|
-
r = self._parent.post_json(path, payload, getReturn = True, nResponses=1)
|
|
526
|
+
r = self._parent.post_json(path, payload, getReturn = True, nResponses=1, timeout=timeout)
|
|
493
527
|
if "motor" in r:
|
|
494
528
|
for index, istepper in enumerate(r["motor"]["steppers"]):
|
|
495
529
|
_position[istepper["stepperid"]]=istepper["position"]*_physicalStepSizes[self.motorAxisOrder[index]]
|
|
@@ -9,12 +9,12 @@ class Serial:
|
|
|
9
9
|
def __init__(self, port, baudrate=115200, timeout=5,
|
|
10
10
|
identity="UC2_Feather", parent=None, DEBUG=False):
|
|
11
11
|
|
|
12
|
-
self.
|
|
12
|
+
self.serialdevice = None
|
|
13
13
|
self.serialport = port
|
|
14
14
|
self.baudrate = baudrate
|
|
15
15
|
self.timeout = timeout
|
|
16
16
|
self._parent = parent
|
|
17
|
-
|
|
17
|
+
self.manufacturer = ""
|
|
18
18
|
if self._parent is None:
|
|
19
19
|
import logging
|
|
20
20
|
self._logger = logging.getLogger(__name__)
|
|
@@ -26,23 +26,24 @@ class Serial:
|
|
|
26
26
|
self.identity = identity
|
|
27
27
|
self.DEBUG = DEBUG
|
|
28
28
|
self.is_connected = False
|
|
29
|
-
self.write_timeout = 0.
|
|
29
|
+
self.write_timeout = 0.05
|
|
30
30
|
self.read_timeout = 0.02
|
|
31
31
|
|
|
32
32
|
self.cmdCallBackFct = None
|
|
33
33
|
|
|
34
34
|
# setup command queue
|
|
35
35
|
self.resetLastCommand = False
|
|
36
|
-
self.
|
|
36
|
+
self.sender_queue = queue.Queue()
|
|
37
37
|
self.responses = {}
|
|
38
38
|
self.commands = {}
|
|
39
39
|
self.lock = threading.Lock()
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
# setup callback list for parent modules
|
|
41
42
|
self.callBackList = []
|
|
42
43
|
|
|
43
44
|
# initialize serial connection
|
|
44
45
|
self.thread = None
|
|
45
|
-
self.
|
|
46
|
+
self.serialdevice= self.openDevice(port, baudrate)
|
|
46
47
|
|
|
47
48
|
def breakCurrentCommunication(self):
|
|
48
49
|
self.resetLastCommand = True
|
|
@@ -66,7 +67,7 @@ class Serial:
|
|
|
66
67
|
def openDevice(self, port=None, baud_rate=115200):
|
|
67
68
|
try: # try to close an eventually open serial connection
|
|
68
69
|
if str(type(self.ser)) != "<class 'uc2rest.mserial.MockSerial'>":
|
|
69
|
-
self.
|
|
70
|
+
self.serialdevice.close()
|
|
70
71
|
except: pass
|
|
71
72
|
|
|
72
73
|
try:
|
|
@@ -101,9 +102,20 @@ class Serial:
|
|
|
101
102
|
self.identifier_counter = 0 # Counter for generating unique identifiers
|
|
102
103
|
self.thread = threading.Thread(target=self._process_commands)
|
|
103
104
|
self.thread.start()
|
|
105
|
+
|
|
106
|
+
# setup sender queue
|
|
107
|
+
self.sender_worker_thread = threading.Thread(target=self._sending_commands)
|
|
108
|
+
self.sender_worker_thread.daemon = True # Ensure the thread exits when the main program does
|
|
109
|
+
self.sender_worker_thread.start()
|
|
104
110
|
return ser
|
|
105
111
|
|
|
106
112
|
def findCorrectSerialDevice(self):
|
|
113
|
+
'''
|
|
114
|
+
This function tries to find the correct serial device from the list of available ports
|
|
115
|
+
It also checks if the firmware is correct by sending a command and checking for the correct response
|
|
116
|
+
It may be that - depending on the OS - the response may be corrupted
|
|
117
|
+
If this is the case try to hard-code the COM port into the config JSON file
|
|
118
|
+
'''
|
|
107
119
|
_available_ports = serial.tools.list_ports.comports(include_links=False)
|
|
108
120
|
ports_to_check = ["COM", "/dev/tt", "/dev/a", "/dev/cu.SLA", "/dev/cu.wchusb"]
|
|
109
121
|
descriptions_to_check = ["CH340", "CP2102"]
|
|
@@ -113,12 +125,14 @@ class Serial:
|
|
|
113
125
|
any(port.description.startswith(allowed_description) for allowed_description in descriptions_to_check):
|
|
114
126
|
if self.tryToConnect(port.device):
|
|
115
127
|
self.is_connected = True
|
|
128
|
+
self.manufacturer = port.manufacturer
|
|
116
129
|
return self.serialdevice
|
|
117
130
|
|
|
118
131
|
self.is_connected = False
|
|
119
132
|
self.serialport = "NotConnected"
|
|
120
133
|
self.serialdevice = None
|
|
121
134
|
self._logger.debug("No USB device connected! Using DUMMY!")
|
|
135
|
+
self.manufacturer = "UC2Mock"
|
|
122
136
|
return None
|
|
123
137
|
|
|
124
138
|
def tryToConnect(self, port):
|
|
@@ -148,7 +162,7 @@ class Serial:
|
|
|
148
162
|
ser.write(json.dumps(payload).encode('utf-8'))
|
|
149
163
|
ser.write(b'\n')
|
|
150
164
|
# iterate a few times in case the debug mode on the ESP32 is turned on and it sends additional lines
|
|
151
|
-
for i in range(
|
|
165
|
+
for i in range(500):
|
|
152
166
|
# if we just want to send but not even wait for a response
|
|
153
167
|
mReadline = ser.readline()
|
|
154
168
|
if self.DEBUG: self._logger.debug("[checkFirmware]: "+str(mReadline))
|
|
@@ -162,6 +176,29 @@ class Serial:
|
|
|
162
176
|
self.identifier_counter += 1
|
|
163
177
|
return self.identifier_counter
|
|
164
178
|
|
|
179
|
+
def _enqueue_command(self, json_command):
|
|
180
|
+
"""Add a command to the queue."""
|
|
181
|
+
self.sender_queue.put(json_command)
|
|
182
|
+
|
|
183
|
+
def _sending_commands(self):
|
|
184
|
+
"""Sending commands from the queue with a 1s delay between them."""
|
|
185
|
+
while self.running:
|
|
186
|
+
|
|
187
|
+
# Wait for the next command
|
|
188
|
+
json_command = self.sender_queue.get()
|
|
189
|
+
|
|
190
|
+
# Process the command
|
|
191
|
+
try:
|
|
192
|
+
self.serialdevice.write(json_command.encode('utf-8'))
|
|
193
|
+
except Exception as e:
|
|
194
|
+
self._logger.error("Failed to write the line in serial: "+str(e))
|
|
195
|
+
|
|
196
|
+
# Signal that the command has been processed
|
|
197
|
+
self.sender_queue.task_done()
|
|
198
|
+
|
|
199
|
+
# Wait for .05 second before processing the next command
|
|
200
|
+
time.sleep(0.05)
|
|
201
|
+
|
|
165
202
|
def _process_commands(self):
|
|
166
203
|
buffer = ""
|
|
167
204
|
reading_json = False
|
|
@@ -170,24 +207,23 @@ class Serial:
|
|
|
170
207
|
lineCounter = 0
|
|
171
208
|
while self.running:
|
|
172
209
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
if self.manufacturer == "UC2Mock":
|
|
211
|
+
self.running = False
|
|
212
|
+
return
|
|
213
|
+
|
|
177
214
|
# device not ready yet
|
|
178
|
-
if self.
|
|
215
|
+
if self.serialdevice is None:
|
|
179
216
|
self.is_connected = False
|
|
180
217
|
continue
|
|
181
218
|
else:
|
|
182
219
|
self.is_connected = True
|
|
183
220
|
|
|
184
221
|
# if we just want to send but not even wait for a response
|
|
222
|
+
|
|
185
223
|
try:
|
|
186
|
-
mReadline = self.
|
|
224
|
+
mReadline = self.serialdevice.readline()
|
|
187
225
|
except Exception as e:
|
|
188
226
|
self._logger.error("Failed to read the line in serial: "+str(e))
|
|
189
|
-
self.is_connected = False
|
|
190
|
-
break
|
|
191
227
|
try:
|
|
192
228
|
line = mReadline.decode('utf-8').strip()
|
|
193
229
|
if self.DEBUG and line!="": self._logger.debug("[ProcessLines]:"+str(line))
|
|
@@ -212,18 +248,29 @@ class Serial:
|
|
|
212
248
|
|
|
213
249
|
except Exception as e:
|
|
214
250
|
self._logger.error("Failed to load the json from serial %s" % buffer)
|
|
215
|
-
self._logger.error("Error: %s"
|
|
251
|
+
self._logger.error("Error: %s" % str(e))
|
|
216
252
|
json_response = {}
|
|
217
253
|
|
|
254
|
+
try: currentIdentifier = json_response["qid"]
|
|
255
|
+
except: pass
|
|
256
|
+
|
|
218
257
|
with self.lock:
|
|
219
|
-
try: currentIdentifier = json_response["qid"]
|
|
220
|
-
except: pass
|
|
221
258
|
try:
|
|
222
259
|
self.responses[currentIdentifier].append(json_response.copy())
|
|
223
260
|
except:
|
|
224
261
|
self.responses[currentIdentifier] = list()
|
|
225
262
|
self.responses[currentIdentifier].append(json_response.copy())
|
|
226
|
-
|
|
263
|
+
buffer = "" # reset buffer
|
|
264
|
+
|
|
265
|
+
# detect a reboot of the device and return the current QIDs
|
|
266
|
+
elif line == "reboot":
|
|
267
|
+
self._logger.warning("Device rebooted")
|
|
268
|
+
self.resetLastCommand = True
|
|
269
|
+
buffer = ""
|
|
270
|
+
lineCounter = 0
|
|
271
|
+
reading_json = False
|
|
272
|
+
self.responses[currentIdentifier].append({"reboot": 1})
|
|
273
|
+
self.responses[currentIdentifier].append({"qid": currentIdentifier})
|
|
227
274
|
|
|
228
275
|
if reading_json:
|
|
229
276
|
buffer += line
|
|
@@ -235,7 +282,7 @@ class Serial:
|
|
|
235
282
|
message = json.dumps(message)
|
|
236
283
|
return self.sendMessage(message, nResponses=0, timeout=timeout)
|
|
237
284
|
|
|
238
|
-
def post_json(self, path, payload, getReturn=True, nResponses=1, timeout=
|
|
285
|
+
def post_json(self, path, payload, getReturn=True, nResponses=1, timeout=100):
|
|
239
286
|
"""Make an HTTP POST request and return the JSON response"""
|
|
240
287
|
if payload is None:
|
|
241
288
|
payload = {}
|
|
@@ -281,26 +328,25 @@ class Serial:
|
|
|
281
328
|
If nResponses is 1, then the command is sent and the response is returned.
|
|
282
329
|
If nResponses is >1, then the command is sent and a list of responses is returned.
|
|
283
330
|
'''
|
|
284
|
-
t0 = time.time()
|
|
285
331
|
if type(command) == str:
|
|
286
332
|
command = json.loads(command)
|
|
287
333
|
identifier = self._generate_identifier()
|
|
288
334
|
command["qid"] = identifier
|
|
289
|
-
self.command_queue.put((identifier, command))
|
|
290
335
|
try:
|
|
291
336
|
json_command = json.dumps(command)+"\n"
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
self._logger.debug("[SendMessage] took: "+str(time.time()-t0))
|
|
337
|
+
self.serialdevice.flush()
|
|
338
|
+
if self.DEBUG: self._logger.debug("[SendMessage]: "+str(json_command))
|
|
339
|
+
# we have to queue the commands and give it some time to process
|
|
340
|
+
self._enqueue_command(json_command)
|
|
341
|
+
|
|
298
342
|
except Exception as e:
|
|
299
343
|
if self.DEBUG: self._logger.error(e)
|
|
300
344
|
return "Failed to Send"
|
|
301
|
-
self.commands[identifier]=command
|
|
302
|
-
if nResponses <= 0 or not self.is_connected or
|
|
345
|
+
self.commands[identifier]=command # FIXME: Need to clear this after the response is received
|
|
346
|
+
if nResponses <= 0 or not self.is_connected or self.manufacturer=="UC2Mock":
|
|
303
347
|
return identifier
|
|
348
|
+
t0 = time.time()
|
|
349
|
+
timeReturnReceived = 0.3
|
|
304
350
|
while self.running:
|
|
305
351
|
time.sleep(0.002)
|
|
306
352
|
if self.resetLastCommand or time.time()-t0>timeout or not self.is_connected:
|
|
@@ -313,6 +359,15 @@ class Serial:
|
|
|
313
359
|
if -identifier in self.responses:
|
|
314
360
|
self._logger.debug("You have sent the wrong command!")
|
|
315
361
|
return "Wrong Command"
|
|
362
|
+
if time.time()-t0>timeReturnReceived and not (identifier in self.responses and len(self.responses[identifier]) > 0):
|
|
363
|
+
self._logger.debug("It takes too long to get a response, we will resend the last command")
|
|
364
|
+
try:
|
|
365
|
+
self.DEBUG=1
|
|
366
|
+
self.serialdevice.write(json.dumps(self.commands[identifier]).encode('utf-8'))
|
|
367
|
+
time.sleep(0.1)
|
|
368
|
+
except Exception as e:
|
|
369
|
+
self._logger.error("Failed to write the line in serial: "+str(e))
|
|
370
|
+
|
|
316
371
|
|
|
317
372
|
def interruptCurrentSerialCommunication(self):
|
|
318
373
|
self.resetLastCommand = True
|
|
@@ -320,7 +375,7 @@ class Serial:
|
|
|
320
375
|
def stop(self):
|
|
321
376
|
self.running = False
|
|
322
377
|
self.thread.join()
|
|
323
|
-
self.
|
|
378
|
+
self.serialdevice.close()
|
|
324
379
|
|
|
325
380
|
def closeSerial(self):
|
|
326
381
|
self.stop()
|
|
@@ -329,7 +384,7 @@ class Serial:
|
|
|
329
384
|
def reconnect(self):
|
|
330
385
|
self.running = False
|
|
331
386
|
try:
|
|
332
|
-
self.
|
|
387
|
+
self.serialdevice.close()
|
|
333
388
|
except:
|
|
334
389
|
pass
|
|
335
390
|
self.serialdevice = self.openDevice(port = self.serialport, baud_rate = self.baudrate)
|
|
@@ -388,6 +443,9 @@ class MockSerial:
|
|
|
388
443
|
self.manufacturer = "UC2Mock"
|
|
389
444
|
self.BAUDRATES = -1
|
|
390
445
|
|
|
446
|
+
def flush(self):
|
|
447
|
+
pass
|
|
448
|
+
|
|
391
449
|
def isOpen(self):
|
|
392
450
|
return self.is_open
|
|
393
451
|
|
|
@@ -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,56 +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=600000, updaterate=1000):
|
|
17
|
-
# {"task": "/heat_act", "active":1, "Kp":1000, "Ki":0.1, "Kd":0.1, "target":37, "timeout":600000, "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 = 9999
|
|
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()
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|