UC2-REST 0.2.0.37__tar.gz → 0.2.0.38__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 (46) hide show
  1. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/PKG-INFO +1 -1
  2. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/SOURCES.txt +2 -0
  4. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/UC2Client.py +9 -1
  5. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/__version__.py +1 -1
  6. uc2_rest-0.2.0.38/uc2rest/gpio.py +171 -0
  7. uc2_rest-0.2.0.38/uc2rest/i2c.py +190 -0
  8. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/motor.py +1 -1
  9. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/mserial.py +8 -5
  10. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/LICENSE +0 -0
  11. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/README.md +0 -0
  12. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/dependency_links.txt +0 -0
  13. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/not-zip-safe +0 -0
  14. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/requires.txt +0 -0
  15. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/UC2_REST.egg-info/top_level.txt +0 -0
  16. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/setup.cfg +0 -0
  17. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/setup.py +0 -0
  18. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/MockSerial.py +0 -0
  19. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/__init__.py +0 -0
  20. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/analog.py +0 -0
  21. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/camera.py +0 -0
  22. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/camera_trigger.py +0 -0
  23. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/can.py +0 -0
  24. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/canota.py +0 -0
  25. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/cmdrecorder.py +0 -0
  26. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/digitalin.py +0 -0
  27. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/digitalout.py +0 -0
  28. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/fan.py +0 -0
  29. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/galvo.py +0 -0
  30. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/gripper.py +0 -0
  31. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/home.py +0 -0
  32. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/laser.py +0 -0
  33. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/lcddisplay.py +0 -0
  34. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/ledmatrix.py +0 -0
  35. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/logger.py +0 -0
  36. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/message.py +0 -0
  37. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/modules.py +0 -0
  38. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/motor_config.py +0 -0
  39. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/objective.py +0 -0
  40. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/pid.py +0 -0
  41. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/rotator.py +0 -0
  42. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/slm.py +0 -0
  43. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/state.py +0 -0
  44. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/temperature.py +0 -0
  45. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/utils.py +0 -0
  46. {uc2_rest-0.2.0.37 → uc2_rest-0.2.0.38}/uc2rest/wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.37
3
+ Version: 0.2.0.38
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.4
2
2
  Name: UC2-REST
3
- Version: 0.2.0.37
3
+ Version: 0.2.0.38
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
@@ -21,8 +21,10 @@ uc2rest/digitalin.py
21
21
  uc2rest/digitalout.py
22
22
  uc2rest/fan.py
23
23
  uc2rest/galvo.py
24
+ uc2rest/gpio.py
24
25
  uc2rest/gripper.py
25
26
  uc2rest/home.py
27
+ uc2rest/i2c.py
26
28
  uc2rest/laser.py
27
29
  uc2rest/lcddisplay.py
28
30
  uc2rest/ledmatrix.py
@@ -22,6 +22,8 @@ from .analog import Analog
22
22
  from .modules import Modules
23
23
  from .digitalout import DigitalOut
24
24
  from .digitalin import DigitalIn
25
+ from .gpio import GPIO
26
+ from .i2c import I2C
25
27
  from .rotator import Rotator
26
28
  from .logger import Logger
27
29
  from .cmdrecorder import cmdRecorder
@@ -155,7 +157,13 @@ class UC2Client(object):
155
157
 
156
158
  # initialize digital in
157
159
  self.digitalin = DigitalIn(self)
158
-
160
+
161
+ # initialize GPIO / collision-detector slave interface
162
+ self.gpio = GPIO(self)
163
+
164
+ # initialize generic I2C passthrough (sensors on the GPIO slave bus)
165
+ self.i2c = I2C(self)
166
+
159
167
  # initialize messaging
160
168
  self.message = Message(self)
161
169
 
@@ -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.37"
9
+ __version__ = "v0.2.0.38"
10
10
  __author__ = 'Benedict Diederich'
11
11
  __author_email__ = 'benedictdied@gmail.com'
12
12
  __license__ = 'GPL v3'
@@ -0,0 +1,171 @@
1
+ """GPIO / collision-detector interface for the UC2 CAN GPIO slave.
2
+
3
+ The CAN master exposes the GPIO slave (XIAO node, default CAN node-id 60)
4
+ through two serial endpoints:
5
+
6
+ {"task":"/gpio_get"}
7
+ -> {"gpio":{"node":60,"mean":585,"filtered":584,"raw":581,
8
+ "reference":585,"threshold":150,"sensitivity":4,
9
+ "trip":0,"estop":0},"ok":true,"qid":N}
10
+
11
+ {"task":"/gpio_act","threshold":150,"sensitivity":4,
12
+ "reference":585,"calibrate":1} (all value fields optional)
13
+
14
+ Collision detection runs ON THE SLAVE: the sensor has an idle value (the
15
+ "reference"); a collision = `sensitivity` consecutive samples deviating more
16
+ than `threshold` ADC counts from the reference (rising OR falling). Sensor
17
+ values are never streamed on the CAN bus — polling /gpio_get triggers SDO
18
+ reads on demand. Only the collision/E-stop EVENT is pushed asynchronously;
19
+ the master then prints:
20
+
21
+ {"gpio":{"event":1,"node":60,"trip":1,"estop":0,"flags":3,
22
+ "filtered":2365,"raw":2937,...}}
23
+
24
+ The "event":1 marker distinguishes pushed edges from /gpio_get responses
25
+ (both share the top-level "gpio" key and therefore both arrive at the same
26
+ serial callback).
27
+ """
28
+
29
+
30
+ class GPIO(object):
31
+ def __init__(self, parent):
32
+ self._parent = parent
33
+
34
+ # last known state (merged from get_status responses and async events)
35
+ self.status = {}
36
+
37
+ # register a callback for any {"gpio":{...}} frame on the serial loop
38
+ if hasattr(self._parent, "serial"):
39
+ self._parent.serial.register_callback(self._callback_gpio, pattern="gpio")
40
+
41
+ # user-registered callbacks
42
+ self._collision_callbacks = [] # fired on async trip/clear events
43
+ self._status_callbacks = [] # fired on every gpio frame
44
+
45
+ # ─────────────────────────────────────────────────────────────────
46
+ # Async event plumbing
47
+ # ─────────────────────────────────────────────────────────────────
48
+ def _callback_gpio(self, data):
49
+ """Handle any serial frame with a top-level "gpio" key.
50
+
51
+ Pushed events carry "event":1 and fire the collision callbacks;
52
+ /gpio_get responses only refresh the cached status."""
53
+ try:
54
+ gpio = data.get("gpio", None)
55
+ if not isinstance(gpio, dict):
56
+ return
57
+ self.status.update(gpio)
58
+
59
+ for cb in self._status_callbacks:
60
+ try:
61
+ cb(dict(self.status))
62
+ except Exception as e:
63
+ print(f"Error in gpio status callback: {e}")
64
+
65
+ if gpio.get("event", 0):
66
+ for cb in self._collision_callbacks:
67
+ try:
68
+ cb(dict(gpio))
69
+ except Exception as e:
70
+ print(f"Error in gpio collision callback: {e}")
71
+ except Exception as e:
72
+ print("Error in _callback_gpio: ", e)
73
+
74
+ def register_collision_callback(self, callbackfct):
75
+ """Register a function called on every asynchronously pushed GPIO
76
+ event (collision trip/clear, E-stop press/release). The callback
77
+ receives the event dict, e.g. {"event":1,"trip":1,"estop":0,...}.
78
+ Check ["trip"] to distinguish collision-began from collision-cleared."""
79
+ if callbackfct not in self._collision_callbacks:
80
+ self._collision_callbacks.append(callbackfct)
81
+
82
+ def register_status_callback(self, callbackfct):
83
+ """Register a function called on EVERY gpio frame (async events and
84
+ /gpio_get responses alike) with the merged status dict."""
85
+ if callbackfct not in self._status_callbacks:
86
+ self._status_callbacks.append(callbackfct)
87
+
88
+ # ─────────────────────────────────────────────────────────────────
89
+ # Query / configuration
90
+ # ─────────────────────────────────────────────────────────────────
91
+ @staticmethod
92
+ def _extract_gpio(response):
93
+ """Unwrap a post_json return into the "gpio" dict.
94
+
95
+ mserial.sendMessage returns a LIST of response dicts on success
96
+ (self.responses[qid] is a per-frame list), a plain string on
97
+ timeout/interrupt, or an int qid when getReturn=False — so handle
98
+ all shapes defensively."""
99
+ candidates = response if isinstance(response, list) else [response]
100
+ for item in candidates:
101
+ if isinstance(item, dict):
102
+ gpio = item.get("gpio")
103
+ if isinstance(gpio, dict):
104
+ return gpio
105
+ return {}
106
+
107
+ def get_status(self, node=None, timeout=1):
108
+ """Poll the collision detector (SDO reads on the CAN bus).
109
+
110
+ Returns the "gpio" dict: mean (rolling average), filtered, raw,
111
+ baseline, sigma, mode (0=auto/1=manual), reference, threshold,
112
+ sensitivity, trip, estop."""
113
+ path = "/gpio_get"
114
+ payload = {"task": path}
115
+ if node is not None:
116
+ payload["node"] = node
117
+ r = self._parent.post_json(path, payload, getReturn=True, timeout=timeout)
118
+ gpio = self._extract_gpio(r)
119
+ if gpio:
120
+ self.status.update(gpio)
121
+ return gpio
122
+ # Response didn't parse (timeout/odd shape) — fall back to the cache
123
+ # maintained by the async serial callback, which sees every frame.
124
+ return dict(self.status)
125
+
126
+ def _act(self, node=None, timeout=1, **fields):
127
+ path = "/gpio_act"
128
+ payload = {"task": path}
129
+ if node is not None:
130
+ payload["node"] = node
131
+ payload.update(fields)
132
+ return self._parent.post_json(path, payload, getReturn=True, timeout=timeout)
133
+
134
+ def set_threshold(self, threshold, node=None, timeout=1):
135
+ """Deviation band (ADC counts) around the reference outside which a
136
+ sample votes for "collision". Persisted in the slave's NVS."""
137
+ return self._act(node=node, timeout=timeout, threshold=int(threshold))
138
+
139
+ def set_sensitivity(self, sensitivity, node=None, timeout=1):
140
+ """Number of CONSECUTIVE out-of-band samples (at 50 Hz) required to
141
+ trip — and in-band samples to clear. 3-4 rejects single-sample spikes
142
+ while confirming a real collision within ~60-80 ms."""
143
+ return self._act(node=node, timeout=timeout, sensitivity=int(sensitivity))
144
+
145
+ def set_reference(self, reference, node=None, timeout=1):
146
+ """Explicitly set the idle baseline (ADC counts). Typically you poll
147
+ get_status()["mean"] while the system is idle and write that value
148
+ here — or simply use calibrate()."""
149
+ return self._act(node=node, timeout=timeout, reference=int(reference))
150
+
151
+ def calibrate(self, node=None, timeout=1):
152
+ """Tell the slave to take its CURRENT rolling mean as the new
153
+ reference (persisted in NVS). MANUAL mode only — in AUTO the baseline
154
+ tracks itself. Do this while the system is idle and collision-free."""
155
+ return self._act(node=node, timeout=timeout, calibrate=1)
156
+
157
+ def set_mode(self, mode, node=None, timeout=1):
158
+ """Select the detection mode (persisted on the slave in NVS).
159
+
160
+ mode: "auto" (default, recommended) — adaptive baseline + robust
161
+ sigma z-score. Parameter-free: tracks a slowly drifting
162
+ background and trips on a fast deflection. No calibration.
163
+ mode: "manual" — fixed reference +/- threshold. Deterministic;
164
+ requires the reference to be calibrated to the idle level.
165
+
166
+ Accepts "auto"/"manual" or 0/1."""
167
+ if isinstance(mode, str):
168
+ mval = 1 if mode.lower() == "manual" else 0
169
+ else:
170
+ mval = 1 if int(mode) else 0
171
+ return self._act(node=node, timeout=timeout, mode=mval)
@@ -0,0 +1,190 @@
1
+ """Generic raw-I2C passthrough for the UC2 CAN GPIO slave.
2
+
3
+ Any I2C device wired to the GPIO slave's sensor bus (SDA=GPIO5/D4,
4
+ SCL=GPIO6/D5 on the can-gpio board) can be read/written from Python WITHOUT
5
+ a device driver on the ESP32. The firmware just relays bytes; the register map
6
+ lives here. One JSON request = one raw transaction (optional write, optional
7
+ inter-phase delay, optional read):
8
+
9
+ {"task":"/i2c_act","node":60,"addr":68,"write":[253],"read":6,
10
+ "delay":12,"stop":1}
11
+ -> {"node":60,"status":2,"data":[.. up to 40 bytes ..],"ok":true}
12
+
13
+ addr : 7-bit I2C address (decimal; 0x44 == 68)
14
+ write : bytes to send first (register pointer / command / config)
15
+ read : number of bytes to read back (0 = write-only)
16
+ delay : ms to wait between the write and the read (device conversion time)
17
+ stop : 1 → send STOP after the write (device wants a full stop before the
18
+ read, e.g. SHT4x); 0/omitted → repeated-START (typical register read)
19
+
20
+ Status byte: 2 = ok, 0x81..0x85 = write failed (I2C NACK / bus error),
21
+ 0x90 = fewer bytes read than requested, 0xA0 = bad parameters.
22
+
23
+ See uc2-ESP: main/src/i2c/I2cBridge.cpp and DeviceRouter::handleI2cAct.
24
+ """
25
+
26
+ import time
27
+
28
+
29
+ class I2C(object):
30
+ STATUS_OK = 0x02
31
+
32
+ def __init__(self, parent):
33
+ self._parent = parent
34
+
35
+ # ─────────────────────────────────────────────────────────────────
36
+ # Generic transaction
37
+ # ─────────────────────────────────────────────────────────────────
38
+ @staticmethod
39
+ def _extract(response):
40
+ """Unwrap a post_json return into the response dict carrying "data".
41
+
42
+ mserial.sendMessage returns a LIST of frames on success, a plain
43
+ string on timeout, or an int qid when getReturn=False — handle all."""
44
+ candidates = response if isinstance(response, list) else [response]
45
+ for item in candidates:
46
+ if isinstance(item, dict) and ("data" in item or "status" in item):
47
+ return item
48
+ return {}
49
+
50
+ def transaction(self, addr, write=None, read=0, delay_ms=0, stop=False,
51
+ node=None, timeout=2):
52
+ """Run one raw I2C transaction on the slave and return the response
53
+ dict {"status":int, "data":[...], ...}. `write` is an iterable of
54
+ byte values; `read` is how many bytes to read back."""
55
+ payload = {"task": "/i2c_act",
56
+ "addr": int(addr) & 0x7F,
57
+ "read": int(read),
58
+ "delay": int(delay_ms)}
59
+ if write:
60
+ payload["write"] = [int(b) & 0xFF for b in write]
61
+ if stop:
62
+ payload["stop"] = 1
63
+ if node is not None:
64
+ payload["node"] = node
65
+ r = self._parent.post_json("/i2c_act", payload, getReturn=True,
66
+ timeout=timeout)
67
+ return self._extract(r)
68
+
69
+ def ok(self, response):
70
+ """True if a transaction response reports success."""
71
+ return isinstance(response, dict) and response.get("status") == self.STATUS_OK
72
+
73
+ # ─────────────────────────────────────────────────────────────────
74
+ # Register-level convenience wrappers
75
+ # ─────────────────────────────────────────────────────────────────
76
+ def read_register(self, addr, reg, length, node=None,
77
+ repeated_start=True, timeout=2):
78
+ """Classic register read: write the register pointer, then read
79
+ `length` bytes (repeated-START by default). Returns a bytes object
80
+ (empty on failure)."""
81
+ res = self.transaction(addr, write=[reg], read=length,
82
+ stop=not repeated_start, node=node, timeout=timeout)
83
+ return bytes(res.get("data", []) or [])
84
+
85
+ def write_register(self, addr, reg, values, node=None, timeout=2):
86
+ """Write `values` (int or iterable) to register `reg`. Returns True on
87
+ an acknowledged write."""
88
+ if isinstance(values, int):
89
+ values = [values]
90
+ res = self.transaction(addr, write=[reg] + [int(v) & 0xFF for v in values],
91
+ read=0, stop=True, node=node, timeout=timeout)
92
+ return self.ok(res)
93
+
94
+ def scan(self, node=None, start=0x08, end=0x77, timeout=2):
95
+ """Probe the bus for responding devices (0-byte write per address).
96
+ Returns a list of 7-bit addresses that ACKed."""
97
+ found = []
98
+ for a in range(start, end + 1):
99
+ # zero-length write: beginTransmission + endTransmission(true).
100
+ res = self.transaction(a, write=[], read=0, stop=True,
101
+ node=node, timeout=timeout)
102
+ if self.ok(res):
103
+ found.append(a)
104
+ return found
105
+
106
+ # ─────────────────────────────────────────────────────────────────
107
+ # Adafruit SHT45 — humidity + temperature (addr 0x44)
108
+ # ─────────────────────────────────────────────────────────────────
109
+ @staticmethod
110
+ def _sht_crc(data):
111
+ """Sensirion CRC-8: poly 0x31, init 0xFF."""
112
+ crc = 0xFF
113
+ for b in data:
114
+ crc ^= b
115
+ for _ in range(8):
116
+ crc = ((crc << 1) ^ 0x31) & 0xFF if (crc & 0x80) else (crc << 1) & 0xFF
117
+ return crc
118
+
119
+ def read_sht45(self, addr=0x44, node=None, precision="high", timeout=2):
120
+ """Read the SHT45. Sends the measure command, waits for the
121
+ conversion, reads 6 bytes (T, T_crc, RH, RH_crc) and converts.
122
+
123
+ precision: "high" (0xFD, ~9 ms), "med" (0xF6), "low" (0xE0).
124
+ Returns {"temperature_c", "humidity_pct", "crc_ok"} or None on error."""
125
+ cmd = {"high": 0xFD, "med": 0xF6, "low": 0xE0}.get(precision, 0xFD)
126
+ res = self.transaction(addr, write=[cmd], read=6, delay_ms=12,
127
+ stop=True, node=node, timeout=timeout)
128
+ data = res.get("data", []) or []
129
+ if len(data) < 6:
130
+ return None
131
+ t_ticks = (data[0] << 8) | data[1]
132
+ rh_ticks = (data[3] << 8) | data[4]
133
+ temp_c = -45.0 + 175.0 * t_ticks / 65535.0
134
+ rh = -6.0 + 125.0 * rh_ticks / 65535.0
135
+ rh = max(0.0, min(100.0, rh))
136
+ crc_ok = (self._sht_crc(data[0:2]) == data[2] and
137
+ self._sht_crc(data[3:5]) == data[5])
138
+ return {"temperature_c": temp_c, "humidity_pct": rh, "crc_ok": crc_ok}
139
+
140
+ # ─────────────────────────────────────────────────────────────────
141
+ # Adafruit TSL2591 — high-dynamic-range light sensor (addr 0x29)
142
+ # ─────────────────────────────────────────────────────────────────
143
+ # Command byte: 0xA0 = normal register access; low 5 bits = register addr.
144
+ _TSL_CMD = 0xA0
145
+ _TSL_ENABLE = 0x00 # PON(0x01) | AEN(0x02) | (AIEN/NPIEN optional)
146
+ _TSL_CONTROL = 0x01 # AGAIN[5:4] | ATIME[2:0]
147
+ _TSL_C0DATAL = 0x14 # CH0 low; auto-increments through 0x17 (CH1 high)
148
+ _TSL_GAIN = {0x00: 1.0, 0x10: 25.0, 0x20: 428.0, 0x30: 9876.0}
149
+
150
+ def read_tsl2591(self, addr=0x29, node=None, gain=0x10, integration=0x01,
151
+ timeout=2):
152
+ """Read the TSL2591 broadband (CH0) and IR (CH1) channels and estimate
153
+ lux. Powers the ALS on, applies gain/integration, waits one integration
154
+ period, then reads the four data bytes.
155
+
156
+ gain: 0x00=1x, 0x10=25x (default), 0x20=428x, 0x30=9876x
157
+ integration: 0x00=100ms .. 0x05=600ms (default 0x01 = 200ms)
158
+ Returns {"ch0_full","ch1_ir","visible","gain","integration_ms",
159
+ "lux","saturated"} or None on error."""
160
+ # Enable ALS (PON | AEN).
161
+ if not self.write_register(addr, self._TSL_CMD | self._TSL_ENABLE, 0x03,
162
+ node=node, timeout=timeout):
163
+ return None
164
+ # Set gain + integration time.
165
+ self.write_register(addr, self._TSL_CMD | self._TSL_CONTROL,
166
+ (gain & 0x30) | (integration & 0x07),
167
+ node=node, timeout=timeout)
168
+ atime_ms = 100 * ((integration & 0x07) + 1)
169
+ # Wait one full integration period (Python-side so we aren't limited to
170
+ # the firmware's 255 ms per-transaction delay cap). Small margin added.
171
+ time.sleep(atime_ms / 1000.0 + 0.05)
172
+ # Burst-read CH0/CH1 (4 bytes, auto-increment) via repeated-START.
173
+ res = self.transaction(addr, write=[self._TSL_CMD | self._TSL_C0DATAL],
174
+ read=4, stop=False, node=node, timeout=timeout)
175
+ data = res.get("data", []) or []
176
+ if len(data) < 4:
177
+ return None
178
+ ch0 = data[0] | (data[1] << 8) # full spectrum
179
+ ch1 = data[2] | (data[3] << 8) # IR
180
+ saturated = ch0 >= 0xFFFF or ch1 >= 0xFFFF
181
+
182
+ # Approximate lux (standard TSL2591 formula; device lot variation ~±).
183
+ again = self._TSL_GAIN.get(gain & 0x30, 1.0)
184
+ lux = None
185
+ if ch0 > 0 and not saturated:
186
+ cpl = (atime_ms * again) / 408.0 # counts-per-lux, DF = 408
187
+ lux = max(0.0, (ch0 - ch1) * (1.0 - float(ch1) / float(ch0)) / cpl)
188
+ return {"ch0_full": ch0, "ch1_ir": ch1, "visible": ch0 - ch1,
189
+ "gain": gain, "integration_ms": atime_ms,
190
+ "lux": lux, "saturated": saturated}
@@ -1098,7 +1098,7 @@ class Motor(object):
1098
1098
  '''
1099
1099
  if type(axis) != int:
1100
1100
  axis = self.xyztTo1230(axis)
1101
-
1101
+ # {"task":"/motor_act", "joystickdir": {"steppers": [{"stepperid": 1, "inverted": 1}]}}
1102
1102
  path = "/motor_act"
1103
1103
  payload = {
1104
1104
  "task": path,
@@ -364,11 +364,14 @@ class Serial:
364
364
  for i in range(500):
365
365
  # if we just want to send but not even wait for a response
366
366
  mReadline = self._read(ser)
367
- if self.DEBUG and mReadline != "" and mReadline != "\n" and mReadline != b'' and mReadline != b'\n':
368
- self._logger.debug("[checkFirmware]: "+str(mReadline))
369
- if mReadline.decode('utf-8').strip() == "++" or mReadline.decode('utf-8').strip().find("++")>=0:
370
- self._freeSerialBuffer(ser)
371
- return True
367
+ try:
368
+ if self.DEBUG and mReadline != "" and mReadline != "\n" and mReadline != b'' and mReadline != b'\n':
369
+ self._logger.debug("[checkFirmware]: "+str(mReadline))
370
+ if mReadline.decode('utf-8').strip() == "++" or mReadline.decode('utf-8').strip().find("++")>=0:
371
+ self._freeSerialBuffer(ser)
372
+ return True
373
+ except Exception as e:
374
+ self._logger.error("Error in checkFirmware: "+str(e))
372
375
  return False
373
376
 
374
377
  def _generate_identifier(self):
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