better-rtplot 0.3.0__tar.gz → 0.4.0__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.
- better_rtplot-0.4.0/PKG-INFO +149 -0
- better_rtplot-0.4.0/README.md +125 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/pyproject.toml +1 -1
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/client.py +173 -9
- better_rtplot-0.3.0/PKG-INFO +0 -682
- better_rtplot-0.3.0/README.md +0 -658
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/LICENSE +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/interactive_test.py +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/saved_plots/.gitignore +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/server.py +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/server_browser.py +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/server_browser_gui.py +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/static/uPlot.iife.min.js +0 -0
- {better_rtplot-0.3.0 → better_rtplot-0.4.0}/rtplot/static/uPlot.min.css +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: better-rtplot
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary:
|
|
5
|
+
License: GPL V3.0
|
|
6
|
+
Author: jmontp
|
|
7
|
+
Author-email: jmontp@umich.edu
|
|
8
|
+
Requires-Python: >=3.9,<3.13
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Provides-Extra: browser
|
|
16
|
+
Provides-Extra: server
|
|
17
|
+
Requires-Dist: aiohttp (>=3.9.0) ; extra == "browser"
|
|
18
|
+
Requires-Dist: numpy (>=1.23.5)
|
|
19
|
+
Requires-Dist: pyqtgraph (>=0.13.0) ; extra == "server"
|
|
20
|
+
Requires-Dist: pyside6 (>6.4.0) ; extra == "server"
|
|
21
|
+
Requires-Dist: pyzmq (>=25.0.0)
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# rtplot — real-time plotting over ZMQ
|
|
25
|
+
|
|
26
|
+
**rtplot** pushes live data from a Python script to a browser plot —
|
|
27
|
+
locally or across a network — in a few lines of code. The plot page
|
|
28
|
+
also hosts interactive widgets (buttons, sliders, dials, numeric / text
|
|
29
|
+
displays) that feed values back to the sender in real time.
|
|
30
|
+
|
|
31
|
+
Typical use: a robot or data-acquisition script runs on a Raspberry Pi,
|
|
32
|
+
and you watch signals and tweak gains from a laptop on the same Wi-Fi.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
Two processes talking over ZMQ, plus a browser viewer:
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
flowchart TD
|
|
42
|
+
script["Your Python script<br/>send_array() · poll_controls()"]
|
|
43
|
+
subgraph server["rtplot-server"]
|
|
44
|
+
browser["browser tab<br/>localhost:8050"]
|
|
45
|
+
end
|
|
46
|
+
script -- "data :5555" --> server
|
|
47
|
+
server -- "controls :5556" --> script
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Sender and server don't have to be on the same machine — see the
|
|
51
|
+
[networking guide](docs/networking.md).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
**Server** — grab the prebuilt binary from the
|
|
58
|
+
[Releases page](https://github.com/jmontp/rtplot/releases):
|
|
59
|
+
|
|
60
|
+
| Platform | Asset |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Windows | `rtplot-server-<version>-windows-x64.exe` |
|
|
63
|
+
| Linux | `rtplot-server-<version>-linux-x86_64.tar.gz` |
|
|
64
|
+
| macOS (Apple Silicon) | `rtplot-server-<version>-macos-arm64.tar.gz` |
|
|
65
|
+
|
|
66
|
+
No Python needed on the viewing machine. On Windows the binary opens
|
|
67
|
+
a small Tk status window showing the listening URL.
|
|
68
|
+
|
|
69
|
+
**Client** — pip install in the env that runs your script:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install better-rtplot
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
(If you'd rather run the server from Python too, use
|
|
76
|
+
`pip install "better-rtplot[browser]"` — see the
|
|
77
|
+
[API reference](docs/api.md#install-detail).)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Your first plot
|
|
82
|
+
|
|
83
|
+
```mermaid
|
|
84
|
+
flowchart TD
|
|
85
|
+
t1["terminal 1: start the server"]
|
|
86
|
+
t2["terminal 2: run your script"]
|
|
87
|
+
br["browser: open localhost:8050"]
|
|
88
|
+
t1 --> t2 --> br
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Terminal 1 — start the server.** Run the prebuilt `rtplot-server`
|
|
92
|
+
binary you downloaded above. Open `http://localhost:8050` in a
|
|
93
|
+
browser — the page is blank until data arrives.
|
|
94
|
+
|
|
95
|
+
**Terminal 2 — run your sender script.** Save as `my_plot.py`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from rtplot import client
|
|
99
|
+
import time
|
|
100
|
+
|
|
101
|
+
client.local_plot()
|
|
102
|
+
client.initialize_plots(["my signal"])
|
|
103
|
+
|
|
104
|
+
for i in range(1000):
|
|
105
|
+
client.send_array(i * 0.01)
|
|
106
|
+
time.sleep(0.01)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python my_plot.py
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A rising line now draws itself in the browser tab.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Highlights
|
|
118
|
+
|
|
119
|
+
- **Fast.** Binary WebSocket deltas up to 1 kHz; the browser coalesces
|
|
120
|
+
samples into one repaint per `requestAnimationFrame`, so rendering
|
|
121
|
+
tracks your monitor refresh rate regardless of sample rate.
|
|
122
|
+
- **Browser-based.** aiohttp + uPlot, no desktop GUI toolkit, works
|
|
123
|
+
over SSH port forwarding.
|
|
124
|
+
- **Remote-friendly.** Sender or plot host can bind. Live Bind /
|
|
125
|
+
Connect buttons retarget without restart.
|
|
126
|
+
- **Config lives with the data.** The sender declares plot layout.
|
|
127
|
+
- **Interactive controls.** Buttons, sliders, dials, displays — polled
|
|
128
|
+
from your loop, no threads, no callbacks.
|
|
129
|
+
- **Static HTML snapshots.** `save_snapshot("out.html")` writes a
|
|
130
|
+
self-contained ~65 KB file with the current trace embedded.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Where to go next
|
|
135
|
+
|
|
136
|
+
- **[API reference](docs/api.md)** — every `rtplot.client` function,
|
|
137
|
+
the plot-layout schema, interactive controls, snapshots, browser UI,
|
|
138
|
+
and `rtplot-server` CLI flags.
|
|
139
|
+
- **[Networking guide](docs/networking.md)** — Mode A vs. Mode B,
|
|
140
|
+
viewing from a phone or second laptop, the WSL2 wrinkle, Cloudflare
|
|
141
|
+
Tunnel, Tailscale.
|
|
142
|
+
- **[Examples](examples/README.md)** — runnable scripts with embedded
|
|
143
|
+
HTML snapshots you can open offline.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
Issues and feature requests:
|
|
148
|
+
[github.com/jmontp/rtplot/issues](https://github.com/jmontp/rtplot/issues).
|
|
149
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# rtplot — real-time plotting over ZMQ
|
|
2
|
+
|
|
3
|
+
**rtplot** pushes live data from a Python script to a browser plot —
|
|
4
|
+
locally or across a network — in a few lines of code. The plot page
|
|
5
|
+
also hosts interactive widgets (buttons, sliders, dials, numeric / text
|
|
6
|
+
displays) that feed values back to the sender in real time.
|
|
7
|
+
|
|
8
|
+
Typical use: a robot or data-acquisition script runs on a Raspberry Pi,
|
|
9
|
+
and you watch signals and tweak gains from a laptop on the same Wi-Fi.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
Two processes talking over ZMQ, plus a browser viewer:
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart TD
|
|
19
|
+
script["Your Python script<br/>send_array() · poll_controls()"]
|
|
20
|
+
subgraph server["rtplot-server"]
|
|
21
|
+
browser["browser tab<br/>localhost:8050"]
|
|
22
|
+
end
|
|
23
|
+
script -- "data :5555" --> server
|
|
24
|
+
server -- "controls :5556" --> script
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Sender and server don't have to be on the same machine — see the
|
|
28
|
+
[networking guide](docs/networking.md).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
**Server** — grab the prebuilt binary from the
|
|
35
|
+
[Releases page](https://github.com/jmontp/rtplot/releases):
|
|
36
|
+
|
|
37
|
+
| Platform | Asset |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Windows | `rtplot-server-<version>-windows-x64.exe` |
|
|
40
|
+
| Linux | `rtplot-server-<version>-linux-x86_64.tar.gz` |
|
|
41
|
+
| macOS (Apple Silicon) | `rtplot-server-<version>-macos-arm64.tar.gz` |
|
|
42
|
+
|
|
43
|
+
No Python needed on the viewing machine. On Windows the binary opens
|
|
44
|
+
a small Tk status window showing the listening URL.
|
|
45
|
+
|
|
46
|
+
**Client** — pip install in the env that runs your script:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install better-rtplot
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
(If you'd rather run the server from Python too, use
|
|
53
|
+
`pip install "better-rtplot[browser]"` — see the
|
|
54
|
+
[API reference](docs/api.md#install-detail).)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Your first plot
|
|
59
|
+
|
|
60
|
+
```mermaid
|
|
61
|
+
flowchart TD
|
|
62
|
+
t1["terminal 1: start the server"]
|
|
63
|
+
t2["terminal 2: run your script"]
|
|
64
|
+
br["browser: open localhost:8050"]
|
|
65
|
+
t1 --> t2 --> br
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Terminal 1 — start the server.** Run the prebuilt `rtplot-server`
|
|
69
|
+
binary you downloaded above. Open `http://localhost:8050` in a
|
|
70
|
+
browser — the page is blank until data arrives.
|
|
71
|
+
|
|
72
|
+
**Terminal 2 — run your sender script.** Save as `my_plot.py`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from rtplot import client
|
|
76
|
+
import time
|
|
77
|
+
|
|
78
|
+
client.local_plot()
|
|
79
|
+
client.initialize_plots(["my signal"])
|
|
80
|
+
|
|
81
|
+
for i in range(1000):
|
|
82
|
+
client.send_array(i * 0.01)
|
|
83
|
+
time.sleep(0.01)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python my_plot.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A rising line now draws itself in the browser tab.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Highlights
|
|
95
|
+
|
|
96
|
+
- **Fast.** Binary WebSocket deltas up to 1 kHz; the browser coalesces
|
|
97
|
+
samples into one repaint per `requestAnimationFrame`, so rendering
|
|
98
|
+
tracks your monitor refresh rate regardless of sample rate.
|
|
99
|
+
- **Browser-based.** aiohttp + uPlot, no desktop GUI toolkit, works
|
|
100
|
+
over SSH port forwarding.
|
|
101
|
+
- **Remote-friendly.** Sender or plot host can bind. Live Bind /
|
|
102
|
+
Connect buttons retarget without restart.
|
|
103
|
+
- **Config lives with the data.** The sender declares plot layout.
|
|
104
|
+
- **Interactive controls.** Buttons, sliders, dials, displays — polled
|
|
105
|
+
from your loop, no threads, no callbacks.
|
|
106
|
+
- **Static HTML snapshots.** `save_snapshot("out.html")` writes a
|
|
107
|
+
self-contained ~65 KB file with the current trace embedded.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Where to go next
|
|
112
|
+
|
|
113
|
+
- **[API reference](docs/api.md)** — every `rtplot.client` function,
|
|
114
|
+
the plot-layout schema, interactive controls, snapshots, browser UI,
|
|
115
|
+
and `rtplot-server` CLI flags.
|
|
116
|
+
- **[Networking guide](docs/networking.md)** — Mode A vs. Mode B,
|
|
117
|
+
viewing from a phone or second laptop, the WSL2 wrinkle, Cloudflare
|
|
118
|
+
Tunnel, Tailscale.
|
|
119
|
+
- **[Examples](examples/README.md)** — runnable scripts with embedded
|
|
120
|
+
HTML snapshots you can open offline.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
Issues and feature requests:
|
|
125
|
+
[github.com/jmontp/rtplot/issues](https://github.com/jmontp/rtplot/issues).
|
|
@@ -2,6 +2,8 @@ import zmq
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import time
|
|
4
4
|
from collections import OrderedDict, namedtuple
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import List, Optional, Tuple, Union
|
|
5
7
|
|
|
6
8
|
###################
|
|
7
9
|
# ZMQ Networking #
|
|
@@ -85,19 +87,172 @@ SENDING_DISPLAY = "4"
|
|
|
85
87
|
#Lightweight result type returned by poll_controls()
|
|
86
88
|
ControlState = namedtuple("ControlState", ["values", "buttons"])
|
|
87
89
|
|
|
90
|
+
|
|
91
|
+
##########################################
|
|
92
|
+
# Typed plot / controls configuration API #
|
|
93
|
+
##########################################
|
|
94
|
+
#
|
|
95
|
+
# These dataclasses are an alternative to the dict-based configuration
|
|
96
|
+
# accepted by initialize_plots(). Both APIs coexist — the typed form
|
|
97
|
+
# serializes to the exact same on-the-wire dict, so the server is
|
|
98
|
+
# unaware of which one the caller used.
|
|
99
|
+
#
|
|
100
|
+
# client.initialize_plots([
|
|
101
|
+
# client.Plot(names=["signal"], yrange=(-6, 6), title="demo"),
|
|
102
|
+
# client.ControlsRow([client.Button("reset", "Reset")]),
|
|
103
|
+
# ])
|
|
104
|
+
#
|
|
105
|
+
# is equivalent to the classic:
|
|
106
|
+
#
|
|
107
|
+
# client.initialize_plots([
|
|
108
|
+
# {"names": ["signal"], "yrange": [-6, 6], "title": "demo"},
|
|
109
|
+
# {"controls": [{"type": "button", "id": "reset", "label": "Reset"}]},
|
|
110
|
+
# ])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _drop_none(d):
|
|
114
|
+
# Wire format treats missing keys as "use default"; None would
|
|
115
|
+
# JSON-serialize to null which the server wouldn't handle.
|
|
116
|
+
return {k: v for k, v in d.items() if v is not None}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _range_to_list(r):
|
|
120
|
+
# yrange/xrange go over the wire as JSON lists.
|
|
121
|
+
return list(r) if r is not None else None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class Plot:
|
|
126
|
+
"""A single plot with optional styling.
|
|
127
|
+
|
|
128
|
+
All fields beyond ``names`` are optional and mirror the styled-plot
|
|
129
|
+
dict keys documented in ``docs/api.md``.
|
|
130
|
+
"""
|
|
131
|
+
names: List[str]
|
|
132
|
+
colors: Optional[List[str]] = None
|
|
133
|
+
line_style: Optional[List[str]] = None
|
|
134
|
+
line_width: Optional[float] = None
|
|
135
|
+
title: Optional[str] = None
|
|
136
|
+
xlabel: Optional[str] = None
|
|
137
|
+
ylabel: Optional[str] = None
|
|
138
|
+
yrange: Optional[Tuple[float, float]] = None
|
|
139
|
+
xrange: Optional[int] = None
|
|
140
|
+
height: Optional[float] = None
|
|
141
|
+
|
|
142
|
+
def to_dict(self):
|
|
143
|
+
return _drop_none({
|
|
144
|
+
"names": list(self.names),
|
|
145
|
+
"colors": list(self.colors) if self.colors is not None else None,
|
|
146
|
+
"line_style": list(self.line_style) if self.line_style is not None else None,
|
|
147
|
+
"line_width": self.line_width,
|
|
148
|
+
"title": self.title,
|
|
149
|
+
"xlabel": self.xlabel,
|
|
150
|
+
"ylabel": self.ylabel,
|
|
151
|
+
"yrange": _range_to_list(self.yrange),
|
|
152
|
+
"xrange": self.xrange,
|
|
153
|
+
"height": self.height,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class Button:
|
|
159
|
+
id: str
|
|
160
|
+
label: str
|
|
161
|
+
height: Optional[float] = None
|
|
162
|
+
|
|
163
|
+
def to_dict(self):
|
|
164
|
+
return _drop_none({
|
|
165
|
+
"type": "button", "id": self.id, "label": self.label,
|
|
166
|
+
"height": self.height,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class Slider:
|
|
172
|
+
id: str
|
|
173
|
+
label: str
|
|
174
|
+
min: float
|
|
175
|
+
max: float
|
|
176
|
+
value: float = 0.0
|
|
177
|
+
step: Optional[float] = None
|
|
178
|
+
format: Optional[str] = None
|
|
179
|
+
height: Optional[float] = None
|
|
180
|
+
|
|
181
|
+
def to_dict(self):
|
|
182
|
+
return _drop_none({
|
|
183
|
+
"type": "slider", "id": self.id, "label": self.label,
|
|
184
|
+
"min": self.min, "max": self.max, "value": self.value,
|
|
185
|
+
"step": self.step, "format": self.format, "height": self.height,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class Dial:
|
|
191
|
+
id: str
|
|
192
|
+
label: str
|
|
193
|
+
min: float
|
|
194
|
+
max: float
|
|
195
|
+
value: float = 0.0
|
|
196
|
+
step: Optional[float] = None
|
|
197
|
+
sensitivity: Optional[float] = None
|
|
198
|
+
format: Optional[str] = None
|
|
199
|
+
height: Optional[float] = None
|
|
200
|
+
|
|
201
|
+
def to_dict(self):
|
|
202
|
+
return _drop_none({
|
|
203
|
+
"type": "dial", "id": self.id, "label": self.label,
|
|
204
|
+
"min": self.min, "max": self.max, "value": self.value,
|
|
205
|
+
"step": self.step, "sensitivity": self.sensitivity,
|
|
206
|
+
"format": self.format, "height": self.height,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class Display:
|
|
212
|
+
id: str
|
|
213
|
+
label: str
|
|
214
|
+
format: Optional[str] = None
|
|
215
|
+
height: Optional[float] = None
|
|
216
|
+
|
|
217
|
+
def to_dict(self):
|
|
218
|
+
return _drop_none({
|
|
219
|
+
"type": "display", "id": self.id, "label": self.label,
|
|
220
|
+
"format": self.format, "height": self.height,
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass
|
|
225
|
+
class Text:
|
|
226
|
+
id: str
|
|
227
|
+
label: str
|
|
228
|
+
value: str = ""
|
|
229
|
+
height: Optional[float] = None
|
|
230
|
+
|
|
231
|
+
def to_dict(self):
|
|
232
|
+
return _drop_none({
|
|
233
|
+
"type": "text", "id": self.id, "label": self.label,
|
|
234
|
+
"value": self.value, "height": self.height,
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class ControlsRow:
|
|
240
|
+
"""A row of control widgets, rendered in place of a plot."""
|
|
241
|
+
controls: List[Union[Button, Slider, Dial, Display, Text, dict]] = field(default_factory=list)
|
|
242
|
+
|
|
243
|
+
def to_dict(self):
|
|
244
|
+
return {"controls": [
|
|
245
|
+
c.to_dict() if hasattr(c, "to_dict") else c
|
|
246
|
+
for c in self.controls
|
|
247
|
+
]}
|
|
248
|
+
|
|
249
|
+
|
|
88
250
|
def local_plot():
|
|
89
251
|
"""Send data to a plot in the same computer"""
|
|
90
252
|
|
|
91
253
|
local_address = "tcp://127.0.0.1:5555"
|
|
92
254
|
configure_ip(ip = local_address)
|
|
93
255
|
|
|
94
|
-
def plot_to_neurobionics_tv():
|
|
95
|
-
"""Send data to a plot in the same computer"""
|
|
96
|
-
|
|
97
|
-
tv_computer_address = "tcp://141.212.77.23:5555"
|
|
98
|
-
configure_ip(ip = tv_computer_address)
|
|
99
|
-
|
|
100
|
-
|
|
101
256
|
def configure_port(new_port:int):
|
|
102
257
|
"""Rebind the local publisher on ``new_port`` (bind mode only).
|
|
103
258
|
|
|
@@ -277,6 +432,11 @@ def initialize_plots(plot_descriptions=1):
|
|
|
277
432
|
plot_desc_dict = OrderedDict()
|
|
278
433
|
plot_desc_dict["plot0"] = plot_descriptions
|
|
279
434
|
|
|
435
|
+
#Process typed inputs (Plot / ControlsRow) passed alone
|
|
436
|
+
elif hasattr(plot_descriptions, "to_dict"):
|
|
437
|
+
plot_desc_dict = OrderedDict()
|
|
438
|
+
plot_desc_dict["plot0"] = plot_descriptions.to_dict()
|
|
439
|
+
|
|
280
440
|
#Process lists of things
|
|
281
441
|
elif isinstance(plot_descriptions, list):
|
|
282
442
|
|
|
@@ -291,10 +451,14 @@ def initialize_plots(plot_descriptions=1):
|
|
|
291
451
|
for i,plot_desc in enumerate(plot_descriptions):
|
|
292
452
|
plot_desc_dict["plot{}".format(i)] = {"names":plot_desc}
|
|
293
453
|
|
|
294
|
-
#Process list of
|
|
295
|
-
|
|
454
|
+
#Process list of dicts or typed objects (Plot / ControlsRow),
|
|
455
|
+
# including mixed lists — anything exposing .to_dict() is normalized
|
|
456
|
+
# to the wire dict form.
|
|
457
|
+
elif isinstance(plot_descriptions[0],dict) or hasattr(plot_descriptions[0], "to_dict"):
|
|
296
458
|
plot_desc_dict = OrderedDict()
|
|
297
459
|
for i,plot_desc in enumerate(plot_descriptions):
|
|
460
|
+
if hasattr(plot_desc, "to_dict"):
|
|
461
|
+
plot_desc = plot_desc.to_dict()
|
|
298
462
|
plot_desc_dict["plot{}".format(i)] = plot_desc
|
|
299
463
|
|
|
300
464
|
#Throw error
|