UC2-REST 0.2.0.38__tar.gz → 0.2.0.39__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 (47) hide show
  1. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/PKG-INFO +1 -1
  2. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/PKG-INFO +1 -1
  3. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/SOURCES.txt +1 -0
  4. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/UC2Client.py +4 -0
  5. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/__version__.py +1 -1
  6. uc2_rest-0.2.0.39/uc2rest/ptz.py +156 -0
  7. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/LICENSE +0 -0
  8. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/README.md +0 -0
  9. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/dependency_links.txt +0 -0
  10. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/not-zip-safe +0 -0
  11. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/requires.txt +0 -0
  12. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/UC2_REST.egg-info/top_level.txt +0 -0
  13. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/setup.cfg +0 -0
  14. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/setup.py +0 -0
  15. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/MockSerial.py +0 -0
  16. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/__init__.py +0 -0
  17. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/analog.py +0 -0
  18. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/camera.py +0 -0
  19. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/camera_trigger.py +0 -0
  20. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/can.py +0 -0
  21. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/canota.py +0 -0
  22. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/cmdrecorder.py +0 -0
  23. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/digitalin.py +0 -0
  24. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/digitalout.py +0 -0
  25. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/fan.py +0 -0
  26. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/galvo.py +0 -0
  27. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/gpio.py +0 -0
  28. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/gripper.py +0 -0
  29. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/home.py +0 -0
  30. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/i2c.py +0 -0
  31. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/laser.py +0 -0
  32. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/lcddisplay.py +0 -0
  33. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/ledmatrix.py +0 -0
  34. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/logger.py +0 -0
  35. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/message.py +0 -0
  36. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/modules.py +0 -0
  37. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/motor.py +0 -0
  38. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/motor_config.py +0 -0
  39. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/mserial.py +0 -0
  40. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/objective.py +0 -0
  41. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/pid.py +0 -0
  42. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/rotator.py +0 -0
  43. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/slm.py +0 -0
  44. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/state.py +0 -0
  45. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/temperature.py +0 -0
  46. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/uc2rest/utils.py +0 -0
  47. {uc2_rest-0.2.0.38 → uc2_rest-0.2.0.39}/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.38
3
+ Version: 0.2.0.39
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.38
3
+ Version: 0.2.0.39
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
@@ -36,6 +36,7 @@ uc2rest/motor_config.py
36
36
  uc2rest/mserial.py
37
37
  uc2rest/objective.py
38
38
  uc2rest/pid.py
39
+ uc2rest/ptz.py
39
40
  uc2rest/rotator.py
40
41
  uc2rest/slm.py
41
42
  uc2rest/state.py
@@ -23,6 +23,7 @@ from .modules import Modules
23
23
  from .digitalout import DigitalOut
24
24
  from .digitalin import DigitalIn
25
25
  from .gpio import GPIO
26
+ from .ptz import PTZ
26
27
  from .i2c import I2C
27
28
  from .rotator import Rotator
28
29
  from .logger import Logger
@@ -161,6 +162,9 @@ class UC2Client(object):
161
162
  # initialize GPIO / collision-detector slave interface
162
163
  self.gpio = GPIO(self)
163
164
 
165
+ # initialize PTZ keyboard bridge interface (CAN node-id 61)
166
+ self.ptz = PTZ(self)
167
+
164
168
  # initialize generic I2C passthrough (sensors on the GPIO slave bus)
165
169
  self.i2c = I2C(self)
166
170
 
@@ -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.38"
9
+ __version__ = "v0.2.0.39"
10
10
  __author__ = 'Benedict Diederich'
11
11
  __author_email__ = 'benedictdied@gmail.com'
12
12
  __license__ = 'GPL v3'
@@ -0,0 +1,156 @@
1
+ """PTZ keyboard interface for the UC2 CAN PTZ-bridge slave.
2
+
3
+ A low-cost CCTV PTZ joystick keyboard (Pelco-D/P over RS-485) is read by a
4
+ dedicated CAN bridge node (XIAO, default CAN node-id 61). The bridge maps the
5
+ joystick to motor moves directly on the bus, and forwards every DISCRETE key
6
+ (presets, AUX, iris) to the master, which prints an asynchronous frame:
7
+
8
+ {"ptz":{"event":1,"node":61,"type":6,"name":"iris_open","arg":0,"seq":21}}
9
+
10
+ type/name: 1 preset_call 2 preset_set 3 preset_clear
11
+ 4 aux_on 5 aux_off 6 iris_open
12
+ 7 iris_close 8 camera_onoff 9 autoscan
13
+ arg: preset number (1..255) or AUX number (1..6); 0 otherwise
14
+ seq: wraps 0..255, one per key event (the master de-dupes on it)
15
+
16
+ This mirrors the collision "gpio" event plumbing (see gpio.py): a single
17
+ serial callback on the top-level "ptz" key, an "event":1 marker to tell a
18
+ pushed key from a /ptz_get response, and a list of user callbacks. ImSwitch
19
+ registers one such callback and maps each key to a microscope function
20
+ (snap image, switch objective, toggle an LED …) — see UC2ConfigController.
21
+
22
+ Debug / config endpoints (handled locally on the bridge node):
23
+ {"task":"/ptz_act","debug":2} raw RS-485 hex dump (wiring bring-up)
24
+ {"task":"/ptz_act","debug":1} decoded frame JSON
25
+ {"task":"/ptz_act","addr":3} accept only camera address 3 (0=any)
26
+ {"task":"/ptz_act","timeout":2000} motion watchdog ms (0=off)
27
+ {"task":"/ptz_get"} parser stats + last frame + motion
28
+ """
29
+
30
+
31
+ class PTZ(object):
32
+ def __init__(self, parent):
33
+ self._parent = parent
34
+
35
+ # last known state (merged from /ptz_get responses and async events)
36
+ self.status = {}
37
+ # last discrete key event pushed by the bridge
38
+ self.last_event = {}
39
+
40
+ # register a callback for any {"ptz":{...}} frame on the serial loop
41
+ if hasattr(self._parent, "serial"):
42
+ self._parent.serial.register_callback(self._callback_ptz, pattern="ptz")
43
+
44
+ # user-registered callbacks
45
+ self._event_callbacks = [] # fired on async key events (event==1)
46
+ self._status_callbacks = [] # fired on every ptz frame
47
+
48
+ # ─────────────────────────────────────────────────────────────────
49
+ # Async event plumbing
50
+ # ─────────────────────────────────────────────────────────────────
51
+ def _callback_ptz(self, data):
52
+ """Handle any serial frame with a top-level "ptz" key.
53
+
54
+ Pushed key events carry "event":1 and fire the event callbacks;
55
+ /ptz_get responses only refresh the cached status."""
56
+ try:
57
+ ptz = data.get("ptz", None)
58
+ if not isinstance(ptz, dict):
59
+ return
60
+ self.status.update(ptz)
61
+
62
+ for cb in self._status_callbacks:
63
+ try:
64
+ cb(dict(self.status))
65
+ except Exception as e:
66
+ print(f"Error in ptz status callback: {e}")
67
+
68
+ if ptz.get("event", 0):
69
+ self.last_event = dict(ptz)
70
+ for cb in self._event_callbacks:
71
+ try:
72
+ cb(dict(ptz))
73
+ except Exception as e:
74
+ print(f"Error in ptz event callback: {e}")
75
+ except Exception as e:
76
+ print("Error in _callback_ptz: ", e)
77
+
78
+ def register_event_callback(self, callbackfct):
79
+ """Register a function called on every asynchronously pushed PTZ key
80
+ event (preset call/set/clear, AUX on/off, iris, …). The callback
81
+ receives the event dict, e.g.
82
+ {"event":1,"node":61,"type":1,"name":"preset_call","arg":1,"seq":7}.
83
+ Use ["name"] and ["arg"] to decide which microscope function to run."""
84
+ if callbackfct not in self._event_callbacks:
85
+ self._event_callbacks.append(callbackfct)
86
+
87
+ def register_status_callback(self, callbackfct):
88
+ """Register a function called on EVERY ptz frame (async key events and
89
+ /ptz_get responses alike) with the merged status dict."""
90
+ if callbackfct not in self._status_callbacks:
91
+ self._status_callbacks.append(callbackfct)
92
+
93
+ # ─────────────────────────────────────────────────────────────────
94
+ # Query / configuration
95
+ # ─────────────────────────────────────────────────────────────────
96
+ @staticmethod
97
+ def _extract_ptz(response):
98
+ """Unwrap a post_json return into the "ptz" dict.
99
+
100
+ mserial.sendMessage returns a LIST of response dicts on success, a
101
+ plain string on timeout, or an int qid — handle all shapes (same
102
+ defensive pattern as GPIO._extract_gpio)."""
103
+ candidates = response if isinstance(response, list) else [response]
104
+ for item in candidates:
105
+ if isinstance(item, dict):
106
+ ptz = item.get("ptz")
107
+ if isinstance(ptz, dict):
108
+ return ptz
109
+ return {}
110
+
111
+ def get_status(self, node=None, timeout=1):
112
+ """Poll the bridge: parser stats (bytesIn/framesOk/resyncs), the last
113
+ decoded frame and the current motion snapshot. Returns the "ptz" dict.
114
+ Note: discrete key events are PUSHED (see register_event_callback);
115
+ polling is only for diagnostics."""
116
+ path = "/ptz_get"
117
+ payload = {"task": path}
118
+ if node is not None:
119
+ payload["node"] = node
120
+ r = self._parent.post_json(path, payload, getReturn=True, timeout=timeout)
121
+ ptz = self._extract_ptz(r)
122
+ if ptz:
123
+ self.status.update(ptz)
124
+ return ptz
125
+ return dict(self.status)
126
+
127
+ def _act(self, node=None, timeout=1, **fields):
128
+ path = "/ptz_act"
129
+ payload = {"task": path}
130
+ if node is not None:
131
+ payload["node"] = node
132
+ payload.update(fields)
133
+ return self._parent.post_json(path, payload, getReturn=True, timeout=timeout)
134
+
135
+ def set_debug(self, level, node=None, timeout=1):
136
+ """Bridge serial debug verbosity: 0 quiet, 1 decoded frame JSON,
137
+ 2 + raw RS-485 hex dump (use for wiring / baud / A-B-swap bring-up)."""
138
+ return self._act(node=node, timeout=timeout, debug=int(level))
139
+
140
+ def set_address(self, addr, node=None, timeout=1):
141
+ """Only accept keyboard frames for this Pelco camera address
142
+ (0 = accept any). Handy when several bridges share one keyboard."""
143
+ return self._act(node=node, timeout=timeout, addr=int(addr))
144
+
145
+ def set_motion_timeout(self, timeout_ms, node=None, serial_timeout=1):
146
+ """Watchdog (ms): stop all axes if the keyboard goes silent while an
147
+ axis is moving (0 = off). Guards against a lost stop frame.
148
+
149
+ Built explicitly (not via _act) because the firmware field is itself
150
+ named "timeout", which would clash with the serial-call timeout kwarg."""
151
+ path = "/ptz_act"
152
+ payload = {"task": path, "timeout": int(timeout_ms)}
153
+ if node is not None:
154
+ payload["node"] = node
155
+ return self._parent.post_json(path, payload, getReturn=True,
156
+ timeout=serial_timeout)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes