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.
Files changed (43) hide show
  1. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/PKG-INFO +14 -5
  2. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/PKG-INFO +14 -5
  3. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/SOURCES.txt +3 -1
  4. uc2_rest-0.2.0.23/UC2_REST.egg-info/requires.txt +3 -0
  5. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/setup.py +1 -1
  6. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/UC2Client.py +14 -12
  7. uc2_rest-0.2.0.23/uc2rest/__init__.py +2 -0
  8. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/__version__.py +1 -1
  9. uc2_rest-0.2.0.23/uc2rest/gripper.py +107 -0
  10. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/home.py +17 -7
  11. uc2_rest-0.2.0.23/uc2rest/ledmatrix.py +324 -0
  12. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/motor.py +184 -43
  13. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/mserial.py +179 -41
  14. uc2_rest-0.2.0.23/uc2rest/objective.py +230 -0
  15. uc2_rest-0.2.0.21/UC2_REST.egg-info/requires.txt +0 -6
  16. uc2_rest-0.2.0.21/uc2rest/__init__.py +0 -6
  17. uc2_rest-0.2.0.21/uc2rest/updater.py +0 -268
  18. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/LICENSE +0 -0
  19. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/README.md +0 -0
  20. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/dependency_links.txt +0 -0
  21. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/not-zip-safe +0 -0
  22. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/UC2_REST.egg-info/top_level.txt +0 -0
  23. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/setup.cfg +0 -0
  24. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/MockSerial.py +0 -0
  25. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/analog.py +0 -0
  26. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/camera.py +0 -0
  27. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/cmdrecorder.py +0 -0
  28. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/config.py +0 -0
  29. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/config_.py +0 -0
  30. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/digitalout.py +0 -0
  31. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/galvo.py +0 -0
  32. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/laser.py +0 -0
  33. /uc2_rest-0.2.0.21/uc2rest/ledmatrix.py → /uc2_rest-0.2.0.23/uc2rest/ledmatrix_.py +0 -0
  34. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/logger.py +0 -0
  35. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/message.py +0 -0
  36. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/modules.py +0 -0
  37. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/pid.py +0 -0
  38. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/rotator.py +0 -0
  39. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/slm.py +0 -0
  40. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/state.py +0 -0
  41. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/temperature.py +0 -0
  42. {uc2_rest-0.2.0.21 → uc2_rest-0.2.0.23}/uc2rest/utils.py +0 -0
  43. {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
1
+ Metadata-Version: 2.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.21
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
- Requires-Dist: esptool
20
- Requires-Dist: progressbar2
21
- Requires-Dist: pillow
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
1
+ Metadata-Version: 2.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.21
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
- Requires-Dist: esptool
20
- Requires-Dist: progressbar2
21
- Requires-Dist: pillow
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
@@ -0,0 +1,3 @@
1
+ numpy
2
+ requests
3
+ pyserial
@@ -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', "esptool", "progressbar2", "pillow"],
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
- # initialize updater
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.closeSerial()
207
+ self.serial.close()
@@ -0,0 +1,2 @@
1
+ from .config import *
2
+ from .UC2Client import *
@@ -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.21"
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
- timeout=timeout,
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
- timeout=timeout,
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
- timeout=timeout,
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 home(self, axis=None, timeout=None, speed=None, direction=None, endposrelease=None, endstoppolarity=None, isBlocking=False):
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":timeout*1000,
81
- "speed":direction*abs(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