enode-host 0.1.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.
- enode_host-0.1.0/PKG-INFO +81 -0
- enode_host-0.1.0/README.md +69 -0
- enode_host-0.1.0/pyproject.toml +25 -0
- enode_host-0.1.0/setup.cfg +4 -0
- enode_host-0.1.0/src/enode_host/__init__.py +1 -0
- enode_host-0.1.0/src/enode_host/async_socket.py +165 -0
- enode_host-0.1.0/src/enode_host/backup/c-wing3.py +226 -0
- enode_host-0.1.0/src/enode_host/backup/c.py +226 -0
- enode_host-0.1.0/src/enode_host/backup/esp_mesh.py +136 -0
- enode_host-0.1.0/src/enode_host/backup/gui_comps.py +113 -0
- enode_host-0.1.0/src/enode_host/backup/gui_wx_bk.py +270 -0
- enode_host-0.1.0/src/enode_host/backup/load_file.py +27 -0
- enode_host-0.1.0/src/enode_host/backup/mesh.py +0 -0
- enode_host-0.1.0/src/enode_host/backup/mesh_bk.py +0 -0
- enode_host-0.1.0/src/enode_host/backup/s.py +151 -0
- enode_host-0.1.0/src/enode_host/backup/sandbox.py +93 -0
- enode_host-0.1.0/src/enode_host/backup/shm.py +262 -0
- enode_host-0.1.0/src/enode_host/backup/shmtools.py +70 -0
- enode_host-0.1.0/src/enode_host/backup/smarts.py +243 -0
- enode_host-0.1.0/src/enode_host/backup/test_wxpython_choice.py +49 -0
- enode_host-0.1.0/src/enode_host/backup/view-wing3.py +494 -0
- enode_host-0.1.0/src/enode_host/backup/wx_example.py +55 -0
- enode_host-0.1.0/src/enode_host/backup/wx_test01.py +43 -0
- enode_host-0.1.0/src/enode_host/cli.py +192 -0
- enode_host-0.1.0/src/enode_host/config.py +8 -0
- enode_host-0.1.0/src/enode_host/constants.py +25 -0
- enode_host-0.1.0/src/enode_host/framed_mesh.py +237 -0
- enode_host-0.1.0/src/enode_host/gui_framed.py +207 -0
- enode_host-0.1.0/src/enode_host/model.py +1415 -0
- enode_host-0.1.0/src/enode_host/protocol.py +311 -0
- enode_host-0.1.0/src/enode_host/psd_recursive.py +139 -0
- enode_host-0.1.0/src/enode_host/queues.py +11 -0
- enode_host-0.1.0/src/enode_host/resampling.py +206 -0
- enode_host-0.1.0/src/enode_host/shm_sigproc.py +47 -0
- enode_host-0.1.0/src/enode_host/storage.py +93 -0
- enode_host-0.1.0/src/enode_host/timestamping.py +79 -0
- enode_host-0.1.0/src/enode_host/types.py +38 -0
- enode_host-0.1.0/src/enode_host/view.py +1233 -0
- enode_host-0.1.0/src/enode_host.egg-info/PKG-INFO +81 -0
- enode_host-0.1.0/src/enode_host.egg-info/SOURCES.txt +42 -0
- enode_host-0.1.0/src/enode_host.egg-info/dependency_links.txt +1 -0
- enode_host-0.1.0/src/enode_host.egg-info/entry_points.txt +2 -0
- enode_host-0.1.0/src/enode_host.egg-info/requires.txt +3 -0
- enode_host-0.1.0/src/enode_host.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: enode-host
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Host-side tools that interact with the ESP-IDF firmware.
|
|
5
|
+
Author: eNode team
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: PyQt5>=5.15
|
|
10
|
+
Requires-Dist: matplotlib>=3.8
|
|
11
|
+
Requires-Dist: pandas>=2.2
|
|
12
|
+
|
|
13
|
+
# Host Python Tools
|
|
14
|
+
|
|
15
|
+
This folder contains host-side Python code that interacts with the ESP-IDF
|
|
16
|
+
firmware (e.g., over serial, sockets, or file exchange). It is kept separate
|
|
17
|
+
from the firmware build to avoid mixing tool dependencies with ESP-IDF.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m venv .venv
|
|
23
|
+
source .venv/bin/activate
|
|
24
|
+
pip install -e .
|
|
25
|
+
|
|
26
|
+
enode-host --help
|
|
27
|
+
enode-host socket 127.0.0.1 3333 "hello"
|
|
28
|
+
enode-host server --port 3333 --interactive
|
|
29
|
+
enode-host gui --port 3333
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- Add runtime dependencies to `pyproject.toml` under `project.dependencies`.
|
|
35
|
+
- Keep device/transport details in small modules so they can be shared by
|
|
36
|
+
scripts and tests.
|
|
37
|
+
- The GUI uses wxPython; install it if you plan to run `enode-host gui`.
|
|
38
|
+
|
|
39
|
+
## Protocol (TCP, length-prefixed)
|
|
40
|
+
|
|
41
|
+
Frame format: 1 byte message type, 2 bytes big-endian payload length, payload.
|
|
42
|
+
|
|
43
|
+
Message types:
|
|
44
|
+
- 0x10 COMMAND (host -> device)
|
|
45
|
+
- 0x20 STATUS (device -> host)
|
|
46
|
+
- 0x30 ACK (either direction)
|
|
47
|
+
- 0x40 DATA (device -> host, ACC batch)
|
|
48
|
+
- 0x41 PPS (device -> host, immediate PPS)
|
|
49
|
+
|
|
50
|
+
Commands (payload begins with command_id):
|
|
51
|
+
- 0x01 start_daq
|
|
52
|
+
- 0x02 stop_daq
|
|
53
|
+
- 0x03 set_mode (1 byte: 0 realtime, 1 past)
|
|
54
|
+
- 0x04 start_realtime_stream
|
|
55
|
+
- 0x05 stop_realtime_stream
|
|
56
|
+
- 0x06 start_past_stream (payload: 8-byte start_ms, 8-byte end_ms)
|
|
57
|
+
- 0x07 stop_past_stream
|
|
58
|
+
|
|
59
|
+
Status payload (16 bytes):
|
|
60
|
+
- node_type: uint8
|
|
61
|
+
- node_number: uint8
|
|
62
|
+
- level: uint8
|
|
63
|
+
- parent_mac: 6 bytes
|
|
64
|
+
- self_mac: 6 bytes
|
|
65
|
+
- rssi: int8
|
|
66
|
+
|
|
67
|
+
DATA payload:
|
|
68
|
+
- node_type: uint8
|
|
69
|
+
- node_number: uint8
|
|
70
|
+
- sample_count: uint8
|
|
71
|
+
- repeated samples (count):
|
|
72
|
+
- cc: uint64 (big-endian)
|
|
73
|
+
- acc_x: float32 (big-endian)
|
|
74
|
+
- acc_y: float32 (big-endian)
|
|
75
|
+
- acc_z: float32 (big-endian)
|
|
76
|
+
|
|
77
|
+
PPS payload:
|
|
78
|
+
- node_type: uint8
|
|
79
|
+
- node_number: uint8
|
|
80
|
+
- cc: uint64 (big-endian)
|
|
81
|
+
- epoch: int64 (big-endian)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Host Python Tools
|
|
2
|
+
|
|
3
|
+
This folder contains host-side Python code that interacts with the ESP-IDF
|
|
4
|
+
firmware (e.g., over serial, sockets, or file exchange). It is kept separate
|
|
5
|
+
from the firmware build to avoid mixing tool dependencies with ESP-IDF.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m venv .venv
|
|
11
|
+
source .venv/bin/activate
|
|
12
|
+
pip install -e .
|
|
13
|
+
|
|
14
|
+
enode-host --help
|
|
15
|
+
enode-host socket 127.0.0.1 3333 "hello"
|
|
16
|
+
enode-host server --port 3333 --interactive
|
|
17
|
+
enode-host gui --port 3333
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
|
|
22
|
+
- Add runtime dependencies to `pyproject.toml` under `project.dependencies`.
|
|
23
|
+
- Keep device/transport details in small modules so they can be shared by
|
|
24
|
+
scripts and tests.
|
|
25
|
+
- The GUI uses wxPython; install it if you plan to run `enode-host gui`.
|
|
26
|
+
|
|
27
|
+
## Protocol (TCP, length-prefixed)
|
|
28
|
+
|
|
29
|
+
Frame format: 1 byte message type, 2 bytes big-endian payload length, payload.
|
|
30
|
+
|
|
31
|
+
Message types:
|
|
32
|
+
- 0x10 COMMAND (host -> device)
|
|
33
|
+
- 0x20 STATUS (device -> host)
|
|
34
|
+
- 0x30 ACK (either direction)
|
|
35
|
+
- 0x40 DATA (device -> host, ACC batch)
|
|
36
|
+
- 0x41 PPS (device -> host, immediate PPS)
|
|
37
|
+
|
|
38
|
+
Commands (payload begins with command_id):
|
|
39
|
+
- 0x01 start_daq
|
|
40
|
+
- 0x02 stop_daq
|
|
41
|
+
- 0x03 set_mode (1 byte: 0 realtime, 1 past)
|
|
42
|
+
- 0x04 start_realtime_stream
|
|
43
|
+
- 0x05 stop_realtime_stream
|
|
44
|
+
- 0x06 start_past_stream (payload: 8-byte start_ms, 8-byte end_ms)
|
|
45
|
+
- 0x07 stop_past_stream
|
|
46
|
+
|
|
47
|
+
Status payload (16 bytes):
|
|
48
|
+
- node_type: uint8
|
|
49
|
+
- node_number: uint8
|
|
50
|
+
- level: uint8
|
|
51
|
+
- parent_mac: 6 bytes
|
|
52
|
+
- self_mac: 6 bytes
|
|
53
|
+
- rssi: int8
|
|
54
|
+
|
|
55
|
+
DATA payload:
|
|
56
|
+
- node_type: uint8
|
|
57
|
+
- node_number: uint8
|
|
58
|
+
- sample_count: uint8
|
|
59
|
+
- repeated samples (count):
|
|
60
|
+
- cc: uint64 (big-endian)
|
|
61
|
+
- acc_x: float32 (big-endian)
|
|
62
|
+
- acc_y: float32 (big-endian)
|
|
63
|
+
- acc_z: float32 (big-endian)
|
|
64
|
+
|
|
65
|
+
PPS payload:
|
|
66
|
+
- node_type: uint8
|
|
67
|
+
- node_number: uint8
|
|
68
|
+
- cc: uint64 (big-endian)
|
|
69
|
+
- epoch: int64 (big-endian)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "enode-host"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Host-side tools that interact with the ESP-IDF firmware."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "eNode team"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"PyQt5>=5.15",
|
|
17
|
+
"matplotlib>=3.8",
|
|
18
|
+
"pandas>=2.2",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
enode-host = "enode_host.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Host-side utilities for interacting with the ESP-IDF firmware."""
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import struct
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from .protocol import (
|
|
7
|
+
MsgType,
|
|
8
|
+
build_ack,
|
|
9
|
+
format_mac,
|
|
10
|
+
parse_acc_batch,
|
|
11
|
+
parse_pps,
|
|
12
|
+
parse_sd_chunk,
|
|
13
|
+
parse_sd_done,
|
|
14
|
+
parse_status,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class SocketResponse:
|
|
20
|
+
payload: bytes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def send_message(host: str, port: int, payload: bytes, timeout: float = 5.0) -> SocketResponse:
|
|
24
|
+
reader: asyncio.StreamReader
|
|
25
|
+
writer: asyncio.StreamWriter
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
reader, writer = await asyncio.wait_for(
|
|
29
|
+
asyncio.open_connection(host, port), timeout=timeout
|
|
30
|
+
)
|
|
31
|
+
except asyncio.TimeoutError as exc:
|
|
32
|
+
raise TimeoutError(f"Timed out connecting to {host}:{port}") from exc
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
writer.write(payload)
|
|
36
|
+
await writer.drain()
|
|
37
|
+
data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
|
38
|
+
return SocketResponse(payload=data)
|
|
39
|
+
finally:
|
|
40
|
+
writer.close()
|
|
41
|
+
await writer.wait_closed()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_client(host: str, port: int, message: str, timeout: float) -> int:
|
|
45
|
+
payload = message.encode("utf-8")
|
|
46
|
+
response = asyncio.run(send_message(host, port, payload, timeout))
|
|
47
|
+
if response.payload:
|
|
48
|
+
print(response.payload.decode("utf-8", errors="replace"))
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def read_frame(reader: asyncio.StreamReader) -> Optional[tuple[int, bytes]]:
|
|
53
|
+
header = await reader.readexactly(3)
|
|
54
|
+
msg_type, length = struct.unpack(">BH", header)
|
|
55
|
+
if length == 0:
|
|
56
|
+
return msg_type, b""
|
|
57
|
+
payload = await reader.readexactly(length)
|
|
58
|
+
return msg_type, payload
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, clients: Dict[str, asyncio.StreamWriter]) -> None:
|
|
62
|
+
peer = writer.get_extra_info("peername")
|
|
63
|
+
peer_id = f"{peer[0]}:{peer[1]}" if peer else "unknown"
|
|
64
|
+
clients[peer_id] = writer
|
|
65
|
+
print(f"[server] connected {peer_id} (clients={len(clients)})")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
while True:
|
|
69
|
+
msg_type, payload = await read_frame(reader)
|
|
70
|
+
if msg_type == MsgType.STATUS:
|
|
71
|
+
status = parse_status(payload)
|
|
72
|
+
print(
|
|
73
|
+
"[status] "
|
|
74
|
+
f"node={status.node_type}:{status.node_number} "
|
|
75
|
+
f"level={status.level} "
|
|
76
|
+
f"parent={format_mac(status.parent_mac)} "
|
|
77
|
+
f"self={format_mac(status.self_mac)} "
|
|
78
|
+
f"rssi={status.rssi}"
|
|
79
|
+
)
|
|
80
|
+
writer.write(build_ack(MsgType.STATUS, 0, "ok"))
|
|
81
|
+
await writer.drain()
|
|
82
|
+
elif msg_type == MsgType.DATA:
|
|
83
|
+
batch = parse_acc_batch(payload)
|
|
84
|
+
print(f"[data] node={batch.node_type}:{batch.node_number} samples={len(batch.samples)}")
|
|
85
|
+
writer.write(build_ack(MsgType.DATA, 0, "ok"))
|
|
86
|
+
await writer.drain()
|
|
87
|
+
elif msg_type == MsgType.PPS:
|
|
88
|
+
pps = parse_pps(payload)
|
|
89
|
+
print(f"[pps] node={pps.node_type}:{pps.node_number} cc={pps.cc} epoch={pps.epoch}")
|
|
90
|
+
writer.write(build_ack(MsgType.PPS, 0, "ok"))
|
|
91
|
+
await writer.drain()
|
|
92
|
+
elif msg_type == MsgType.SD_STREAM:
|
|
93
|
+
chunk = parse_sd_chunk(payload)
|
|
94
|
+
print(
|
|
95
|
+
f"[sd] node={chunk.node_type}:{chunk.node_number} "
|
|
96
|
+
f"file_time={chunk.file_time} offset={chunk.offset} size={len(chunk.data)}"
|
|
97
|
+
)
|
|
98
|
+
writer.write(build_ack(MsgType.SD_STREAM, 0, "ok"))
|
|
99
|
+
await writer.drain()
|
|
100
|
+
elif msg_type == MsgType.SD_DONE:
|
|
101
|
+
done = parse_sd_done(payload)
|
|
102
|
+
print(
|
|
103
|
+
f"[sd] node={done.node_type}:{done.node_number} "
|
|
104
|
+
f"file_time={done.file_time} status={done.status}"
|
|
105
|
+
)
|
|
106
|
+
writer.write(build_ack(MsgType.SD_DONE, 0, "ok"))
|
|
107
|
+
await writer.drain()
|
|
108
|
+
else:
|
|
109
|
+
print(f"[server] msg_type=0x{msg_type:02X} len={len(payload)}")
|
|
110
|
+
writer.write(build_ack(msg_type, 0, "ok"))
|
|
111
|
+
await writer.drain()
|
|
112
|
+
except asyncio.IncompleteReadError:
|
|
113
|
+
pass
|
|
114
|
+
finally:
|
|
115
|
+
clients.pop(peer_id, None)
|
|
116
|
+
writer.close()
|
|
117
|
+
await writer.wait_closed()
|
|
118
|
+
print(f"[server] disconnected {peer_id} (clients={len(clients)})")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def broadcast_messages(
|
|
122
|
+
queue: asyncio.Queue[bytes],
|
|
123
|
+
clients: Dict[str, asyncio.StreamWriter],
|
|
124
|
+
) -> None:
|
|
125
|
+
while True:
|
|
126
|
+
payload = await queue.get()
|
|
127
|
+
if not clients:
|
|
128
|
+
print("[server] no clients to broadcast to")
|
|
129
|
+
continue
|
|
130
|
+
for peer_id, writer in list(clients.items()):
|
|
131
|
+
try:
|
|
132
|
+
writer.write(payload)
|
|
133
|
+
await writer.drain()
|
|
134
|
+
except ConnectionError:
|
|
135
|
+
clients.pop(peer_id, None)
|
|
136
|
+
print(f"[server] broadcast to {len(clients)} clients")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def run_server(
|
|
140
|
+
host: str,
|
|
141
|
+
port: int,
|
|
142
|
+
on_connect_payload: Optional[bytes],
|
|
143
|
+
command_queue: Optional[asyncio.Queue[bytes]] = None,
|
|
144
|
+
) -> None:
|
|
145
|
+
clients: Dict[str, asyncio.StreamWriter] = {}
|
|
146
|
+
|
|
147
|
+
async def _client_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
|
|
148
|
+
if on_connect_payload:
|
|
149
|
+
writer.write(on_connect_payload)
|
|
150
|
+
await writer.drain()
|
|
151
|
+
await handle_client(reader, writer, clients)
|
|
152
|
+
|
|
153
|
+
server = await asyncio.start_server(_client_handler, host=host, port=port)
|
|
154
|
+
addrs = ", ".join(str(sock.getsockname()) for sock in server.sockets or [])
|
|
155
|
+
print(f"[server] listening on {addrs}")
|
|
156
|
+
|
|
157
|
+
async with server:
|
|
158
|
+
if command_queue is None:
|
|
159
|
+
await server.serve_forever()
|
|
160
|
+
else:
|
|
161
|
+
broadcaster = asyncio.create_task(broadcast_messages(command_queue, clients))
|
|
162
|
+
try:
|
|
163
|
+
await server.serve_forever()
|
|
164
|
+
finally:
|
|
165
|
+
broadcaster.cancel()
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/local/bin/python3
|
|
2
|
+
|
|
3
|
+
#----------------------------------------------
|
|
4
|
+
# NOTE
|
|
5
|
+
# 1. Ask Ki about the definition of local axes for M352, or see the line # 397 of model.py
|
|
6
|
+
# when you look at the screen of the node,
|
|
7
|
+
# the direction to the left hand-side is the X-axis
|
|
8
|
+
# the downward direction is the Y-axis
|
|
9
|
+
# the direction perpendicular to the screen and coming towards yourself is Z-axis
|
|
10
|
+
#
|
|
11
|
+
# this is the right-hand rule:
|
|
12
|
+
# the direction of you index finger is the x-axis,
|
|
13
|
+
# that of the middle finger is the y-axis
|
|
14
|
+
# that of the thumb is the z-axis
|
|
15
|
+
#
|
|
16
|
+
#---------------------------------------------
|
|
17
|
+
# HOW TO USE THE SYSTEM
|
|
18
|
+
#
|
|
19
|
+
# 1. please execute this file first, so that the server is ready to receive data from ndoes
|
|
20
|
+
# 2. turn on the node(s)
|
|
21
|
+
# - the PPS LED on the display can be believed
|
|
22
|
+
# - please don't believe RSSI, Lv, BAT %, as they are not completed yet.
|
|
23
|
+
# 3. Files are saved under data2 folder in HDF
|
|
24
|
+
# 4. Please read example.ipynb for how to load data file and display it
|
|
25
|
+
# - Please notice the time is in UTC to avoid any issues regarding British Summer Time
|
|
26
|
+
# 5. For time-sync, never reset the acc node, but power-cycle it.
|
|
27
|
+
# - there are two buttons on the sensor node:
|
|
28
|
+
# - one for power next to the USB-c port
|
|
29
|
+
# - the other for reset next to the SD slot
|
|
30
|
+
# - pressing the pwr button for 5 sec will shutdown the node, and pressing it for 1 sec will start the node
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
import wx
|
|
35
|
+
import view
|
|
36
|
+
import model
|
|
37
|
+
import importlib
|
|
38
|
+
importlib.reload(view)
|
|
39
|
+
importlib.reload(model)
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
import threading
|
|
43
|
+
import queues
|
|
44
|
+
from numpy import array
|
|
45
|
+
import shm_sigproc
|
|
46
|
+
import glob
|
|
47
|
+
import os
|
|
48
|
+
import esp_mesh
|
|
49
|
+
import pandas as pd
|
|
50
|
+
from model import CmdType, ChannelType
|
|
51
|
+
from enum import IntEnum
|
|
52
|
+
|
|
53
|
+
os.remove('log/my_log_file.log')
|
|
54
|
+
|
|
55
|
+
logging.basicConfig(level=logging.INFO, # Set the minimum log level
|
|
56
|
+
#format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
57
|
+
format='%(name)s - %(levelname)s - %(message)s',
|
|
58
|
+
handlers=[logging.FileHandler('log/my_log_file.log'), logging.StreamHandler()])
|
|
59
|
+
logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
os.makedirs("raw-data", exist_ok = True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CMD_TYPE(IntEnum):
|
|
66
|
+
SET_MODE = 0
|
|
67
|
+
CLEAR_STORAGE = 1
|
|
68
|
+
NOTIFY_PARENT = 2
|
|
69
|
+
NOTIFY_CHILDREN = 3
|
|
70
|
+
SHUTDOWN = 4
|
|
71
|
+
|
|
72
|
+
class OPERATION_MODE(IntEnum):
|
|
73
|
+
ONLINE_DAQ = 0
|
|
74
|
+
STANDALONE_DATA_PEEKING = 1
|
|
75
|
+
POST_DAQ_DATA_STREAMING = 2
|
|
76
|
+
MAINTENANCE = 3
|
|
77
|
+
|
|
78
|
+
class Controller:
|
|
79
|
+
|
|
80
|
+
def __init__(self):
|
|
81
|
+
|
|
82
|
+
self.model = model.Model(self)
|
|
83
|
+
self.view = view.View(self)
|
|
84
|
+
self.view.Show()
|
|
85
|
+
self.mesh = esp_mesh.Esp_Mesh(self.model)
|
|
86
|
+
|
|
87
|
+
# mesh.begin()
|
|
88
|
+
# shm_sigproc.begin()
|
|
89
|
+
|
|
90
|
+
#
|
|
91
|
+
# BINDINGS
|
|
92
|
+
#
|
|
93
|
+
|
|
94
|
+
# node_num_txt
|
|
95
|
+
self.view.m_textCtrl2.Bind(wx.EVT_TEXT, self.on_change_node_nums)
|
|
96
|
+
|
|
97
|
+
# Menu File>Exit
|
|
98
|
+
self.view.Bind(wx.EVT_MENU, self.on_exit, self.view.m_menu_file_quit)
|
|
99
|
+
|
|
100
|
+
# Menu Data>Export
|
|
101
|
+
self.view.Bind(wx.EVT_MENU, self.on_data_export, self.view.m_menu_data_export)
|
|
102
|
+
|
|
103
|
+
# Menu CMD>SD clear
|
|
104
|
+
self.view.Bind(wx.EVT_MENU, self.on_cmd_sdclear, self.view.m_menu_cmd_sdclear)
|
|
105
|
+
|
|
106
|
+
# Menu CMD>Shutdown
|
|
107
|
+
self.view.Bind(wx.EVT_MENU, self.on_cmd_shutdown, self.view.m_menu_cmd_shutdown)
|
|
108
|
+
|
|
109
|
+
# Menu Tool>clf
|
|
110
|
+
self.view.Bind(wx.EVT_MENU, self.on_view_clf, self.view.m_menu_view_clf)
|
|
111
|
+
|
|
112
|
+
# Start DAQ Button
|
|
113
|
+
self.view.m_button1.Bind(wx.EVT_BUTTON, self.Start_Button_onclick)
|
|
114
|
+
|
|
115
|
+
# Stop DAQ Button
|
|
116
|
+
self.view.m_button2.Bind(wx.EVT_BUTTON, self.Stop_Button_onclick)
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
self.view.m_choice_OperationMode.Bind(wx.EVT_CHOICE, self.choice_OperationMode_onchange)
|
|
120
|
+
|
|
121
|
+
def on_change_node_nums(self, event):
|
|
122
|
+
|
|
123
|
+
self.model.options['node_nums_txt'] = self.view.m_textCtrl2.GetValue()
|
|
124
|
+
self.model.parse_node_nums_txt()
|
|
125
|
+
self.model.save_config()
|
|
126
|
+
self.model.init_mesh_status_data()
|
|
127
|
+
self.model.init_other_data()
|
|
128
|
+
self.view.mesh_status_data_view()
|
|
129
|
+
self.view.m_grid2.ForceRefresh()
|
|
130
|
+
# Init the time history and PSD plots
|
|
131
|
+
self.view.init_plot()
|
|
132
|
+
|
|
133
|
+
def on_exit(self, event):
|
|
134
|
+
|
|
135
|
+
self.view.Close()
|
|
136
|
+
|
|
137
|
+
def on_data_export(self, event):
|
|
138
|
+
|
|
139
|
+
self.model.export()
|
|
140
|
+
|
|
141
|
+
def Start_Button_onclick(self, event):
|
|
142
|
+
|
|
143
|
+
map = {
|
|
144
|
+
OPERATION_MODE.ONLINE_DAQ: 1,
|
|
145
|
+
OPERATION_MODE.STANDALONE_DATA_PEEKING: 5,
|
|
146
|
+
OPERATION_MODE.POST_DAQ_DATA_STREAMING: 7,
|
|
147
|
+
}
|
|
148
|
+
operation_Mode = self.view.m_choice_OperationMode.GetSelection()
|
|
149
|
+
|
|
150
|
+
self.model.init_other_data()
|
|
151
|
+
self.view.init_plot()
|
|
152
|
+
self.view.figure_update()
|
|
153
|
+
for inode in self.mesh.sockets.keys():
|
|
154
|
+
self.mesh.send_cmd(inode, CMD_TYPE.SET_MODE, data0 = map[operation_Mode])
|
|
155
|
+
self.model.update_mesh_status_data(inode, 'CMD', "sent")
|
|
156
|
+
self.view.table_update()
|
|
157
|
+
logger.info('command sent: {} start'.format(OPERATION_MODE(operation_Mode).name))
|
|
158
|
+
|
|
159
|
+
def Stop_Button_onclick(self, event):
|
|
160
|
+
|
|
161
|
+
map = {
|
|
162
|
+
OPERATION_MODE.ONLINE_DAQ: 0,
|
|
163
|
+
OPERATION_MODE.STANDALONE_DATA_PEEKING: 4,
|
|
164
|
+
OPERATION_MODE.POST_DAQ_DATA_STREAMING: 2,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
operation_Mode = self.view.m_choice_OperationMode.GetSelection()
|
|
168
|
+
|
|
169
|
+
for inode in self.mesh.sockets.keys():
|
|
170
|
+
self.mesh.send_cmd(inode, CMD_TYPE.SET_MODE, data0 = map[operation_Mode])
|
|
171
|
+
self.model.update_mesh_status_data(inode, 'CMD', "sent")
|
|
172
|
+
self.view.table_update()
|
|
173
|
+
|
|
174
|
+
logger.info('command sent: {} stop'.format(OPERATION_MODE(operation_Mode).name))
|
|
175
|
+
# self.view.gui_update(1)
|
|
176
|
+
|
|
177
|
+
def choice_OperationMode_onchange(self, event):
|
|
178
|
+
operation_Mode = self.view.m_choice_OperationMode.GetSelection()
|
|
179
|
+
if operation_Mode == OPERATION_MODE.POST_DAQ_DATA_STREAMING:
|
|
180
|
+
for inode in self.mesh.sockets.keys():
|
|
181
|
+
self.mesh.send_cmd(inode, CMD_TYPE.SET_MODE, data0 = 2)
|
|
182
|
+
self.model.update_mesh_status_data(inode, 'CMD', "sent")
|
|
183
|
+
self.view.table_update()
|
|
184
|
+
|
|
185
|
+
if operation_Mode == OPERATION_MODE.ONLINE_DAQ:
|
|
186
|
+
self.PPS_outdate_check = True
|
|
187
|
+
else:
|
|
188
|
+
self.PPS_outdate_check = False
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def on_view_clf(self, event):
|
|
192
|
+
self.model.init_other_data()
|
|
193
|
+
self.view.init_plot()
|
|
194
|
+
self.view.figure_update()
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
def on_cmd_sdclear(self, event):
|
|
198
|
+
|
|
199
|
+
for inode in self.mesh.sockets.keys():
|
|
200
|
+
self.mesh.send_cmd(inode, CMD_TYPE.CLEAR_STORAGE)
|
|
201
|
+
self.model.update_mesh_status_data(inode, 'CMD', "sent")
|
|
202
|
+
self.view.table_update()
|
|
203
|
+
logger.info('command sent: SD Clear sent')
|
|
204
|
+
|
|
205
|
+
def on_cmd_shutdown(self, event):
|
|
206
|
+
|
|
207
|
+
for inode in self.mesh.sockets.keys():
|
|
208
|
+
self.mesh.send_cmd(inode, CMD_TYPE.SHUTDOWN)
|
|
209
|
+
self.model.update_mesh_status_data(inode, 'CMD', "sent")
|
|
210
|
+
self.view.table_update()
|
|
211
|
+
logger.info('command sent: SD Clear sent')
|
|
212
|
+
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == '__main__':
|
|
218
|
+
app = wx.App()
|
|
219
|
+
controller = Controller()
|
|
220
|
+
# controller.view.gui_update(1)
|
|
221
|
+
app.MainLoop()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|