nwaves-leader 0.1.1__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.
- nwaves_leader-0.1.1/PKG-INFO +41 -0
- nwaves_leader-0.1.1/README.md +28 -0
- nwaves_leader-0.1.1/nwaves_leader/__init__.py +2 -0
- nwaves_leader-0.1.1/nwaves_leader/__main__.py +4 -0
- nwaves_leader-0.1.1/nwaves_leader/bridge.py +267 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/PKG-INFO +41 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/SOURCES.txt +12 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/dependency_links.txt +1 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/entry_points.txt +2 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/requires.txt +1 -0
- nwaves_leader-0.1.1/nwaves_leader.egg-info/top_level.txt +1 -0
- nwaves_leader-0.1.1/pyproject.toml +19 -0
- nwaves_leader-0.1.1/setup.cfg +4 -0
- nwaves_leader-0.1.1/setup.py +17 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nwaves-leader
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: NWaves leader bridge: drive the NWaves SO-101 simulation with the leader arm plugged into your computer
|
|
5
|
+
Author: NWaves
|
|
6
|
+
Author-email: NWaves <support@nwaves.ai>
|
|
7
|
+
License: Proprietary
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: pyserial>=3.5
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
|
|
14
|
+
# NWaves leader bridge
|
|
15
|
+
|
|
16
|
+
Drives the NWaves SO-101 simulation with the leader arm plugged into your own computer,
|
|
17
|
+
for browsers that cannot open USB devices themselves (Firefox, Safari). Chrome and Edge
|
|
18
|
+
connect over USB directly from the session page and do not need it.
|
|
19
|
+
|
|
20
|
+
Install and run (the session page shows the same one-liners):
|
|
21
|
+
|
|
22
|
+
macOS / Linux: curl -fsSL https://<academy host>/nwaves-leader.sh | sh
|
|
23
|
+
Windows: irm https://<academy host>/nwaves-leader.ps1 | iex
|
|
24
|
+
any: python3 -m pip install nwaves-leader then nwaves-leader
|
|
25
|
+
|
|
26
|
+
Then press "Connect through the NWaves leader bridge" in the session page.
|
|
27
|
+
|
|
28
|
+
The bridge listens on 127.0.0.1:8765 only, accepts connections from nwaves.ai pages only,
|
|
29
|
+
and sends nothing but the six servo positions to the page in your browser. The page does
|
|
30
|
+
the calibration and forwards the positions to your simulation.
|
|
31
|
+
|
|
32
|
+
What the academy hosts and how to rebuild it (from the repo root): `sh tools/nwaves-leader/build.sh`
|
|
33
|
+
|
|
34
|
+
- `nwaves-leader.pyz`: the self-contained app the installers download (pyserial is vendored, BSD licence
|
|
35
|
+
kept inside), runs with any Python 3.8+ as `python3 nwaves-leader.pyz`
|
|
36
|
+
- `nwaves_leader-<version>-py3-none-any.whl`: for `pip install` and for PyPI
|
|
37
|
+
- `nwaves-leader.py`: the single-file version (needs pyserial)
|
|
38
|
+
|
|
39
|
+
Publishing to PyPI (name `nwaves-leader` was free on 2026-10-17): create the NWaves account on pypi.org with
|
|
40
|
+
2FA, make an API token, then `python3 -m pip install build twine && python3 -m build && twine upload dist/*`
|
|
41
|
+
with `TWINE_USERNAME=__token__ TWINE_PASSWORD=<token>`. Bump `version` in pyproject.toml and setup.py first.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# NWaves leader bridge
|
|
2
|
+
|
|
3
|
+
Drives the NWaves SO-101 simulation with the leader arm plugged into your own computer,
|
|
4
|
+
for browsers that cannot open USB devices themselves (Firefox, Safari). Chrome and Edge
|
|
5
|
+
connect over USB directly from the session page and do not need it.
|
|
6
|
+
|
|
7
|
+
Install and run (the session page shows the same one-liners):
|
|
8
|
+
|
|
9
|
+
macOS / Linux: curl -fsSL https://<academy host>/nwaves-leader.sh | sh
|
|
10
|
+
Windows: irm https://<academy host>/nwaves-leader.ps1 | iex
|
|
11
|
+
any: python3 -m pip install nwaves-leader then nwaves-leader
|
|
12
|
+
|
|
13
|
+
Then press "Connect through the NWaves leader bridge" in the session page.
|
|
14
|
+
|
|
15
|
+
The bridge listens on 127.0.0.1:8765 only, accepts connections from nwaves.ai pages only,
|
|
16
|
+
and sends nothing but the six servo positions to the page in your browser. The page does
|
|
17
|
+
the calibration and forwards the positions to your simulation.
|
|
18
|
+
|
|
19
|
+
What the academy hosts and how to rebuild it (from the repo root): `sh tools/nwaves-leader/build.sh`
|
|
20
|
+
|
|
21
|
+
- `nwaves-leader.pyz`: the self-contained app the installers download (pyserial is vendored, BSD licence
|
|
22
|
+
kept inside), runs with any Python 3.8+ as `python3 nwaves-leader.pyz`
|
|
23
|
+
- `nwaves_leader-<version>-py3-none-any.whl`: for `pip install` and for PyPI
|
|
24
|
+
- `nwaves-leader.py`: the single-file version (needs pyserial)
|
|
25
|
+
|
|
26
|
+
Publishing to PyPI (name `nwaves-leader` was free on 2026-10-17): create the NWaves account on pypi.org with
|
|
27
|
+
2FA, make an API token, then `python3 -m pip install build twine && python3 -m build && twine upload dist/*`
|
|
28
|
+
with `TWINE_USERNAME=__token__ TWINE_PASSWORD=<token>`. Bump `version` in pyproject.toml and setup.py first.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""NWaves leader bridge.
|
|
3
|
+
|
|
4
|
+
Lets a browser without USB access (Firefox, Safari, ...) drive the NWaves SO-101
|
|
5
|
+
simulation with the leader arm plugged into this computer. It reads the arm's six
|
|
6
|
+
servos over its USB adapter and hands the positions to the NWaves session page over
|
|
7
|
+
localhost; the page does the calibration and talks to your simulation.
|
|
8
|
+
|
|
9
|
+
python3 nwaves-leader.py # finds the arm's port by itself
|
|
10
|
+
python3 nwaves-leader.py --port COM5 (or /dev/cu.usbserial-XXXX, /dev/ttyACM0)
|
|
11
|
+
|
|
12
|
+
Needs Python 3.8+ and pyserial: python3 -m pip install pyserial
|
|
13
|
+
Then press "Connect through the NWaves leader bridge" in the session page.
|
|
14
|
+
"""
|
|
15
|
+
import argparse
|
|
16
|
+
import base64
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import socket
|
|
20
|
+
import struct
|
|
21
|
+
import sys
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import serial
|
|
27
|
+
from serial.tools import list_ports
|
|
28
|
+
except ImportError:
|
|
29
|
+
print("pyserial is missing. Install it with: python3 -m pip install pyserial")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
VERSION = "0.1.1"
|
|
33
|
+
IDS = [1, 2, 3, 4, 5, 6]
|
|
34
|
+
BAUD = 1000000
|
|
35
|
+
PRESENT_POSITION = 56
|
|
36
|
+
RATE_HZ = 30
|
|
37
|
+
HOST, PORT = "127.0.0.1", 8765
|
|
38
|
+
ALLOWED_ORIGIN_SUFFIXES = (".nwaves.ai", "//localhost", "//127.0.0.1")
|
|
39
|
+
KNOWN_ADAPTERS = {0x1A86, 0x10C4, 0x0403, 0x2341, 0x1D50} # CH34x, CP210x, FTDI, Arduino, generic
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def checksum(data):
|
|
43
|
+
return (~sum(data)) & 0xFF
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def sync_read_packet(ids, addr, length):
|
|
47
|
+
params = [addr, length] + list(ids)
|
|
48
|
+
body = [0xFE, len(params) + 2, 0x82] + params
|
|
49
|
+
return bytes([0xFF, 0xFF] + body + [checksum(body)])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Arm:
|
|
53
|
+
def __init__(self, port):
|
|
54
|
+
self.ser = serial.Serial(port, BAUD, timeout=0.02)
|
|
55
|
+
self.pending = b""
|
|
56
|
+
|
|
57
|
+
def close(self):
|
|
58
|
+
try:
|
|
59
|
+
self.ser.close()
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def read_positions(self, timeout=0.04):
|
|
64
|
+
self.ser.reset_input_buffer()
|
|
65
|
+
self.ser.write(sync_read_packet(IDS, PRESENT_POSITION, 2))
|
|
66
|
+
want = set(IDS)
|
|
67
|
+
out = {}
|
|
68
|
+
deadline = time.monotonic() + timeout
|
|
69
|
+
buf = b""
|
|
70
|
+
while want and time.monotonic() < deadline:
|
|
71
|
+
chunk = self.ser.read(64)
|
|
72
|
+
if not chunk:
|
|
73
|
+
continue
|
|
74
|
+
buf += chunk
|
|
75
|
+
i = 0
|
|
76
|
+
while i + 5 < len(buf):
|
|
77
|
+
if buf[i] != 0xFF or buf[i + 1] != 0xFF:
|
|
78
|
+
i += 1
|
|
79
|
+
continue
|
|
80
|
+
sid, length = buf[i + 2], buf[i + 3]
|
|
81
|
+
end = i + 4 + length
|
|
82
|
+
if end > len(buf):
|
|
83
|
+
break
|
|
84
|
+
body = buf[i + 2:end - 1]
|
|
85
|
+
if checksum(body) == buf[end - 1]:
|
|
86
|
+
params = buf[i + 5:end - 1]
|
|
87
|
+
if sid in want and len(params) >= 2:
|
|
88
|
+
out[sid] = params[0] | (params[1] << 8)
|
|
89
|
+
want.discard(sid)
|
|
90
|
+
i = end
|
|
91
|
+
else:
|
|
92
|
+
i += 2
|
|
93
|
+
buf = buf[i:]
|
|
94
|
+
return [out.get(i) for i in IDS]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def find_port(explicit=None):
|
|
98
|
+
ports = list(list_ports.comports())
|
|
99
|
+
if explicit:
|
|
100
|
+
return explicit
|
|
101
|
+
known = [p for p in ports if (p.vid or 0) in KNOWN_ADAPTERS]
|
|
102
|
+
candidates = known or [p for p in ports if any(k in (p.device or "") for k in ("usbserial", "usbmodem", "ttyUSB", "ttyACM", "COM"))]
|
|
103
|
+
for p in candidates:
|
|
104
|
+
try:
|
|
105
|
+
arm = Arm(p.device)
|
|
106
|
+
if any(v is not None for v in arm.read_positions(0.2)):
|
|
107
|
+
arm.close()
|
|
108
|
+
return p.device
|
|
109
|
+
arm.close()
|
|
110
|
+
except Exception:
|
|
111
|
+
continue
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------------ a minimal WebSocket server
|
|
116
|
+
def ws_accept(key):
|
|
117
|
+
return base64.b64encode(hashlib.sha1((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode()).digest()).decode()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def ws_send_text(conn, text):
|
|
121
|
+
data = text.encode()
|
|
122
|
+
header = bytes([0x81])
|
|
123
|
+
n = len(data)
|
|
124
|
+
if n < 126:
|
|
125
|
+
header += bytes([n])
|
|
126
|
+
elif n < 65536:
|
|
127
|
+
header += bytes([126]) + struct.pack(">H", n)
|
|
128
|
+
else:
|
|
129
|
+
header += bytes([127]) + struct.pack(">Q", n)
|
|
130
|
+
conn.sendall(header + data)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def serve(state):
|
|
134
|
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
135
|
+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
136
|
+
srv.bind((HOST, PORT))
|
|
137
|
+
srv.listen(2)
|
|
138
|
+
print(f"[nwaves-leader] ready: press 'Connect through the NWaves leader bridge' in the session page")
|
|
139
|
+
while True:
|
|
140
|
+
conn, _ = srv.accept()
|
|
141
|
+
threading.Thread(target=handle_client, args=(conn, state), daemon=True).start()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def handle_client(conn, state):
|
|
145
|
+
try:
|
|
146
|
+
conn.settimeout(5)
|
|
147
|
+
req = b""
|
|
148
|
+
while b"\r\n\r\n" not in req:
|
|
149
|
+
chunk = conn.recv(4096)
|
|
150
|
+
if not chunk:
|
|
151
|
+
return
|
|
152
|
+
req += chunk
|
|
153
|
+
head = req.decode(errors="replace")
|
|
154
|
+
headers = {}
|
|
155
|
+
for line in head.split("\r\n")[1:]:
|
|
156
|
+
if ":" in line:
|
|
157
|
+
k, v = line.split(":", 1)
|
|
158
|
+
headers[k.strip().lower()] = v.strip()
|
|
159
|
+
origin = headers.get("origin", "")
|
|
160
|
+
if origin and not any(s in origin for s in ALLOWED_ORIGIN_SUFFIXES):
|
|
161
|
+
conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n")
|
|
162
|
+
return
|
|
163
|
+
key = headers.get("sec-websocket-key")
|
|
164
|
+
if not key:
|
|
165
|
+
conn.sendall(b"HTTP/1.1 400 Bad Request\r\n\r\nNWaves leader bridge: WebSocket clients only")
|
|
166
|
+
return
|
|
167
|
+
conn.sendall(("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
|
168
|
+
f"Sec-WebSocket-Accept: {ws_accept(key)}\r\n\r\n").encode())
|
|
169
|
+
conn.settimeout(None)
|
|
170
|
+
print("[nwaves-leader] session page connected")
|
|
171
|
+
ws_send_text(conn, json.dumps({"hello": "nwaves-leader", "port": state["port"], "rate_hz": RATE_HZ}))
|
|
172
|
+
# Drain anything the page sends (pings, close) in a side thread.
|
|
173
|
+
def drain():
|
|
174
|
+
try:
|
|
175
|
+
while conn.recv(4096):
|
|
176
|
+
pass
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
state["closed"].add(conn)
|
|
180
|
+
threading.Thread(target=drain, daemon=True).start()
|
|
181
|
+
last_sent = None
|
|
182
|
+
while conn not in state["closed"]:
|
|
183
|
+
raw = state["latest"]
|
|
184
|
+
if raw is not None and raw is not last_sent:
|
|
185
|
+
ws_send_text(conn, json.dumps({"raw": raw}))
|
|
186
|
+
last_sent = raw
|
|
187
|
+
if state["error"]:
|
|
188
|
+
ws_send_text(conn, json.dumps({"error": state["error"]}))
|
|
189
|
+
break
|
|
190
|
+
time.sleep(1.0 / RATE_HZ)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
finally:
|
|
194
|
+
try:
|
|
195
|
+
conn.close()
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
print("[nwaves-leader] session page disconnected")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def list_ports_cmd():
|
|
202
|
+
ports = list(list_ports.comports())
|
|
203
|
+
if not ports:
|
|
204
|
+
print("[nwaves-leader] no serial ports found; plug the leader arm's USB cable in")
|
|
205
|
+
return 1
|
|
206
|
+
print("[nwaves-leader] serial ports on this computer:")
|
|
207
|
+
for p in ports:
|
|
208
|
+
tag = " (likely the arm's USB adapter)" if (p.vid or 0) in KNOWN_ADAPTERS else ""
|
|
209
|
+
print(f" {p.device} {p.description or ''}{tag}")
|
|
210
|
+
return 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main():
|
|
214
|
+
ap = argparse.ArgumentParser(prog="nwaves-leader",
|
|
215
|
+
description="NWaves leader bridge: your SO-101 leader arm -> the NWaves session page",
|
|
216
|
+
epilog="Commands: start (default), ports, version.")
|
|
217
|
+
ap.add_argument("command", nargs="?", default="start", choices=["start", "ports", "version"],
|
|
218
|
+
help="start the bridge (default), list serial ports, or print the version")
|
|
219
|
+
ap.add_argument("--port", help="serial port of the leader arm (default: found automatically)")
|
|
220
|
+
ap.add_argument("--version", action="version", version=f"nwaves-leader {VERSION}")
|
|
221
|
+
args = ap.parse_args()
|
|
222
|
+
if args.command == "version":
|
|
223
|
+
print(f"nwaves-leader {VERSION}")
|
|
224
|
+
return
|
|
225
|
+
if args.command == "ports":
|
|
226
|
+
sys.exit(list_ports_cmd())
|
|
227
|
+
print(f"[nwaves-leader] NWaves leader bridge {VERSION} (Ctrl-C stops it; 'nwaves-leader ports' lists the ports)")
|
|
228
|
+
port = find_port(args.port)
|
|
229
|
+
if not port:
|
|
230
|
+
names = [p.device for p in list_ports.comports()]
|
|
231
|
+
print("[nwaves-leader] no leader arm found." + (f" Ports seen: {', '.join(names)}" if names else " No serial ports at all:")
|
|
232
|
+
+ "\n - plug the arm's USB cable in and make sure the arm is powered\n"
|
|
233
|
+
" - on Windows and macOS the USB adapter may need its driver installed\n"
|
|
234
|
+
" - or pass the port yourself: python3 nwaves-leader.py --port <port>")
|
|
235
|
+
sys.exit(2)
|
|
236
|
+
print(f"[nwaves-leader] leader arm on {port}")
|
|
237
|
+
arm = Arm(port)
|
|
238
|
+
state = {"latest": None, "error": None, "port": port, "closed": set()}
|
|
239
|
+
threading.Thread(target=serve, args=(state,), daemon=True).start()
|
|
240
|
+
misses = 0
|
|
241
|
+
try:
|
|
242
|
+
while True:
|
|
243
|
+
t = time.monotonic()
|
|
244
|
+
try:
|
|
245
|
+
raw = arm.read_positions()
|
|
246
|
+
except Exception as e:
|
|
247
|
+
state["error"] = f"leader arm unplugged ({e})"
|
|
248
|
+
print(f"[nwaves-leader] {state['error']}")
|
|
249
|
+
break
|
|
250
|
+
if any(v is None for v in raw):
|
|
251
|
+
misses += 1
|
|
252
|
+
if misses % 60 == 1:
|
|
253
|
+
silent = [i for i, v in zip(IDS, raw) if v is None]
|
|
254
|
+
print(f"[nwaves-leader] no answer from servo {silent}; check the cable and power")
|
|
255
|
+
else:
|
|
256
|
+
misses = 0
|
|
257
|
+
state["latest"] = raw
|
|
258
|
+
time.sleep(max(0.0, 1.0 / RATE_HZ - (time.monotonic() - t)))
|
|
259
|
+
except KeyboardInterrupt:
|
|
260
|
+
pass
|
|
261
|
+
finally:
|
|
262
|
+
arm.close()
|
|
263
|
+
print("[nwaves-leader] stopped")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
main()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nwaves-leader
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: NWaves leader bridge: drive the NWaves SO-101 simulation with the leader arm plugged into your computer
|
|
5
|
+
Author: NWaves
|
|
6
|
+
Author-email: NWaves <support@nwaves.ai>
|
|
7
|
+
License: Proprietary
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: pyserial>=3.5
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
|
|
14
|
+
# NWaves leader bridge
|
|
15
|
+
|
|
16
|
+
Drives the NWaves SO-101 simulation with the leader arm plugged into your own computer,
|
|
17
|
+
for browsers that cannot open USB devices themselves (Firefox, Safari). Chrome and Edge
|
|
18
|
+
connect over USB directly from the session page and do not need it.
|
|
19
|
+
|
|
20
|
+
Install and run (the session page shows the same one-liners):
|
|
21
|
+
|
|
22
|
+
macOS / Linux: curl -fsSL https://<academy host>/nwaves-leader.sh | sh
|
|
23
|
+
Windows: irm https://<academy host>/nwaves-leader.ps1 | iex
|
|
24
|
+
any: python3 -m pip install nwaves-leader then nwaves-leader
|
|
25
|
+
|
|
26
|
+
Then press "Connect through the NWaves leader bridge" in the session page.
|
|
27
|
+
|
|
28
|
+
The bridge listens on 127.0.0.1:8765 only, accepts connections from nwaves.ai pages only,
|
|
29
|
+
and sends nothing but the six servo positions to the page in your browser. The page does
|
|
30
|
+
the calibration and forwards the positions to your simulation.
|
|
31
|
+
|
|
32
|
+
What the academy hosts and how to rebuild it (from the repo root): `sh tools/nwaves-leader/build.sh`
|
|
33
|
+
|
|
34
|
+
- `nwaves-leader.pyz`: the self-contained app the installers download (pyserial is vendored, BSD licence
|
|
35
|
+
kept inside), runs with any Python 3.8+ as `python3 nwaves-leader.pyz`
|
|
36
|
+
- `nwaves_leader-<version>-py3-none-any.whl`: for `pip install` and for PyPI
|
|
37
|
+
- `nwaves-leader.py`: the single-file version (needs pyserial)
|
|
38
|
+
|
|
39
|
+
Publishing to PyPI (name `nwaves-leader` was free on 2026-10-17): create the NWaves account on pypi.org with
|
|
40
|
+
2FA, make an API token, then `python3 -m pip install build twine && python3 -m build && twine upload dist/*`
|
|
41
|
+
with `TWINE_USERNAME=__token__ TWINE_PASSWORD=<token>`. Bump `version` in pyproject.toml and setup.py first.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
nwaves_leader/__init__.py
|
|
5
|
+
nwaves_leader/__main__.py
|
|
6
|
+
nwaves_leader/bridge.py
|
|
7
|
+
nwaves_leader.egg-info/PKG-INFO
|
|
8
|
+
nwaves_leader.egg-info/SOURCES.txt
|
|
9
|
+
nwaves_leader.egg-info/dependency_links.txt
|
|
10
|
+
nwaves_leader.egg-info/entry_points.txt
|
|
11
|
+
nwaves_leader.egg-info/requires.txt
|
|
12
|
+
nwaves_leader.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyserial>=3.5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nwaves_leader
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nwaves-leader"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "NWaves leader bridge: drive the NWaves SO-101 simulation with the leader arm plugged into your computer"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "Proprietary"}
|
|
12
|
+
authors = [{name = "NWaves", email = "support@nwaves.ai"}]
|
|
13
|
+
dependencies = ["pyserial>=3.5"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
nwaves-leader = "nwaves_leader.bridge:main"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
packages = ["nwaves_leader"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Metadata lives in pyproject.toml; this file lets old setuptools (without PEP 621
|
|
2
|
+
# support) build the same wheel.
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name="nwaves-leader",
|
|
7
|
+
version="0.1.1",
|
|
8
|
+
description="NWaves leader bridge: drive the NWaves SO-101 simulation with the leader arm plugged into your computer",
|
|
9
|
+
long_description=open("README.md").read(),
|
|
10
|
+
long_description_content_type="text/markdown",
|
|
11
|
+
author="NWaves",
|
|
12
|
+
author_email="support@nwaves.ai",
|
|
13
|
+
python_requires=">=3.8",
|
|
14
|
+
packages=["nwaves_leader"],
|
|
15
|
+
install_requires=["pyserial>=3.5"],
|
|
16
|
+
entry_points={"console_scripts": ["nwaves-leader=nwaves_leader.bridge:main"]},
|
|
17
|
+
)
|