UC2-REST 0.2.0.21__tar.gz → 0.2.0.23__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.21 → uc2_rest-0.2.0.23}/PKG-INFO +14 -5
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/PKG-INFO +14 -5
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/SOURCES.txt +3 -1
- uc2_rest-0.2.0.23/UC2_REST.egg-info/requires.txt +3 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/setup.py +1 -1
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/UC2Client.py +14 -12
- uc2_rest-0.2.0.23/uc2rest/__init__.py +2 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/__version__.py +1 -1
- uc2_rest-0.2.0.23/uc2rest/gripper.py +107 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/home.py +17 -7
- uc2_rest-0.2.0.23/uc2rest/ledmatrix.py +324 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/motor.py +184 -43
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/mserial.py +179 -41
- uc2_rest-0.2.0.23/uc2rest/objective.py +230 -0
- uc2_rest-0.2.0.21/UC2_REST.egg-info/requires.txt +0 -6
- uc2_rest-0.2.0.21/uc2rest/__init__.py +0 -6
- uc2_rest-0.2.0.21/uc2rest/updater.py +0 -268
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/LICENSE +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/README.md +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/not-zip-safe +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/top_level.txt +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/setup.cfg +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/MockSerial.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/analog.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/camera.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/cmdrecorder.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/config.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/config_.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/digitalout.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/galvo.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/laser.py +0 -0
- /uc2_rest-0.2.0.21/uc2rest/ledmatrix.py → /uc2_rest-0.2.0.23/uc2rest/ledmatrix_.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/logger.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/message.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/modules.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/pid.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/rotator.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/slm.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/state.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/temperature.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/utils.py +0 -0
- {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/wifi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: UC2-REST
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.23
|
|
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
|
|
@@ -16,9 +16,18 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: numpy
|
|
17
17
|
Requires-Dist: requests
|
|
18
18
|
Requires-Dist: pyserial
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
<p align="left">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: UC2-REST
|
|
3
|
-
Version: 0.2.0.
|
|
3
|
+
Version: 0.2.0.23
|
|
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
|
|
@@ -16,9 +16,18 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: numpy
|
|
17
17
|
Requires-Dist: requests
|
|
18
18
|
Requires-Dist: pyserial
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
<p align="left">
|
|
@@ -18,19 +18,21 @@ uc2rest/config.py
|
|
|
18
18
|
uc2rest/config_.py
|
|
19
19
|
uc2rest/digitalout.py
|
|
20
20
|
uc2rest/galvo.py
|
|
21
|
+
uc2rest/gripper.py
|
|
21
22
|
uc2rest/home.py
|
|
22
23
|
uc2rest/laser.py
|
|
23
24
|
uc2rest/ledmatrix.py
|
|
25
|
+
uc2rest/ledmatrix_.py
|
|
24
26
|
uc2rest/logger.py
|
|
25
27
|
uc2rest/message.py
|
|
26
28
|
uc2rest/modules.py
|
|
27
29
|
uc2rest/motor.py
|
|
28
30
|
uc2rest/mserial.py
|
|
31
|
+
uc2rest/objective.py
|
|
29
32
|
uc2rest/pid.py
|
|
30
33
|
uc2rest/rotator.py
|
|
31
34
|
uc2rest/slm.py
|
|
32
35
|
uc2rest/state.py
|
|
33
36
|
uc2rest/temperature.py
|
|
34
|
-
uc2rest/updater.py
|
|
35
37
|
uc2rest/utils.py
|
|
36
38
|
uc2rest/wifi.py
|
|
@@ -27,7 +27,7 @@ setup(
|
|
|
27
27
|
packages=['uc2rest'],
|
|
28
28
|
include_package_data=True,
|
|
29
29
|
python_requires='>=3.7',
|
|
30
|
-
install_requires=['numpy', 'requests', 'pyserial'
|
|
30
|
+
install_requires=['numpy', 'requests', 'pyserial'],
|
|
31
31
|
license=about['__license__'],
|
|
32
32
|
zip_safe=False,
|
|
33
33
|
entry_points={
|
|
@@ -11,7 +11,9 @@ from .galvo import Galvo
|
|
|
11
11
|
from .config import config
|
|
12
12
|
from .ledmatrix import LedMatrix
|
|
13
13
|
from .motor import Motor
|
|
14
|
+
from .gripper import Gripper
|
|
14
15
|
from .home import Home
|
|
16
|
+
from .objective import Objective
|
|
15
17
|
from .state import State
|
|
16
18
|
from .laser import Laser
|
|
17
19
|
from .wifi import Wifi
|
|
@@ -41,7 +43,7 @@ class UC2Client(object):
|
|
|
41
43
|
# BAUDRATE = 500000
|
|
42
44
|
BAUDRATE = 500000
|
|
43
45
|
|
|
44
|
-
def __init__(self, host=None, port=31950, serialport=None, identity="UC2_Feather", baudrate=BAUDRATE, NLeds=64, SerialManager=None, DEBUG=False, logger=None):
|
|
46
|
+
def __init__(self, host=None, port=31950, serialport=None, identity="UC2_Feather", baudrate=BAUDRATE, NLeds=64, SerialManager=None, DEBUG=False, logger=None, skipFirmwareCheck=False):
|
|
45
47
|
'''
|
|
46
48
|
This client connects to the UC2-REST microcontroller that can be found here
|
|
47
49
|
https://github.com/openUC2/UC2-REST
|
|
@@ -64,7 +66,7 @@ class UC2Client(object):
|
|
|
64
66
|
# initialize communication channel (# connect to wifi or usb)
|
|
65
67
|
if serialport is not None:
|
|
66
68
|
# use USB connection
|
|
67
|
-
self.serial = Serial(serialport, baudrate, parent=self, identity=identity, DEBUG=DEBUG)
|
|
69
|
+
self.serial = Serial(serialport, baudrate, parent=self, identity=identity, DEBUG=DEBUG, skipFirmwareCheck=skipFirmwareCheck)
|
|
68
70
|
self.is_serial = True
|
|
69
71
|
self.is_connected = self.serial.is_connected
|
|
70
72
|
self.serial.DEBUG = DEBUG
|
|
@@ -84,9 +86,7 @@ class UC2Client(object):
|
|
|
84
86
|
else:
|
|
85
87
|
self.logger.error("No ESP32 device is connected - check IP or Serial port!")
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
if not self.isPyScript: from .updater import updater
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
# import libraries depending on API version
|
|
91
91
|
self.logger.debug("Using API version 2")
|
|
92
92
|
|
|
@@ -111,12 +111,18 @@ class UC2Client(object):
|
|
|
111
111
|
# initilize motor
|
|
112
112
|
self.motor = Motor(self)
|
|
113
113
|
|
|
114
|
+
# initialize gripper
|
|
115
|
+
self.gripper = Gripper(self)
|
|
116
|
+
|
|
114
117
|
# initialize rotator
|
|
115
118
|
self.rotator = Rotator(self)
|
|
116
119
|
|
|
117
120
|
# initiliaze homing
|
|
118
121
|
self.home = Home(self)
|
|
119
122
|
|
|
123
|
+
# initialize objective
|
|
124
|
+
self.objective = Objective(self)
|
|
125
|
+
|
|
120
126
|
# initialize temperature
|
|
121
127
|
self.temperature = Temperature(self)
|
|
122
128
|
|
|
@@ -150,11 +156,7 @@ class UC2Client(object):
|
|
|
150
156
|
try: self.pinConfig = self.config.loadConfigDevice()
|
|
151
157
|
except: self.pinConfig = None
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
if not self.isPyScript:
|
|
155
|
-
try: self.updater = updater(parent=self)
|
|
156
|
-
except: self.updater = None
|
|
157
|
-
|
|
159
|
+
|
|
158
160
|
# initialize module controller
|
|
159
161
|
self.modules = Modules(parent=self)
|
|
160
162
|
|
|
@@ -192,7 +194,7 @@ class UC2Client(object):
|
|
|
192
194
|
if timeout <=0:
|
|
193
195
|
getReturn = False
|
|
194
196
|
return self.serial.post_json(path, payload=None, getReturn=getReturn, nResponses=1, timeout=timeout)
|
|
195
|
-
#return self.serial.read_json()
|
|
197
|
+
#return self.serial.read_json()<
|
|
196
198
|
else:
|
|
197
199
|
self.logger.error("No ESP32 device is connected - check IP or Serial port!")
|
|
198
200
|
return None
|
|
@@ -202,4 +204,4 @@ class UC2Client(object):
|
|
|
202
204
|
self.serial.DEBUG = debug
|
|
203
205
|
|
|
204
206
|
def close(self):
|
|
205
|
-
self.serial.
|
|
207
|
+
self.serial.close()
|
|
@@ -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.23"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
class Gripper(object):
|
|
5
|
+
"""
|
|
6
|
+
Example Python class for controlling a gripper servo via JSON commands
|
|
7
|
+
to the MCU, similar in style to the Objective class provided.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, parent):
|
|
11
|
+
"""
|
|
12
|
+
parent: an object that handles the low-level post_json(path, payload, getReturn, timeout, nResponses)
|
|
13
|
+
similar to the 'Home' or 'Objective' parent in the template.
|
|
14
|
+
"""
|
|
15
|
+
self._parent = parent
|
|
16
|
+
self.timeout = 10 # default timeout for blocking commands
|
|
17
|
+
# Default angles
|
|
18
|
+
self.close_angle = 0
|
|
19
|
+
self.open_angle = 180
|
|
20
|
+
|
|
21
|
+
def close(self, isBlocking=False):
|
|
22
|
+
"""
|
|
23
|
+
Closes the gripper to close_angle (0° by default).
|
|
24
|
+
Sends {"task":"/gripper_act","action":"close"} over Serial/JSON.
|
|
25
|
+
"""
|
|
26
|
+
path = "/gripper_act"
|
|
27
|
+
payload = {
|
|
28
|
+
"task": path,
|
|
29
|
+
"action": "close"
|
|
30
|
+
}
|
|
31
|
+
# Possibly wait for the MCU response
|
|
32
|
+
nResponses = 1 if isBlocking else 0
|
|
33
|
+
r = self._parent.post_json(
|
|
34
|
+
path,
|
|
35
|
+
payload,
|
|
36
|
+
getReturn=isBlocking,
|
|
37
|
+
timeout=self.timeout if isBlocking else 0,
|
|
38
|
+
nResponses=nResponses
|
|
39
|
+
)
|
|
40
|
+
return r
|
|
41
|
+
|
|
42
|
+
def open(self, isBlocking=False):
|
|
43
|
+
"""
|
|
44
|
+
Opens the gripper to open_angle (180° by default).
|
|
45
|
+
Sends {"task":"/gripper_act","action":"open"}.
|
|
46
|
+
"""
|
|
47
|
+
path = "/gripper_act"
|
|
48
|
+
payload = {
|
|
49
|
+
"task": path,
|
|
50
|
+
"action": "open"
|
|
51
|
+
}
|
|
52
|
+
nResponses = 1 if isBlocking else 0
|
|
53
|
+
r = self._parent.post_json(
|
|
54
|
+
path,
|
|
55
|
+
payload,
|
|
56
|
+
getReturn=isBlocking,
|
|
57
|
+
timeout=self.timeout if isBlocking else 0,
|
|
58
|
+
nResponses=nResponses
|
|
59
|
+
)
|
|
60
|
+
return r
|
|
61
|
+
|
|
62
|
+
def setAngle(self, angle, isBlocking=False):
|
|
63
|
+
"""
|
|
64
|
+
Moves the gripper servo to an arbitrary angle (0–180).
|
|
65
|
+
Sends {"task":"/gripper_act","action":"degree","value":<angle>}.
|
|
66
|
+
"""
|
|
67
|
+
path = "/gripper_act"
|
|
68
|
+
payload = {
|
|
69
|
+
"task": path,
|
|
70
|
+
"action": "degree",
|
|
71
|
+
"value": angle
|
|
72
|
+
}
|
|
73
|
+
nResponses = 1 if isBlocking else 0
|
|
74
|
+
r = self._parent.post_json(
|
|
75
|
+
path,
|
|
76
|
+
payload,
|
|
77
|
+
getReturn=isBlocking,
|
|
78
|
+
timeout=self.timeout if isBlocking else 0,
|
|
79
|
+
nResponses=nResponses
|
|
80
|
+
)
|
|
81
|
+
return r
|
|
82
|
+
|
|
83
|
+
def getstatus(self):
|
|
84
|
+
"""
|
|
85
|
+
Query the current gripper status.
|
|
86
|
+
Expects MCU to return something like:
|
|
87
|
+
{
|
|
88
|
+
"gripper":{
|
|
89
|
+
"pos": 90,
|
|
90
|
+
"isOpen": 0 or 1,
|
|
91
|
+
"state": 1,
|
|
92
|
+
"isRunning": 0
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
"""
|
|
96
|
+
path = "/gripper_get"
|
|
97
|
+
payload = {"task": path}
|
|
98
|
+
r = self._parent.post_json(path, payload, timeout=1)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# The last item in r is assumed to contain the "gripper" field
|
|
102
|
+
status = r[-1]["gripper"]
|
|
103
|
+
except:
|
|
104
|
+
# Fallback if parsing fails
|
|
105
|
+
status = {"pos": 0, "isOpen": None, "state": 0, "isRunning": 0}
|
|
106
|
+
|
|
107
|
+
return status
|
|
@@ -15,7 +15,7 @@ class Home(object):
|
|
|
15
15
|
# axis = 1 corresponds to 'X'
|
|
16
16
|
axis = 1
|
|
17
17
|
self.home(axis=axis,
|
|
18
|
-
|
|
18
|
+
endstoptimeout=timeout,
|
|
19
19
|
speed = speed,
|
|
20
20
|
direction = direction,
|
|
21
21
|
endposrelease=endposrelease,
|
|
@@ -26,7 +26,7 @@ class Home(object):
|
|
|
26
26
|
# axis = 2 corresponds to 'Y'
|
|
27
27
|
axis = 2
|
|
28
28
|
self.home(axis=axis,
|
|
29
|
-
|
|
29
|
+
endstoptimeout=timeout,
|
|
30
30
|
speed = speed,
|
|
31
31
|
direction = direction,
|
|
32
32
|
endposrelease=endposrelease,
|
|
@@ -37,14 +37,25 @@ class Home(object):
|
|
|
37
37
|
# axisa = 3 corresponds to 'Z'
|
|
38
38
|
axis = 3
|
|
39
39
|
self.home(axis=axis,
|
|
40
|
-
|
|
40
|
+
endstoptimeout=timeout,
|
|
41
41
|
speed = speed,
|
|
42
42
|
direction = direction,
|
|
43
43
|
endposrelease=endposrelease,
|
|
44
44
|
endstoppolarity=endstoppolarity,
|
|
45
45
|
isBlocking=isBlocking)
|
|
46
46
|
|
|
47
|
-
def
|
|
47
|
+
def home_a(self, speed = None, direction = None, endposrelease = None, endstoppolarity=None, timeout=None, isBlocking=False):
|
|
48
|
+
# axis = 0 corresponds to 'A'
|
|
49
|
+
axis = 0
|
|
50
|
+
self.home(axis=axis,
|
|
51
|
+
endstoptimeout=timeout,
|
|
52
|
+
speed = speed,
|
|
53
|
+
direction = direction,
|
|
54
|
+
endposrelease=endposrelease,
|
|
55
|
+
endstoppolarity=endstoppolarity,
|
|
56
|
+
isBlocking=isBlocking)
|
|
57
|
+
|
|
58
|
+
def home(self, axis=None, timeout=None, speed=None, direction=None, endposrelease=None, endstoppolarity=None, endstoptimeout=10000, isBlocking=False):
|
|
48
59
|
'''
|
|
49
60
|
axis = 0,1,2,3 or 'A, 'X','Y','Z'
|
|
50
61
|
timeout => when to stop homing (it's a while loop on the MCU)
|
|
@@ -77,10 +88,9 @@ class Home(object):
|
|
|
77
88
|
"steppers": [
|
|
78
89
|
{
|
|
79
90
|
"stepperid": axis,
|
|
80
|
-
"timeout":
|
|
81
|
-
"speed":
|
|
91
|
+
"timeout":endstoptimeout,
|
|
92
|
+
"speed":abs(speed),
|
|
82
93
|
"direction":direction,
|
|
83
|
-
"endposrelease":endposrelease,
|
|
84
94
|
"endstoppolarity":endstoppolarity
|
|
85
95
|
}]
|
|
86
96
|
}}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
gTimeout = 2
|
|
4
|
+
|
|
5
|
+
class LedMatrix(object):
|
|
6
|
+
def __init__(self, parent, NLeds=64):
|
|
7
|
+
"""
|
|
8
|
+
This class handles sending JSON commands for controlling a NeoPixel or LED matrix device.
|
|
9
|
+
The 'parent' is assumed to be an object providing post_json(...) and get_json(...) methods.
|
|
10
|
+
"""
|
|
11
|
+
self.NLeds = NLeds
|
|
12
|
+
self.Nx = self.Ny = int(np.sqrt(NLeds)) if NLeds > 0 else 8
|
|
13
|
+
|
|
14
|
+
# We assume the pattern is binary (0 or 1) for an NxN grid, stored in ledpattern NxN or flattened.
|
|
15
|
+
# We store an RGB for each LED if needed. -1 indicates "unset" or no color assigned.
|
|
16
|
+
self.ledpattern = np.ones((self.NLeds, 3)) * -1
|
|
17
|
+
|
|
18
|
+
self._parent = parent # must have post_json() etc.
|
|
19
|
+
self.timeout = 1
|
|
20
|
+
self.intensity = (255, 255, 255)
|
|
21
|
+
|
|
22
|
+
# Maps textual modes to an integer, used for backward compatibility
|
|
23
|
+
self.ledArrayModes = {
|
|
24
|
+
"array": 0,
|
|
25
|
+
"full": 1,
|
|
26
|
+
"single": 2,
|
|
27
|
+
"off": 3,
|
|
28
|
+
"left": 4,
|
|
29
|
+
"right": 5,
|
|
30
|
+
"top": 6,
|
|
31
|
+
"bottom": 7,
|
|
32
|
+
"multi": 8
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
self.currentLedArrayMode = "full"
|
|
36
|
+
|
|
37
|
+
######################################################################################################
|
|
38
|
+
# BASE / LEGACY METHODS
|
|
39
|
+
######################################################################################################
|
|
40
|
+
|
|
41
|
+
def send_LEDMatrix_array(self, led_pattern, getReturn=True, timeout=gTimeout):
|
|
42
|
+
"""
|
|
43
|
+
Send an LED array pattern e.g. a list of { id, r, g, b } or a NumPy array shape (N,3).
|
|
44
|
+
Produces JSON:
|
|
45
|
+
{
|
|
46
|
+
"task": "/ledarr_act",
|
|
47
|
+
"led": {
|
|
48
|
+
"action": "array",
|
|
49
|
+
"LEDArrMode": 0,
|
|
50
|
+
"led_array": [
|
|
51
|
+
{"id":0,"r":...,"g":...,"b":...},
|
|
52
|
+
...
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
"""
|
|
57
|
+
path = "/ledarr_act"
|
|
58
|
+
|
|
59
|
+
# If led_pattern is a NumPy array, convert it to a list of dict
|
|
60
|
+
if not isinstance(led_pattern, list):
|
|
61
|
+
if len(led_pattern.shape) == 3:
|
|
62
|
+
# flatten 3D into 2D
|
|
63
|
+
led_pattern = np.reshape(led_pattern, (led_pattern.shape[0]*led_pattern.shape[1], led_pattern.shape[2]))
|
|
64
|
+
|
|
65
|
+
pattern_list = []
|
|
66
|
+
for i in range(led_pattern.shape[0]):
|
|
67
|
+
pattern_list.append({
|
|
68
|
+
"id": i,
|
|
69
|
+
"r": int(led_pattern[i, 0]),
|
|
70
|
+
"g": int(led_pattern[i, 1]),
|
|
71
|
+
"b": int(led_pattern[i, 2])
|
|
72
|
+
})
|
|
73
|
+
else:
|
|
74
|
+
pattern_list = led_pattern
|
|
75
|
+
|
|
76
|
+
payload = {
|
|
77
|
+
"task": path,
|
|
78
|
+
"qid": 0, # or fill in dynamically if you like
|
|
79
|
+
"led": {
|
|
80
|
+
"action": "array",
|
|
81
|
+
"LEDArrMode": self.ledArrayModes["array"],
|
|
82
|
+
"led_array": pattern_list
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
87
|
+
if not getReturn or timeout == 0:
|
|
88
|
+
r = {"success": 1}
|
|
89
|
+
self.currentLedArrayMode = "array"
|
|
90
|
+
return r
|
|
91
|
+
|
|
92
|
+
def send_LEDMatrix_full(self, intensity=(255, 255, 255), getReturn=True, timeout=gTimeout):
|
|
93
|
+
"""
|
|
94
|
+
Fill all LEDs with the same (r,g,b).
|
|
95
|
+
Creates JSON with: "action": "fill"
|
|
96
|
+
"""
|
|
97
|
+
path = "/ledarr_act"
|
|
98
|
+
payload = {
|
|
99
|
+
"task": path,
|
|
100
|
+
"qid": 0,
|
|
101
|
+
"led": {
|
|
102
|
+
"action": "fill",
|
|
103
|
+
"LEDArrMode": self.ledArrayModes["full"],
|
|
104
|
+
"r": int(intensity[0]),
|
|
105
|
+
"g": int(intensity[1]),
|
|
106
|
+
"b": int(intensity[2])
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
111
|
+
if not getReturn or timeout == 0:
|
|
112
|
+
r = {"success": 1}
|
|
113
|
+
self.currentLedArrayMode = "full"
|
|
114
|
+
return r
|
|
115
|
+
|
|
116
|
+
def send_LEDMatrix_single(self, indexled=0, intensity=(255, 255, 255), getReturn=True, timeout=gTimeout):
|
|
117
|
+
"""
|
|
118
|
+
Update only a single LED with color (r,g,b).
|
|
119
|
+
Creates JSON: "action":"single"
|
|
120
|
+
"""
|
|
121
|
+
path = "/ledarr_act"
|
|
122
|
+
payload = {
|
|
123
|
+
"task": path,
|
|
124
|
+
"qid": 0,
|
|
125
|
+
"led": {
|
|
126
|
+
"action": "single",
|
|
127
|
+
"LEDArrMode": self.ledArrayModes["single"],
|
|
128
|
+
"ledIndex": int(indexled),
|
|
129
|
+
"r": int(intensity[0]),
|
|
130
|
+
"g": int(intensity[1]),
|
|
131
|
+
"b": int(intensity[2])
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
136
|
+
if not getReturn or timeout == 0:
|
|
137
|
+
r = {"success": 1}
|
|
138
|
+
self.currentLedArrayMode = "single"
|
|
139
|
+
return r
|
|
140
|
+
|
|
141
|
+
def get_LEDMatrix(self, timeout=gTimeout):
|
|
142
|
+
"""
|
|
143
|
+
Request info from the device about pin/LED count, etc.
|
|
144
|
+
Usually the device responds with JSON containing these details.
|
|
145
|
+
"""
|
|
146
|
+
path = "/ledarr_get"
|
|
147
|
+
payload = {"task": path}
|
|
148
|
+
r = self._parent.post_json(path, payload, getReturn=True, timeout=timeout)
|
|
149
|
+
return r
|
|
150
|
+
|
|
151
|
+
def setSingle(self, indexled, state):
|
|
152
|
+
"""
|
|
153
|
+
High-level usage: set a single LED's "state" (0 or 1), then send it with intensity scaling.
|
|
154
|
+
"""
|
|
155
|
+
ix = indexled // self.Nx
|
|
156
|
+
iy = indexled % self.Nx
|
|
157
|
+
self.ledpattern[indexled] = (state, state, state)
|
|
158
|
+
# If your wiring is zig-zag or reversed, you may need to adapt index
|
|
159
|
+
if ix % 2 != 0:
|
|
160
|
+
indexled = (ix * self.Nx) + (self.Ny - iy - 1)
|
|
161
|
+
|
|
162
|
+
intensityScaled = np.array(state) * np.array(self.intensity)
|
|
163
|
+
self.send_LEDMatrix_single(indexled=indexled, intensity=intensityScaled, timeout=self.timeout)
|
|
164
|
+
return self.ledpattern
|
|
165
|
+
|
|
166
|
+
def setAll(self, state: int, intensity:int =None, getReturn=True):
|
|
167
|
+
"""
|
|
168
|
+
Turn on or off all LEDs at a certain intensity.
|
|
169
|
+
If state is a boolean array or a single boolean, we set them all.
|
|
170
|
+
"""
|
|
171
|
+
onval = np.sum(state) > 0
|
|
172
|
+
if intensity is not None:
|
|
173
|
+
self.intensity = intensity
|
|
174
|
+
intensity2display = np.array(self.intensity) * onval
|
|
175
|
+
self.send_LEDMatrix_full(intensity=intensity2display, getReturn=getReturn)
|
|
176
|
+
self.ledpattern = onval * np.ones((self.NLeds, 3))
|
|
177
|
+
return self.ledpattern
|
|
178
|
+
|
|
179
|
+
def setIntensity(self, intensity:int, getReturn:bool=True):
|
|
180
|
+
self.intensity = intensity
|
|
181
|
+
self.setPattern(getReturn=getReturn)
|
|
182
|
+
|
|
183
|
+
def setPattern(self, getReturn:bool=True, ledpattern=None):
|
|
184
|
+
"""
|
|
185
|
+
If ledpattern is provided, store it, then send to device.
|
|
186
|
+
If the entire pattern is "on", we do a 'full' fill for speed,
|
|
187
|
+
otherwise we do 'array' updates for each LED.
|
|
188
|
+
"""
|
|
189
|
+
if ledpattern is not None:
|
|
190
|
+
self.ledpattern = ledpattern
|
|
191
|
+
pattern2send = (self.ledpattern >= 1) * self.intensity
|
|
192
|
+
|
|
193
|
+
if np.sum(self.ledpattern, 0)[0] == self.ledpattern.shape[0]:
|
|
194
|
+
# All on => just do a fill
|
|
195
|
+
self.send_LEDMatrix_full(pattern2send[0, :], getReturn=getReturn)
|
|
196
|
+
else:
|
|
197
|
+
# partial => send an array of per-LED
|
|
198
|
+
self.send_LEDMatrix_array(pattern2send, getReturn=getReturn)
|
|
199
|
+
return self.ledpattern
|
|
200
|
+
|
|
201
|
+
def getPattern(self):
|
|
202
|
+
return self.ledpattern
|
|
203
|
+
|
|
204
|
+
def set_led(self, colour=(0, 0, 0)):
|
|
205
|
+
"""
|
|
206
|
+
(Legacy) Possibly sets a single color.
|
|
207
|
+
Demo only - not used in the new command style.
|
|
208
|
+
"""
|
|
209
|
+
path = "/led"
|
|
210
|
+
payload = {"r": colour[0], "g": colour[1], "b": colour[2]}
|
|
211
|
+
r = self._parent.post_json(path, payload)
|
|
212
|
+
return r
|
|
213
|
+
|
|
214
|
+
def get_ledpin(self):
|
|
215
|
+
"""
|
|
216
|
+
Another legacy method that might do a GET request.
|
|
217
|
+
"""
|
|
218
|
+
path = "/ledarr_get"
|
|
219
|
+
r = self._parent.get_json(path, getReturn=True, timeout=1)
|
|
220
|
+
return r
|
|
221
|
+
|
|
222
|
+
######################################################################################################
|
|
223
|
+
# NEW METHODS FOR "OFF", "RINGS", "CIRCLES", "HALVES", etc.
|
|
224
|
+
######################################################################################################
|
|
225
|
+
def send_LEDMatrix_off(self, getReturn=True, timeout=gTimeout):
|
|
226
|
+
"""
|
|
227
|
+
Turn all LEDs off.
|
|
228
|
+
JSON: "action":"off"
|
|
229
|
+
"""
|
|
230
|
+
path = "/ledarr_act"
|
|
231
|
+
payload = {
|
|
232
|
+
"task": path,
|
|
233
|
+
"qid": 0,
|
|
234
|
+
"led": {
|
|
235
|
+
"action": "off"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
239
|
+
return r
|
|
240
|
+
|
|
241
|
+
def send_LEDMatrix_halves(self, region="left", intensity=(255,255,255), getReturn=True, timeout=gTimeout):
|
|
242
|
+
"""
|
|
243
|
+
Light only "left"/"right"/"top"/"bottom" half in color, rest is dark.
|
|
244
|
+
JSON: "action":"halves", "region": <string>
|
|
245
|
+
"""
|
|
246
|
+
if type(intensity)==int:
|
|
247
|
+
intensity = (intensity, intensity, intensity)
|
|
248
|
+
path = "/ledarr_act"
|
|
249
|
+
payload = {
|
|
250
|
+
"task": path,
|
|
251
|
+
"qid": 0,
|
|
252
|
+
"led": {
|
|
253
|
+
"action": "halves",
|
|
254
|
+
"region": region,
|
|
255
|
+
"r": int(intensity[0]),
|
|
256
|
+
"g": int(intensity[1]),
|
|
257
|
+
"b": int(intensity[2])
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
261
|
+
return r
|
|
262
|
+
|
|
263
|
+
def send_LEDMatrix_rings(self, radius=4, intensity=(255,255,255), getReturn=True, timeout=gTimeout):
|
|
264
|
+
"""
|
|
265
|
+
Draw a ring of radius N. Often done by filling the circle and carving out center to make it hollow.
|
|
266
|
+
JSON: "action":"rings", "radius": <int>
|
|
267
|
+
"""
|
|
268
|
+
if type(intensity)==int:
|
|
269
|
+
intensity = (intensity, intensity, intensity)
|
|
270
|
+
path = "/ledarr_act"
|
|
271
|
+
payload = {
|
|
272
|
+
"task": path,
|
|
273
|
+
"qid": 0,
|
|
274
|
+
"led": {
|
|
275
|
+
"action": "rings",
|
|
276
|
+
"radius": radius,
|
|
277
|
+
"r": int(intensity[0]),
|
|
278
|
+
"g": int(intensity[1]),
|
|
279
|
+
"b": int(intensity[2])
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
283
|
+
return r
|
|
284
|
+
|
|
285
|
+
def send_LEDMatrix_circles(self, radius=4, intensity=(255,255,255), getReturn=True, timeout=gTimeout):
|
|
286
|
+
"""
|
|
287
|
+
Draw a filled circle of radius N in color.
|
|
288
|
+
JSON: "action":"circles", "radius": <int>
|
|
289
|
+
"""
|
|
290
|
+
if type(intensity)==int:
|
|
291
|
+
intensity = (intensity, intensity, intensity)
|
|
292
|
+
path = "/ledarr_act"
|
|
293
|
+
payload = {
|
|
294
|
+
"task": path,
|
|
295
|
+
"qid": 0,
|
|
296
|
+
"led": {
|
|
297
|
+
"action": "circles",
|
|
298
|
+
"radius": radius,
|
|
299
|
+
"r": int(intensity[0]),
|
|
300
|
+
"g": int(intensity[1]),
|
|
301
|
+
"b": int(intensity[2])
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
r = self._parent.post_json(path, payload, getReturn=getReturn, timeout=timeout)
|
|
305
|
+
return r
|
|
306
|
+
|
|
307
|
+
def send_LEDMatrix_status(self, status="idle"):
|
|
308
|
+
"""
|
|
309
|
+
Set the status of the LED matrix to "idle" or "busy".
|
|
310
|
+
JSON: "action":"status", "status": <string>
|
|
311
|
+
"""
|
|
312
|
+
if status not in ["error", "idle", "warn", "success", "busy", "rainbow"]:
|
|
313
|
+
status = "idle"
|
|
314
|
+
path = "/ledarr_act"
|
|
315
|
+
payload = {
|
|
316
|
+
"task": path,
|
|
317
|
+
"qid": 0,
|
|
318
|
+
"led": {
|
|
319
|
+
"action": "status",
|
|
320
|
+
"status": status
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
r = self._parent.post_json(path, payload)
|
|
324
|
+
return r
|