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.
Files changed (39) hide show
  1. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/PKG-INFO +1 -1
  2. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/SOURCES.txt +2 -0
  4. uc2_rest-0.2.0.20/uc2rest/MockSerial.py +74 -0
  5. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/UC2Client.py +6 -3
  6. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/__version__.py +1 -1
  7. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/home.py +1 -1
  8. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/laser.py +1 -2
  9. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/ledmatrix.py +4 -4
  10. uc2_rest-0.2.0.20/uc2rest/message.py +90 -0
  11. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/motor.py +24 -15
  12. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/mserial.py +93 -31
  13. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/state.py +12 -0
  14. uc2_rest-0.2.0.20/uc2rest/temperature.py +93 -0
  15. UC2-REST-0.2.0.18/uc2rest/temperature.py +0 -54
  16. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/LICENSE +0 -0
  17. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/README.md +0 -0
  18. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/dependency_links.txt +0 -0
  19. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/not-zip-safe +0 -0
  20. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/requires.txt +0 -0
  21. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/UC2_REST.egg-info/top_level.txt +0 -0
  22. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/setup.cfg +0 -0
  23. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/setup.py +0 -0
  24. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/__init__.py +0 -0
  25. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/analog.py +0 -0
  26. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/camera.py +0 -0
  27. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/cmdrecorder.py +0 -0
  28. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/config.py +0 -0
  29. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/config_.py +0 -0
  30. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/digitalout.py +0 -0
  31. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/galvo.py +0 -0
  32. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/logger.py +0 -0
  33. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/modules.py +0 -0
  34. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/pid.py +0 -0
  35. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/rotator.py +0 -0
  36. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/slm.py +0 -0
  37. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/updater.py +0 -0
  38. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/utils.py +0 -0
  39. {UC2-REST-0.2.0.18 → uc2_rest-0.2.0.20}/uc2rest/wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UC2-REST
3
- Version: 0.2.0.18
3
+ Version: 0.2.0.20
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.1
2
2
  Name: UC2-REST
3
- Version: 0.2.0.18
3
+ Version: 0.2.0.20
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
@@ -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 = 115200
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, timeout=timeout, nResponses=nResponses)
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.18"
9
+ __version__ = "v0.2.0.20"
10
10
  __author__ = 'Benedict Diederich'
11
11
  __author_email__ = 'benedictdied@gmail.com'
12
12
  __license__ = 'GPL v3'
@@ -80,7 +80,7 @@ class Home(object):
80
80
  "timeout":timeout*1000,
81
81
  "speed":direction*abs(speed),
82
82
  "direction":direction,
83
- "endposrelease":endposrelease,
83
+ "endposrelease":endposrelease,
84
84
  "endstoppolarity":endstoppolarity
85
85
  }]
86
86
  }}
@@ -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 = True):
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 = 1
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=False, timeout=gTimeout):
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=False):
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=False):
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": 100, "dStepsLine": 1, "nTriggerLine": 1, "nStepsPixel": 100, "dStepsPixel": 1, "nTriggerPixel": 1, "delayTimeStep": 10, "stopped": 0, "nFrames": 5}}"}}
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=.2):
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 = 1.5
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 = 0.02
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.command_queue = queue.Queue()
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
- readLine = ser.readline().decode('utf-8').strip()
56
- if self.DEBUG: self._logger.debug(readLine)
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 = serial.tools.list_ports.comports(include_links=False)
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: self._logger.debug("[checkFirmware]: "+str(mReadline))
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
- # Check if the last command went through successfully
180
- #if not self.command_queue.empty() and not reading_json:
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
- mReadline = self.serialdevice.readline()
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
- buffer = "" # reset buffer
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
- with self.lock:
297
- self.serialdevice.flush()
298
- self._logger.debug("[SendMessage]: "+str(json_command))
299
- self.serialdevice.write(json_command.encode('utf-8') )
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 not type(self.serialdevice.BAUDRATES) is tuple:
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