Skip to content

Commit 8ae51ee

Browse files
committed
#2 Complete the basic framework for the Python version.
1 parent 66245fd commit 8ae51ee

File tree

11 files changed

+675
-0
lines changed

11 files changed

+675
-0
lines changed

python/infinite_sense_core/__init__.py

Whitespace-only changes.

python/infinite_sense_core/app.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import tkinter as tk
2+
3+
SENSOR_SPACING = 60 # 每个传感器之间的垂直间隔
4+
TICK_INTERVAL = 100 # 时间刻度线间隔(ms)
5+
6+
# 示例传感器数据:{sensor_name: [timestamp1, timestamp2, ...]}
7+
sensor_data = {
8+
"IMU": [100, 300, 500, 800],
9+
"Cam": [150, 350, 700],
10+
"Lid": [200, 600, 900]
11+
}
12+
13+
14+
class TimelineApp:
15+
def __init__(self, root):
16+
self.root = root
17+
self.canvas = tk.Canvas(root, bg="white")
18+
self.canvas.pack(fill=tk.BOTH, expand=True)
19+
20+
self.time_scale = 1.0 # px / ms,初始比例
21+
self.time_offset = 0 # ms,时间轴左端对应时间(平移偏移)
22+
23+
self.dragging = False
24+
self.last_x = 0
25+
26+
self.canvas.bind("<ButtonPress-1>", self.on_drag_start)
27+
self.canvas.bind("<B1-Motion>", self.on_drag_motion)
28+
self.canvas.bind("<MouseWheel>", self.on_mousewheel) # Windows/Mac
29+
self.canvas.bind("<Button-4>", self.on_mousewheel) # Linux 鼠标滚轮向上
30+
self.canvas.bind("<Button-5>", self.on_mousewheel) # Linux 鼠标滚轮向下
31+
32+
# 绑定窗口尺寸变化事件
33+
self.canvas.bind("<Configure>", lambda e: self.draw())
34+
35+
self.draw()
36+
37+
def draw(self):
38+
self.canvas.delete("all")
39+
width = self.canvas.winfo_width()
40+
height = self.canvas.winfo_height()
41+
42+
sensors = list(sensor_data.keys())
43+
44+
# 画每个传感器的时间轴
45+
for idx, sensor in enumerate(sensors):
46+
y = 50 + idx * SENSOR_SPACING
47+
self.canvas.create_text(30, y, text=sensor, anchor="e")
48+
self.canvas.create_line(40, y, width - 20, y, fill="gray", dash=(2, 2))
49+
50+
for t in sensor_data[sensor]:
51+
# 时间根据偏移和缩放计算X坐标
52+
x = 40 + (t - self.time_offset) * self.time_scale
53+
if 40 <= x <= width - 20:
54+
self.canvas.create_line(x, y - 10, x, y + 10, fill="blue")
55+
self.canvas.create_text(x, y + 15, text=str(t), anchor="n", font=("Arial", 8))
56+
57+
# 画时间刻度线
58+
max_time_display = self.time_offset + (width - 60) / self.time_scale
59+
t = (self.time_offset // TICK_INTERVAL) * TICK_INTERVAL
60+
while t <= max_time_display:
61+
x = 40 + (t - self.time_offset) * self.time_scale
62+
self.canvas.create_line(x, 20, x, height - 20, fill="lightgray", dash=(1, 2))
63+
self.canvas.create_text(x, 20, text=str(int(t)), anchor="s", font=("Arial", 8))
64+
t += TICK_INTERVAL
65+
66+
def on_drag_start(self, event):
67+
self.dragging = True
68+
self.last_x = event.x
69+
70+
def on_drag_motion(self, event):
71+
if self.dragging:
72+
dx = event.x - self.last_x
73+
self.last_x = event.x
74+
self.time_offset -= dx / self.time_scale
75+
if self.time_offset < 0:
76+
self.time_offset = 0
77+
self.draw()
78+
79+
def on_mousewheel(self, event):
80+
if event.num == 4 or event.delta > 0:
81+
factor = 1.1
82+
else:
83+
factor = 0.9
84+
85+
width = self.canvas.winfo_width()
86+
mouse_time = self.time_offset + (event.x - 40) / self.time_scale
87+
88+
new_scale = self.time_scale * factor
89+
new_scale = max(0.1, min(new_scale, 10))
90+
91+
self.time_offset = mouse_time - (event.x - 40) / new_scale
92+
if self.time_offset < 0:
93+
self.time_offset = 0
94+
95+
self.time_scale = new_scale
96+
self.draw()

python/infinite_sense_core/data.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import json
2+
from typing import Dict
3+
4+
from .message import Messenger
5+
6+
def process_trigger_data(data: Dict):
7+
if data.get("f") != "t":
8+
return
9+
time_stamp = data["t"]
10+
status = data["s"]
11+
# print(f"[Trigger] time={time_stamp}, status={status}")
12+
13+
14+
def process_imu_data(data: Dict):
15+
if data.get("f") != "imu":
16+
return
17+
imu = {
18+
"time_stamp_us": data["t"],
19+
"a": data["d"][:3],
20+
"g": data["d"][3:6],
21+
"temperature": data["d"][6],
22+
"q": data["q"][:4]
23+
}
24+
Messenger.get_instance().pub("imu_1", json.dumps(imu))
25+
26+
27+
def process_gps_data(data: Dict):
28+
if data.get("f") != "GNGGA":
29+
return
30+
gps = {
31+
"data": data["d"],
32+
"trigger_time_us": data["pps"],
33+
"time_stamp_us": data["t"]
34+
}
35+
Messenger.pub("gps", json.dumps(gps))
36+
37+
def process_log_data(data: Dict):
38+
if data.get("f") != "log":
39+
return
40+
level = data.get("l", "INFO")
41+
msg = data.get("msg", "")
42+
print(f"[LOG-{level}] {msg}")
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import threading
2+
import time
3+
import logging
4+
5+
class Synchronizer:
6+
def __init__(self):
7+
self.net_manager = None
8+
self.serial_manager = None
9+
self.sensor_manager = None
10+
self.net_ip = None
11+
self.net_port = None
12+
self.serial_dev = None
13+
self.serial_baud_rate = None
14+
self._lock = threading.Lock()
15+
16+
logging.info("\n"
17+
" ▗▄▄▄▖▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖▗▄▄▄▖▗▄▄▄▖▗▄▄▄▖ ▗▄▄▖▗▄▄▄▖▗▖ ▗▖ ▗▄▄▖▗▄▄▄▖\n"
18+
" █ ▐▛▚▖▐▌▐▌ █ ▐▛▚▖▐▌ █ █ ▐▌ ▐▌ ▐▌ ▐▛▚▖▐▌▐▌ ▐▌ \n"
19+
" █ ▐▌ ▝▜▌▐▛▀▀▘ █ ▐▌ ▝▜▌ █ █ ▐▛▀▀▘ ▝▀▚▖▐▛▀▀▘▐▌ ▝▜▌ ▝▀▚▖▐▛▀▀▘\n"
20+
" ▗▄█▄▖▐▌ ▐▌▐▌ ▗▄█▄▖▐▌ ▐▌▗▄█▄▖ █ ▐▙▄▄▖▗▄▄▞▘▐▙▄▄▖▐▌ ▐▌▗▄▄▞▘▐▙▄▄▖")
21+
22+
def set_log_path(self, path):
23+
logging.basicConfig(filename=path, level=logging.INFO,
24+
format='[%(asctime)s] %(levelname)s: %(message)s')
25+
26+
def set_net_link(self, net_ip, net_port):
27+
self.net_ip = net_ip
28+
self.net_port = net_port
29+
from infinite_sense_core.net import NetManager
30+
self.net_manager = NetManager(net_ip, net_port)
31+
32+
def set_usb_link(self, serial_dev, serial_baud_rate):
33+
self.serial_dev = serial_dev
34+
self.serial_baud_rate = serial_baud_rate
35+
from usb import UsbManager
36+
self.serial_manager = UsbManager(serial_dev, serial_baud_rate)
37+
self.net_manager = None
38+
39+
def use_sensor(self, sensor):
40+
self.sensor_manager = sensor
41+
42+
def start(self):
43+
with self._lock:
44+
try:
45+
if self.net_manager:
46+
self.net_manager.start()
47+
if self.serial_manager:
48+
self.serial_manager.start()
49+
if self.sensor_manager:
50+
time.sleep(2)
51+
self.sensor_manager.initialization()
52+
self.sensor_manager.start()
53+
logging.info("Synchronizer Started.")
54+
except Exception as e:
55+
logging.error(f"Synchronizer start exception: {e}")
56+
57+
def stop(self):
58+
with self._lock:
59+
try:
60+
if self.net_manager:
61+
self.net_manager.stop()
62+
if self.serial_manager:
63+
self.serial_manager.stop()
64+
if self.sensor_manager:
65+
self.sensor_manager.stop()
66+
logging.info("Synchronizer Stopped.")
67+
except Exception as e:
68+
logging.error(f"Synchronizer stop exception: {e}")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import zmq
2+
import threading
3+
4+
class Messenger:
5+
_instance = None
6+
def __init__(self, endpoint="tcp://127.0.0.1:4565"):
7+
self.endpoint = endpoint
8+
self.context = zmq.Context()
9+
self.publisher = self.context.socket(zmq.PUB)
10+
self.sub_threads = []
11+
self.lock = threading.Lock()
12+
13+
try:
14+
self.publisher.bind(self.endpoint)
15+
print(f"[INFO] Link Net: {self.endpoint}")
16+
except zmq.ZMQError as e:
17+
print(f"[ERROR] Net initialization error: {e}")
18+
self.cleanup()
19+
Messenger._instance = self
20+
21+
@classmethod
22+
def get_instance(cls):
23+
if cls._instance is None:
24+
cls._instance = Messenger()
25+
return cls._instance
26+
27+
def cleanup(self):
28+
try:
29+
self.publisher.close()
30+
self.context.term()
31+
for thread in self.sub_threads:
32+
if thread.is_alive():
33+
thread.join()
34+
print("[INFO] Messenger clean up successful")
35+
except Exception:
36+
print("[ERROR] Messenger clean up error")
37+
38+
def pub(self, topic: str, metadata: str):
39+
try:
40+
with self.lock:
41+
self.publisher.send_string(topic, zmq.SNDMORE)
42+
self.publisher.send_string(metadata, zmq.DONTWAIT)
43+
except zmq.ZMQError as e:
44+
print(f"[ERROR] Publish error: {e}")
45+
46+
def pub_struct(self, topic: str, data: bytes):
47+
try:
48+
with self.lock:
49+
self.publisher.send_string(+topic, zmq.SNDMORE)
50+
self.publisher.send(data, zmq.DONTWAIT)
51+
except zmq.ZMQError as e:
52+
print(f"[ERROR] Publish struct error: {e}")
53+
54+
def sub(self, topic: str, callback):
55+
def run():
56+
try:
57+
subscriber = self.context.socket(zmq.SUB)
58+
subscriber.connect(self.endpoint)
59+
subscriber.setsockopt_string(zmq.SUBSCRIBE, topic)
60+
while True:
61+
topic_msg = subscriber.recv_string()
62+
data_msg = subscriber.recv_string()
63+
if topic_msg != topic:
64+
continue
65+
callback(data_msg)
66+
except zmq.ZMQError as e:
67+
print(f"[ERROR] Exception in Sub thread for topic [{topic}]: {e}")
68+
69+
thread = threading.Thread(target=run, daemon=True)
70+
thread.start()
71+
self.sub_threads.append(thread)
72+
73+
def sub_struct(self, topic: str, callback):
74+
def run():
75+
try:
76+
context = zmq.Context()
77+
subscriber = context.socket(zmq.SUB)
78+
subscriber.connect(self.endpoint)
79+
subscriber.setsockopt_string(zmq.SUBSCRIBE, topic)
80+
while True:
81+
topic_msg = subscriber.recv_string()
82+
data_msg = subscriber.recv()
83+
if topic_msg != topic:
84+
continue
85+
callback(data_msg)
86+
except zmq.ZMQError as e:
87+
print(f"[ERROR] Exception in SubStruct for topic [{topic}]: {e}")
88+
89+
thread = threading.Thread(target=run, daemon=True)
90+
thread.start()
91+
self.sub_threads.append(thread)

python/infinite_sense_core/net.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import json
2+
import socket
3+
import struct
4+
import threading
5+
import time
6+
7+
from infinite_sense_core.data import process_trigger_data, process_imu_data, process_gps_data, process_log_data
8+
from .times import precise_sleep
9+
from .ptp import Ptp
10+
11+
class NetManager:
12+
def __init__(self, target_ip: str, port: int):
13+
self.target_ip = target_ip
14+
self.port = port
15+
self.started = False
16+
17+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
18+
self.sock.setblocking(False)
19+
20+
curr_time = int(time.time() * 1_000_000)
21+
self.sock.sendto(struct.pack("<Q", curr_time), (self.target_ip, self.port))
22+
23+
self.ptp = Ptp()
24+
self.ptp.set_net_ptr(self.sock, self.target_ip, self.port)
25+
26+
self.rx_thread = None
27+
self.tx_thread = None
28+
29+
def start(self):
30+
if self.started:
31+
return
32+
self.started = True
33+
self.rx_thread = threading.Thread(target=self.receive, daemon=True)
34+
self.tx_thread = threading.Thread(target=self.timestamp_synchronization, daemon=True)
35+
self.rx_thread.start()
36+
self.tx_thread.start()
37+
print("[NetManager] Started")
38+
39+
def stop(self):
40+
if not self.started:
41+
return
42+
self.started = False
43+
print("[NetManager] Stopped")
44+
45+
def receive(self):
46+
while self.started:
47+
try:
48+
data, addr = self.sock.recvfrom(65536)
49+
except BlockingIOError:
50+
precise_sleep(0.001)
51+
continue
52+
53+
try:
54+
recv_data = data.decode("utf-8")
55+
if not recv_data:
56+
continue
57+
json_data = json.loads(recv_data)
58+
except Exception as e:
59+
print(f"[WARN] JSON parse error: {e}")
60+
continue
61+
# print(json_data)
62+
self.ptp.receive_ptp_data(json_data)
63+
process_trigger_data(json_data)
64+
process_imu_data(json_data)
65+
process_gps_data(json_data)
66+
process_log_data(json_data)
67+
68+
def timestamp_synchronization(self):
69+
while self.started:
70+
try:
71+
self.ptp.send_ptp_data()
72+
precise_sleep(0.01)
73+
except Exception as e:
74+
print(f"[ERROR] Timestamp sync error: {e}")
75+
76+
77+
if __name__ == "__main__":
78+
nm = NetManager("192.168.1.188", 8888)
79+
nm.start()
80+
try:
81+
while True:
82+
time.sleep(1)
83+
except KeyboardInterrupt:
84+
nm.stop()

0 commit comments

Comments
 (0)