enode-host 0.1.0__tar.gz → 0.1.2__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.
- {enode_host-0.1.0 → enode_host-0.1.2}/PKG-INFO +7 -1
- {enode_host-0.1.0 → enode_host-0.1.2}/README.md +6 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/pyproject.toml +1 -1
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/async_socket.py +42 -20
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/cli.py +132 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/constants.py +1 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/framed_mesh.py +9 -2
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/gui_framed.py +11 -2
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/model.py +161 -9
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/protocol.py +11 -1
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/view.py +471 -99
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/PKG-INFO +7 -1
- {enode_host-0.1.0 → enode_host-0.1.2}/setup.cfg +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/__init__.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/c-wing3.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/c.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/esp_mesh.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/gui_comps.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/gui_wx_bk.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/load_file.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/mesh.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/mesh_bk.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/s.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/sandbox.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/shm.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/shmtools.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/smarts.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/test_wxpython_choice.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/view-wing3.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/wx_example.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/backup/wx_test01.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/config.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/psd_recursive.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/queues.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/resampling.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/shm_sigproc.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/storage.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/timestamping.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host/types.py +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/SOURCES.txt +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/dependency_links.txt +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/entry_points.txt +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/requires.txt +0 -0
- {enode_host-0.1.0 → enode_host-0.1.2}/src/enode_host.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: enode-host
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Host-side tools that interact with the ESP-IDF firmware.
|
|
5
5
|
Author: eNode team
|
|
6
6
|
License: MIT
|
|
@@ -35,6 +35,12 @@ enode-host gui --port 3333
|
|
|
35
35
|
- Keep device/transport details in small modules so they can be shared by
|
|
36
36
|
scripts and tests.
|
|
37
37
|
- The GUI uses wxPython; install it if you plan to run `enode-host gui`.
|
|
38
|
+
- The GUI needs an active X11/Wayland display. On headless hosts, use
|
|
39
|
+
`xvfb-run enode-host gui` or run `enode-host server` instead.
|
|
40
|
+
- The GUI sets `WEBKIT_DISABLE_DMABUF_RENDERER=1` by default on Linux to avoid
|
|
41
|
+
WebView/EGL crashes. You can override it by exporting a different value.
|
|
42
|
+
- If the GUI still aborts with WebView/EGL errors, you can disable the map panel
|
|
43
|
+
with `ENODE_DISABLE_WEBVIEW=1 enode-host gui`.
|
|
38
44
|
|
|
39
45
|
## Protocol (TCP, length-prefixed)
|
|
40
46
|
|
|
@@ -23,6 +23,12 @@ enode-host gui --port 3333
|
|
|
23
23
|
- Keep device/transport details in small modules so they can be shared by
|
|
24
24
|
scripts and tests.
|
|
25
25
|
- The GUI uses wxPython; install it if you plan to run `enode-host gui`.
|
|
26
|
+
- The GUI needs an active X11/Wayland display. On headless hosts, use
|
|
27
|
+
`xvfb-run enode-host gui` or run `enode-host server` instead.
|
|
28
|
+
- The GUI sets `WEBKIT_DISABLE_DMABUF_RENDERER=1` by default on Linux to avoid
|
|
29
|
+
WebView/EGL crashes. You can override it by exporting a different value.
|
|
30
|
+
- If the GUI still aborts with WebView/EGL errors, you can disable the map panel
|
|
31
|
+
with `ENODE_DISABLE_WEBVIEW=1 enode-host gui`.
|
|
26
32
|
|
|
27
33
|
## Protocol (TCP, length-prefixed)
|
|
28
34
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import struct
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import Dict, Optional
|
|
@@ -14,6 +15,8 @@ from .protocol import (
|
|
|
14
15
|
parse_status,
|
|
15
16
|
)
|
|
16
17
|
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
@dataclass
|
|
19
22
|
class SocketResponse:
|
|
@@ -62,51 +65,70 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit
|
|
|
62
65
|
peer = writer.get_extra_info("peername")
|
|
63
66
|
peer_id = f"{peer[0]}:{peer[1]}" if peer else "unknown"
|
|
64
67
|
clients[peer_id] = writer
|
|
65
|
-
|
|
68
|
+
logger.info("[server] connected %s (clients=%d)", peer_id, len(clients))
|
|
66
69
|
|
|
67
70
|
try:
|
|
68
71
|
while True:
|
|
69
72
|
msg_type, payload = await read_frame(reader)
|
|
70
73
|
if msg_type == MsgType.STATUS:
|
|
71
74
|
status = parse_status(payload)
|
|
72
|
-
|
|
73
|
-
"[status] "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
logger.info(
|
|
76
|
+
"[status] node=%s:%s level=%s parent=%s self=%s rssi=%s",
|
|
77
|
+
status.node_type,
|
|
78
|
+
status.node_number,
|
|
79
|
+
status.level,
|
|
80
|
+
format_mac(status.parent_mac),
|
|
81
|
+
format_mac(status.self_mac),
|
|
82
|
+
status.rssi,
|
|
79
83
|
)
|
|
80
84
|
writer.write(build_ack(MsgType.STATUS, 0, "ok"))
|
|
81
85
|
await writer.drain()
|
|
82
86
|
elif msg_type == MsgType.DATA:
|
|
83
87
|
batch = parse_acc_batch(payload)
|
|
84
|
-
|
|
88
|
+
logger.info(
|
|
89
|
+
"[data] node=%s:%s samples=%d",
|
|
90
|
+
batch.node_type,
|
|
91
|
+
batch.node_number,
|
|
92
|
+
len(batch.samples),
|
|
93
|
+
)
|
|
85
94
|
writer.write(build_ack(MsgType.DATA, 0, "ok"))
|
|
86
95
|
await writer.drain()
|
|
87
96
|
elif msg_type == MsgType.PPS:
|
|
88
97
|
pps = parse_pps(payload)
|
|
89
|
-
|
|
98
|
+
logger.info(
|
|
99
|
+
"[pps] node=%s:%s cc=%s epoch=%s",
|
|
100
|
+
pps.node_type,
|
|
101
|
+
pps.node_number,
|
|
102
|
+
pps.cc,
|
|
103
|
+
pps.epoch,
|
|
104
|
+
)
|
|
90
105
|
writer.write(build_ack(MsgType.PPS, 0, "ok"))
|
|
91
106
|
await writer.drain()
|
|
92
107
|
elif msg_type == MsgType.SD_STREAM:
|
|
93
108
|
chunk = parse_sd_chunk(payload)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
logger.info(
|
|
110
|
+
"[sd] node=%s:%s file_time=%s offset=%s size=%d",
|
|
111
|
+
chunk.node_type,
|
|
112
|
+
chunk.node_number,
|
|
113
|
+
chunk.file_time,
|
|
114
|
+
chunk.offset,
|
|
115
|
+
len(chunk.data),
|
|
97
116
|
)
|
|
98
117
|
writer.write(build_ack(MsgType.SD_STREAM, 0, "ok"))
|
|
99
118
|
await writer.drain()
|
|
100
119
|
elif msg_type == MsgType.SD_DONE:
|
|
101
120
|
done = parse_sd_done(payload)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
logger.info(
|
|
122
|
+
"[sd] node=%s:%s file_time=%s status=%s",
|
|
123
|
+
done.node_type,
|
|
124
|
+
done.node_number,
|
|
125
|
+
done.file_time,
|
|
126
|
+
done.status,
|
|
105
127
|
)
|
|
106
128
|
writer.write(build_ack(MsgType.SD_DONE, 0, "ok"))
|
|
107
129
|
await writer.drain()
|
|
108
130
|
else:
|
|
109
|
-
|
|
131
|
+
logger.info("[server] msg_type=0x%02X len=%d", msg_type, len(payload))
|
|
110
132
|
writer.write(build_ack(msg_type, 0, "ok"))
|
|
111
133
|
await writer.drain()
|
|
112
134
|
except asyncio.IncompleteReadError:
|
|
@@ -115,7 +137,7 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit
|
|
|
115
137
|
clients.pop(peer_id, None)
|
|
116
138
|
writer.close()
|
|
117
139
|
await writer.wait_closed()
|
|
118
|
-
|
|
140
|
+
logger.info("[server] disconnected %s (clients=%d)", peer_id, len(clients))
|
|
119
141
|
|
|
120
142
|
|
|
121
143
|
async def broadcast_messages(
|
|
@@ -125,7 +147,7 @@ async def broadcast_messages(
|
|
|
125
147
|
while True:
|
|
126
148
|
payload = await queue.get()
|
|
127
149
|
if not clients:
|
|
128
|
-
|
|
150
|
+
logger.info("[server] no clients to broadcast to")
|
|
129
151
|
continue
|
|
130
152
|
for peer_id, writer in list(clients.items()):
|
|
131
153
|
try:
|
|
@@ -133,7 +155,7 @@ async def broadcast_messages(
|
|
|
133
155
|
await writer.drain()
|
|
134
156
|
except ConnectionError:
|
|
135
157
|
clients.pop(peer_id, None)
|
|
136
|
-
|
|
158
|
+
logger.info("[server] broadcast to %d clients", len(clients))
|
|
137
159
|
|
|
138
160
|
|
|
139
161
|
async def run_server(
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import asyncio
|
|
3
3
|
import contextlib
|
|
4
|
+
import datetime as dt
|
|
5
|
+
import configparser
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
4
10
|
from typing import List, Optional
|
|
5
11
|
|
|
6
12
|
from .async_socket import run_client, run_server
|
|
@@ -13,6 +19,7 @@ from .protocol import (
|
|
|
13
19
|
build_sd_stream_stop,
|
|
14
20
|
build_sd_clear,
|
|
15
21
|
build_set_mode,
|
|
22
|
+
build_shutdown,
|
|
16
23
|
build_start_daq,
|
|
17
24
|
build_stop_daq,
|
|
18
25
|
)
|
|
@@ -61,9 +68,129 @@ def build_command_from_tokens(tokens: List[str]) -> Optional[bytes]:
|
|
|
61
68
|
return build_sd_stream_stop()
|
|
62
69
|
if cmd == "sd_clear":
|
|
63
70
|
return build_sd_clear()
|
|
71
|
+
if cmd == "shutdown":
|
|
72
|
+
return build_shutdown()
|
|
64
73
|
raise ValueError(f"unknown command: {cmd}")
|
|
65
74
|
|
|
66
75
|
|
|
76
|
+
def _is_truthy(value: Optional[str]) -> bool:
|
|
77
|
+
if not value:
|
|
78
|
+
return False
|
|
79
|
+
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _setup_logging() -> None:
|
|
83
|
+
config = configparser.ConfigParser()
|
|
84
|
+
log_date = dt.datetime.now().strftime("%Y-%m%d")
|
|
85
|
+
log_name = f"enode-host-{log_date}.log"
|
|
86
|
+
repo_root = Path(__file__).resolve().parents[3]
|
|
87
|
+
logs_dir = repo_root / "logs"
|
|
88
|
+
try:
|
|
89
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
except OSError as exc:
|
|
91
|
+
print(f"[log] failed to create logs dir: {logs_dir} ({exc})", file=sys.stderr)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
log_path = logs_dir / log_name
|
|
95
|
+
log_format = "%(asctime)s %(levelname)s %(name)s: %(message)s"
|
|
96
|
+
formatter = logging.Formatter(log_format, datefmt="%Y-%m-%d %H:%M:%S")
|
|
97
|
+
|
|
98
|
+
root = logging.getLogger()
|
|
99
|
+
root.setLevel(logging.INFO)
|
|
100
|
+
|
|
101
|
+
config_candidates = [Path.cwd() / "config.ini", repo_root / "python" / "config.ini"]
|
|
102
|
+
config.read([str(path) for path in config_candidates if path.exists()])
|
|
103
|
+
file_pps = config.getboolean("Logging", "file_pps", fallback=True)
|
|
104
|
+
file_conn_rpt = config.getboolean("Logging", "file_conn_rpt", fallback=True)
|
|
105
|
+
console_pps = config.getboolean("Logging", "terminal_pps", fallback=True)
|
|
106
|
+
console_conn_rpt = config.getboolean("Logging", "terminal_conn_rpt", fallback=False)
|
|
107
|
+
|
|
108
|
+
class _ConsoleFilter(logging.Filter):
|
|
109
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
110
|
+
msg = record.getMessage()
|
|
111
|
+
if not console_pps and msg.startswith("[pps]"):
|
|
112
|
+
return False
|
|
113
|
+
if not console_conn_rpt and msg.startswith("Conn Rpt:"):
|
|
114
|
+
return False
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
class _FileFilter(logging.Filter):
|
|
118
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
119
|
+
msg = record.getMessage()
|
|
120
|
+
if not file_pps and msg.startswith("[pps]"):
|
|
121
|
+
return False
|
|
122
|
+
if not file_conn_rpt and msg.startswith("Conn Rpt:"):
|
|
123
|
+
return False
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
want_file = True
|
|
127
|
+
want_console = True
|
|
128
|
+
for handler in root.handlers:
|
|
129
|
+
if isinstance(handler, logging.FileHandler):
|
|
130
|
+
try:
|
|
131
|
+
if Path(handler.baseFilename).resolve() == log_path.resolve():
|
|
132
|
+
want_file = False
|
|
133
|
+
except OSError:
|
|
134
|
+
pass
|
|
135
|
+
handler.addFilter(_FileFilter())
|
|
136
|
+
elif isinstance(handler, logging.StreamHandler):
|
|
137
|
+
if not isinstance(handler, logging.FileHandler):
|
|
138
|
+
want_console = False
|
|
139
|
+
handler.addFilter(_ConsoleFilter())
|
|
140
|
+
handler.setFormatter(formatter)
|
|
141
|
+
|
|
142
|
+
if want_file:
|
|
143
|
+
file_handler = logging.FileHandler(log_path, encoding="utf-8")
|
|
144
|
+
file_handler.setFormatter(formatter)
|
|
145
|
+
file_handler.addFilter(_FileFilter())
|
|
146
|
+
root.addHandler(file_handler)
|
|
147
|
+
if want_console:
|
|
148
|
+
console_handler = logging.StreamHandler()
|
|
149
|
+
console_handler.setFormatter(formatter)
|
|
150
|
+
console_handler.addFilter(_ConsoleFilter())
|
|
151
|
+
root.addHandler(console_handler)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _display_available() -> bool:
|
|
155
|
+
wayland_display = os.environ.get("WAYLAND_DISPLAY")
|
|
156
|
+
xdg_runtime_dir = os.environ.get("XDG_RUNTIME_DIR")
|
|
157
|
+
if wayland_display and xdg_runtime_dir:
|
|
158
|
+
if Path(xdg_runtime_dir, wayland_display).exists():
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
display = os.environ.get("DISPLAY")
|
|
162
|
+
if not display:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
host, _, rest = display.rpartition(":")
|
|
166
|
+
if not rest:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
display_num = rest.split(".", 1)[0]
|
|
170
|
+
if not display_num.isdigit():
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
host = host.lower()
|
|
174
|
+
if host in ("", "unix"):
|
|
175
|
+
return Path(f"/tmp/.X11-unix/X{display_num}").exists()
|
|
176
|
+
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _ensure_gui_display() -> bool:
|
|
181
|
+
if _is_truthy(os.environ.get("ENODE_GUI_FORCE")):
|
|
182
|
+
return True
|
|
183
|
+
if _display_available():
|
|
184
|
+
return True
|
|
185
|
+
print("[gui] no display detected (X11/Wayland).", file=sys.stderr)
|
|
186
|
+
print(
|
|
187
|
+
"[gui] Run `enode-host server ...`, or start an X11/Wayland session, or use `xvfb-run enode-host gui`.",
|
|
188
|
+
file=sys.stderr,
|
|
189
|
+
)
|
|
190
|
+
print("[gui] Set ENODE_GUI_FORCE=1 to try anyway.", file=sys.stderr)
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
|
|
67
194
|
def build_parser() -> argparse.ArgumentParser:
|
|
68
195
|
parser = argparse.ArgumentParser(
|
|
69
196
|
prog="enode-host",
|
|
@@ -112,6 +239,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
112
239
|
|
|
113
240
|
|
|
114
241
|
def main() -> int:
|
|
242
|
+
_setup_logging()
|
|
115
243
|
parser = build_parser()
|
|
116
244
|
args = parser.parse_args()
|
|
117
245
|
|
|
@@ -120,6 +248,8 @@ def main() -> int:
|
|
|
120
248
|
return 0
|
|
121
249
|
|
|
122
250
|
if args.command is None:
|
|
251
|
+
if not _ensure_gui_display():
|
|
252
|
+
return 2
|
|
123
253
|
try:
|
|
124
254
|
from .gui_framed import run_gui
|
|
125
255
|
except Exception as exc:
|
|
@@ -142,6 +272,8 @@ def main() -> int:
|
|
|
142
272
|
return 0
|
|
143
273
|
|
|
144
274
|
if args.command == "gui":
|
|
275
|
+
if not _ensure_gui_display():
|
|
276
|
+
return 2
|
|
145
277
|
try:
|
|
146
278
|
from .gui_framed import run_gui
|
|
147
279
|
except Exception as exc:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import threading
|
|
3
4
|
from typing import Dict, Optional
|
|
4
5
|
|
|
@@ -16,6 +17,8 @@ from .protocol import (
|
|
|
16
17
|
parse_gnss,
|
|
17
18
|
)
|
|
18
19
|
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
class FramedMesh:
|
|
21
24
|
def __init__(self, model, host: str = "0.0.0.0", port: int = 3333):
|
|
@@ -71,6 +74,7 @@ class FramedMesh:
|
|
|
71
74
|
daq_mode=status.daq_mode,
|
|
72
75
|
daq_on=status.daq_on,
|
|
73
76
|
stream_status=status.stream_status,
|
|
77
|
+
bat_vol=status.bat_vol,
|
|
74
78
|
notify=False,
|
|
75
79
|
)
|
|
76
80
|
elif msg_type == MsgType.DATA:
|
|
@@ -87,12 +91,15 @@ class FramedMesh:
|
|
|
87
91
|
pps = parse_pps(payload)
|
|
88
92
|
except Exception as exc:
|
|
89
93
|
preview = payload[:12].hex()
|
|
90
|
-
|
|
94
|
+
logger.warning(
|
|
95
|
+
"[pps] parse failed len=%d head=%s err=%s", len(payload), preview, exc
|
|
96
|
+
)
|
|
91
97
|
continue
|
|
92
98
|
node_id = self.model.make_node_key(pps.node_type, pps.node_number)
|
|
93
99
|
self.clients[node_id] = writer
|
|
94
100
|
with self._stat_lock:
|
|
95
101
|
self._pps_frames += 1
|
|
102
|
+
self._ui_log(f"[pps] node={node_id} cc={pps.cc} epoch={pps.epoch}")
|
|
96
103
|
self.model.enqueue_pps(node_id, pps.cc, pps.epoch)
|
|
97
104
|
elif msg_type == MsgType.SD_STREAM:
|
|
98
105
|
chunk = parse_sd_chunk(payload)
|
|
@@ -158,7 +165,7 @@ class FramedMesh:
|
|
|
158
165
|
if callable(ui_log):
|
|
159
166
|
ui_log(msg)
|
|
160
167
|
else:
|
|
161
|
-
|
|
168
|
+
logger.info("%s", msg)
|
|
162
169
|
|
|
163
170
|
async def _send(self, writer: asyncio.StreamWriter, payload: bytes) -> None:
|
|
164
171
|
writer.write(payload)
|
|
@@ -4,7 +4,7 @@ import threading
|
|
|
4
4
|
import wx
|
|
5
5
|
|
|
6
6
|
from .framed_mesh import FramedMesh
|
|
7
|
-
from .cli import build_command_from_tokens
|
|
7
|
+
from .cli import _setup_logging, build_command_from_tokens
|
|
8
8
|
from .protocol import (
|
|
9
9
|
Toggle,
|
|
10
10
|
build_realtime_stream,
|
|
@@ -12,6 +12,7 @@ from .protocol import (
|
|
|
12
12
|
build_sd_stream_start,
|
|
13
13
|
build_sd_stream_stop,
|
|
14
14
|
build_set_daq_mode,
|
|
15
|
+
build_shutdown,
|
|
15
16
|
build_start_daq,
|
|
16
17
|
build_stop_daq,
|
|
17
18
|
)
|
|
@@ -30,6 +31,10 @@ class Controller:
|
|
|
30
31
|
self.mesh = FramedMesh(self.model, host=host, port=port)
|
|
31
32
|
|
|
32
33
|
self.view.m_button_nodes_update.Bind(wx.EVT_BUTTON, self.on_change_node_nums)
|
|
34
|
+
self.view.m_textCtrl_acc.Bind(wx.EVT_TEXT_ENTER, self.on_change_node_nums)
|
|
35
|
+
self.view.m_textCtrl_tmp.Bind(wx.EVT_TEXT_ENTER, self.on_change_node_nums)
|
|
36
|
+
self.view.m_textCtrl_str.Bind(wx.EVT_TEXT_ENTER, self.on_change_node_nums)
|
|
37
|
+
self.view.m_textCtrl_veh.Bind(wx.EVT_TEXT_ENTER, self.on_change_node_nums)
|
|
33
38
|
self.view.Bind(wx.EVT_MENU, self.on_exit, self.view.m_menu_file_quit)
|
|
34
39
|
self.view.Bind(wx.EVT_MENU, self.on_merge_rt_files, self.view.m_menu_data_export)
|
|
35
40
|
self.view.Bind(wx.EVT_MENU, self.on_merge_sd_files, self.view.m_menu_data_merge_sd)
|
|
@@ -37,7 +42,7 @@ class Controller:
|
|
|
37
42
|
self.view.Bind(wx.EVT_MENU, self.on_plot_sd_merged, self.view.m_menu_data_plot_sd)
|
|
38
43
|
self.view.Bind(wx.EVT_MENU, self.on_clear_rt_files, self.view.m_menu_data_clear_rt)
|
|
39
44
|
self.view.Bind(wx.EVT_MENU, self.on_clear_sd_files, self.view.m_menu_data_clear_sd)
|
|
40
|
-
self.view.Bind(wx.
|
|
45
|
+
self.view.m_button_clf.Bind(wx.EVT_BUTTON, self.on_view_clf)
|
|
41
46
|
self.view.m_button1.Bind(wx.EVT_BUTTON, self.on_command_choice_send)
|
|
42
47
|
self.view.m_textCtrl_cmd.Bind(wx.EVT_TEXT_ENTER, self.on_command_send)
|
|
43
48
|
|
|
@@ -79,6 +84,7 @@ class Controller:
|
|
|
79
84
|
self.model.init_other_data()
|
|
80
85
|
self.view.init_plot()
|
|
81
86
|
self.view.figure_update()
|
|
87
|
+
self.view.show_rt_plots()
|
|
82
88
|
|
|
83
89
|
def _broadcast(self, payload: bytes, label: str) -> None:
|
|
84
90
|
sent = self.mesh.broadcast_command(payload, label=label)
|
|
@@ -105,6 +111,8 @@ class Controller:
|
|
|
105
111
|
self._broadcast(build_sd_stream_stop(), "sd_stream_stop")
|
|
106
112
|
elif selection == "SD Clear All":
|
|
107
113
|
self._broadcast(build_sd_clear(), "sd_clear")
|
|
114
|
+
elif selection == "Shutdown":
|
|
115
|
+
self._broadcast(build_shutdown(), "shutdown")
|
|
108
116
|
|
|
109
117
|
def set_daq_mode(self, node_id: str, mode: int) -> None:
|
|
110
118
|
payload = build_set_daq_mode(mode)
|
|
@@ -202,6 +210,7 @@ class Controller:
|
|
|
202
210
|
|
|
203
211
|
|
|
204
212
|
def run_gui(host: str = "0.0.0.0", port: int = 3333) -> None:
|
|
213
|
+
_setup_logging()
|
|
205
214
|
app = wx.App()
|
|
206
215
|
Controller(host=host, port=port)
|
|
207
216
|
app.MainLoop()
|