fix: 修复打砖块游戏碰撞穿透bug,添加渐进提速机制
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
REM ── JE-Skin DevKit: 打包 Python gRPC server 为 exe ──
|
||||
REM 前提: pip install pyinstaller grpcio grpcio-tools openpyxl
|
||||
REM 前提: pip install pyinstaller grpcio grpcio-tools numpy openpyxl
|
||||
|
||||
echo [1/3] Generating gRPC stubs...
|
||||
python -m grpc_tools.protoc ^
|
||||
@@ -21,4 +21,4 @@ pyinstaller ^
|
||||
|
||||
echo [3/3] Done!
|
||||
echo Output: dist/je-skin-devkit-server.exe
|
||||
pause
|
||||
pause
|
||||
|
||||
@@ -5,8 +5,8 @@ a = Analysis(
|
||||
['sensor_server.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=['grpc', 'openpyxl'],
|
||||
datas=[('sensor_stream_pb2.py', '.'), ('sensor_stream_pb2_grpc.py', '.')],
|
||||
hiddenimports=['grpc', 'openpyxl', 'numpy'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
|
||||
@@ -234,30 +234,63 @@ class SensorPushServicer(sensor_stream_pb2_grpc.SensorPushServicer):
|
||||
def __init__(self):
|
||||
self.frame_count = 0
|
||||
self.last_report_time = time.time()
|
||||
self.last_angle = None
|
||||
|
||||
def Upload(self, request_iterator, context):
|
||||
print("[SensorPush] Client connected, waiting for frames...")
|
||||
reset_baseline()
|
||||
self.last_angle = None
|
||||
|
||||
for frame in request_iterator:
|
||||
self.frame_count += 1
|
||||
angle = 0.0
|
||||
ok = True
|
||||
message = "OK"
|
||||
if len(frame.matrix) == SENSOR_ROWS * SENSOR_COLS:
|
||||
try:
|
||||
angle = get_pzt_angle(frame.matrix)
|
||||
self.last_angle = angle
|
||||
if self.frame_count <= 10 or self.frame_count % 30 == 0:
|
||||
print(
|
||||
f"[SensorPush] PZT angle frame #{frame.seq} "
|
||||
f"dts={frame.dts_ms} angle={angle:.2f}"
|
||||
)
|
||||
except Exception as e:
|
||||
ok = False
|
||||
message = str(e)
|
||||
print(f"[SensorPush] PZT compute error on frame #{frame.seq}: {e}")
|
||||
else:
|
||||
ok = False
|
||||
message = f"Invalid matrix length: {len(frame.matrix)}"
|
||||
|
||||
yield sensor_stream_pb2.PztAngleResponse(
|
||||
seq=frame.seq,
|
||||
timestamp_ms=frame.timestamp_ms,
|
||||
angle=angle,
|
||||
dts_ms=frame.dts_ms,
|
||||
ok=ok,
|
||||
message=message,
|
||||
)
|
||||
|
||||
if self.frame_count % 100 == 0:
|
||||
now = time.time()
|
||||
elapsed = now - self.last_report_time
|
||||
fps = 100 / elapsed if elapsed > 0 else 0
|
||||
self.last_report_time = now
|
||||
angle_text = (
|
||||
f"{self.last_angle:.2f}"
|
||||
if self.last_angle is not None
|
||||
else "n/a"
|
||||
)
|
||||
print(
|
||||
f"[SensorPush] Frame #{frame.seq} | "
|
||||
f"{frame.rows}x{frame.cols} | "
|
||||
f"angle={angle_text} | "
|
||||
f"force={frame.resultant_force:.1f} | "
|
||||
f"total={self.frame_count} | ~{fps:.1f} fps"
|
||||
)
|
||||
|
||||
print(f"[SensorPush] Stream ended. Total: {self.frame_count}")
|
||||
return sensor_stream_pb2.UploadResponse(
|
||||
ok=True,
|
||||
frames_received=self.frame_count,
|
||||
message=f"Processed {self.frame_count} frames",
|
||||
)
|
||||
|
||||
|
||||
class ExportProcessorServicer(sensor_stream_pb2_grpc.ExportProcessorServicer):
|
||||
@@ -316,8 +349,119 @@ def serve(port: int):
|
||||
server.wait_for_termination()
|
||||
|
||||
|
||||
import numpy as np
|
||||
import threading
|
||||
|
||||
# ===================== 算法参数=====================
|
||||
TOTAL_PRESSURE_LOW_THRESHOLD = 500
|
||||
COP_STABILITY_FRAMES_REQUIRED = 5
|
||||
SENSOR_ROWS = 12
|
||||
SENSOR_COLS = 7
|
||||
|
||||
# ===================== 线程安全全局状态 =====================
|
||||
first_frame = None
|
||||
first_frame_lock = threading.Lock()
|
||||
|
||||
first_contact_CoP_x = None
|
||||
first_contact_CoP_y = None
|
||||
contact_initialized = False
|
||||
|
||||
total_pressure_low_counter = 0
|
||||
|
||||
# ===================== 基线减除 =====================
|
||||
def subtract_baseline(current_frame):
|
||||
global first_frame
|
||||
current_frame = np.array(current_frame, dtype=np.float32).flatten()
|
||||
|
||||
with first_frame_lock:
|
||||
if first_frame is None:
|
||||
first_frame = current_frame.copy()
|
||||
|
||||
diff = current_frame - first_frame
|
||||
return np.clip(diff, 0, None)
|
||||
|
||||
# ===================== 重置CoP状态 =====================
|
||||
def reset_cop_state():
|
||||
global first_contact_CoP_x, first_contact_CoP_y, contact_initialized
|
||||
global total_pressure_low_counter
|
||||
|
||||
first_contact_CoP_x = None
|
||||
first_contact_CoP_y = None
|
||||
contact_initialized = False
|
||||
total_pressure_low_counter = 0
|
||||
|
||||
# ===================== CoP压力中心计算 =====================
|
||||
def compute_pressure_direction(baseline_subtracted_frame):
|
||||
global first_contact_CoP_x, first_contact_CoP_y, contact_initialized
|
||||
global total_pressure_low_counter
|
||||
|
||||
rows, cols = SENSOR_ROWS, SENSOR_COLS
|
||||
frame_flat = np.asarray(baseline_subtracted_frame, dtype=np.float32).flatten()
|
||||
frame2d = frame_flat.reshape(rows, cols)
|
||||
|
||||
total_pressure = np.sum(frame2d)
|
||||
if total_pressure < TOTAL_PRESSURE_LOW_THRESHOLD:
|
||||
total_pressure_low_counter += 1
|
||||
else:
|
||||
total_pressure_low_counter = 0
|
||||
|
||||
if total_pressure_low_counter >= COP_STABILITY_FRAMES_REQUIRED:
|
||||
reset_cop_state()
|
||||
return 0.0, 0.0
|
||||
|
||||
if total_pressure == 0:
|
||||
return 0.0, 0.0
|
||||
|
||||
x_grid = np.tile(np.arange(cols), (rows, 1))
|
||||
y_grid = np.repeat(np.arange(rows), cols).reshape(rows, cols)
|
||||
cop_x = np.sum(frame2d * x_grid) / total_pressure
|
||||
cop_y = np.sum(frame2d * y_grid) / total_pressure
|
||||
|
||||
delta_CoP_x = 0.0
|
||||
delta_CoP_y = 0.0
|
||||
|
||||
if not contact_initialized:
|
||||
first_contact_CoP_x = cop_x
|
||||
first_contact_CoP_y = cop_y
|
||||
contact_initialized = True
|
||||
else:
|
||||
delta_CoP_x = cop_x - first_contact_CoP_x
|
||||
delta_CoP_y = cop_y - first_contact_CoP_y
|
||||
|
||||
return delta_CoP_x, delta_CoP_y
|
||||
|
||||
# ===================== 角度计算核心 =====================
|
||||
def compute_vector_angle(x: float, y: float) -> tuple[float, float]:
|
||||
epsilon = 1e-8
|
||||
mag = np.hypot(x, y)
|
||||
angle = np.degrees(np.arctan2(y, x + epsilon))
|
||||
if angle < 0:
|
||||
angle += 360
|
||||
return angle, mag
|
||||
|
||||
def compute_PZT_angle(Px: float, Py: float) -> tuple[float, float]:
|
||||
return compute_vector_angle(Px, -Py)
|
||||
|
||||
# ===================== 核心入口函数 =====================
|
||||
def get_pzt_angle(adc_data):
|
||||
if len(adc_data) != 84:
|
||||
raise ValueError("ADC数据长度必须为84")
|
||||
baseline_subtracted = subtract_baseline(adc_data)
|
||||
dx, dy = compute_pressure_direction(baseline_subtracted)
|
||||
pzt_angle, _ = compute_PZT_angle(dx, dy)
|
||||
|
||||
return pzt_angle
|
||||
|
||||
# ===================== 重置基线(校准用) =====================
|
||||
def reset_baseline():
|
||||
global first_frame
|
||||
with first_frame_lock:
|
||||
first_frame = None
|
||||
reset_cop_state()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="JE-Skin DevKit gRPC Server")
|
||||
parser.add_argument("--port", type=int, default=50051, help="gRPC listen port (default: 50051)")
|
||||
args = parser.parse_args()
|
||||
serve(args.port)
|
||||
serve(args.port)
|
||||
|
||||
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13sensor_stream.proto\x12\rsensor_stream\"\x85\x01\n\x0bSensorFrame\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\x0c\n\x04rows\x18\x03 \x01(\r\x12\x0c\n\x04\x63ols\x18\x04 \x01(\r\x12\x0e\n\x06matrix\x18\x05 \x03(\r\x12\x17\n\x0fresultant_force\x18\x06 \x01(\x01\x12\x0e\n\x06\x64ts_ms\x18\x07 \x01(\r\"F\n\x0eUploadResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x17\n\x0f\x66rames_received\x18\x02 \x01(\x04\x12\x0f\n\x07message\x18\x03 \x01(\t\"8\n\x0eProcessRequest\x12\x10\n\x08\x63sv_path\x18\x01 \x01(\t\x12\x14\n\x0csave_as_xlsx\x18\x02 \x01(\x08\"\xa6\x01\n\x0fProcessResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x13\n\x0boutput_path\x18\x02 \x01(\t\x12\x13\n\x0bgroups_used\x18\x03 \x01(\r\x12\x12\n\nmean_value\x18\x04 \x01(\x01\x12\x11\n\tthreshold\x18\x05 \x01(\x01\x12\x12\n\nrows_total\x18\x06 \x01(\r\x12\x11\n\trows_kept\x18\x07 \x01(\r\x12\x0f\n\x07message\x18\x08 \x01(\t2S\n\nSensorPush\x12\x45\n\x06Upload\x12\x1a.sensor_stream.SensorFrame\x1a\x1d.sensor_stream.UploadResponse(\x01\x32_\n\x0f\x45xportProcessor\x12L\n\x0bProcessFile\x12\x1d.sensor_stream.ProcessRequest\x1a\x1e.sensor_stream.ProcessResponseb\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13sensor_stream.proto\x12\rsensor_stream\"\x85\x01\n\x0bSensorFrame\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\x0c\n\x04rows\x18\x03 \x01(\r\x12\x0c\n\x04\x63ols\x18\x04 \x01(\r\x12\x0e\n\x06matrix\x18\x05 \x03(\r\x12\x17\n\x0fresultant_force\x18\x06 \x01(\x01\x12\x0e\n\x06\x64ts_ms\x18\x07 \x01(\r\"q\n\x10PztAngleResponse\x12\x0b\n\x03seq\x18\x01 \x01(\x04\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x04\x12\r\n\x05\x61ngle\x18\x03 \x01(\x02\x12\x0e\n\x06\x64ts_ms\x18\x04 \x01(\r\x12\n\n\x02ok\x18\x05 \x01(\x08\x12\x0f\n\x07message\x18\x06 \x01(\t\"8\n\x0eProcessRequest\x12\x10\n\x08\x63sv_path\x18\x01 \x01(\t\x12\x14\n\x0csave_as_xlsx\x18\x02 \x01(\x08\"\xa6\x01\n\x0fProcessResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x13\n\x0boutput_path\x18\x02 \x01(\t\x12\x13\n\x0bgroups_used\x18\x03 \x01(\r\x12\x12\n\nmean_value\x18\x04 \x01(\x01\x12\x11\n\tthreshold\x18\x05 \x01(\x01\x12\x12\n\nrows_total\x18\x06 \x01(\r\x12\x11\n\trows_kept\x18\x07 \x01(\r\x12\x0f\n\x07message\x18\x08 \x01(\t2W\n\nSensorPush\x12I\n\x06Upload\x12\x1a.sensor_stream.SensorFrame\x1a\x1f.sensor_stream.PztAngleResponse(\x01\x30\x01\x32_\n\x0f\x45xportProcessor\x12L\n\x0bProcessFile\x12\x1d.sensor_stream.ProcessRequest\x1a\x1e.sensor_stream.ProcessResponseb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -33,14 +33,14 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
||||
DESCRIPTOR._loaded_options = None
|
||||
_globals['_SENSORFRAME']._serialized_start=39
|
||||
_globals['_SENSORFRAME']._serialized_end=172
|
||||
_globals['_UPLOADRESPONSE']._serialized_start=174
|
||||
_globals['_UPLOADRESPONSE']._serialized_end=244
|
||||
_globals['_PROCESSREQUEST']._serialized_start=246
|
||||
_globals['_PROCESSREQUEST']._serialized_end=302
|
||||
_globals['_PROCESSRESPONSE']._serialized_start=305
|
||||
_globals['_PROCESSRESPONSE']._serialized_end=471
|
||||
_globals['_SENSORPUSH']._serialized_start=473
|
||||
_globals['_SENSORPUSH']._serialized_end=556
|
||||
_globals['_EXPORTPROCESSOR']._serialized_start=558
|
||||
_globals['_EXPORTPROCESSOR']._serialized_end=653
|
||||
_globals['_PZTANGLERESPONSE']._serialized_start=174
|
||||
_globals['_PZTANGLERESPONSE']._serialized_end=287
|
||||
_globals['_PROCESSREQUEST']._serialized_start=289
|
||||
_globals['_PROCESSREQUEST']._serialized_end=345
|
||||
_globals['_PROCESSRESPONSE']._serialized_start=348
|
||||
_globals['_PROCESSRESPONSE']._serialized_end=514
|
||||
_globals['_SENSORPUSH']._serialized_start=516
|
||||
_globals['_SENSORPUSH']._serialized_end=603
|
||||
_globals['_EXPORTPROCESSOR']._serialized_start=605
|
||||
_globals['_EXPORTPROCESSOR']._serialized_end=700
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
@@ -26,8 +26,7 @@ if _version_not_supported:
|
||||
|
||||
|
||||
class SensorPushStub(object):
|
||||
"""传感器数据推送服务 —— Rust 端作为 gRPC client 推送实时帧
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
@@ -35,16 +34,15 @@ class SensorPushStub(object):
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.Upload = channel.stream_unary(
|
||||
self.Upload = channel.stream_stream(
|
||||
'/sensor_stream.SensorPush/Upload',
|
||||
request_serializer=sensor__stream__pb2.SensorFrame.SerializeToString,
|
||||
response_deserializer=sensor__stream__pb2.UploadResponse.FromString,
|
||||
response_deserializer=sensor__stream__pb2.PztAngleResponse.FromString,
|
||||
_registered_method=True)
|
||||
|
||||
|
||||
class SensorPushServicer(object):
|
||||
"""传感器数据推送服务 —— Rust 端作为 gRPC client 推送实时帧
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def Upload(self, request_iterator, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
@@ -55,10 +53,10 @@ class SensorPushServicer(object):
|
||||
|
||||
def add_SensorPushServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
'Upload': grpc.stream_unary_rpc_method_handler(
|
||||
'Upload': grpc.stream_stream_rpc_method_handler(
|
||||
servicer.Upload,
|
||||
request_deserializer=sensor__stream__pb2.SensorFrame.FromString,
|
||||
response_serializer=sensor__stream__pb2.UploadResponse.SerializeToString,
|
||||
response_serializer=sensor__stream__pb2.PztAngleResponse.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
@@ -69,8 +67,7 @@ def add_SensorPushServicer_to_server(servicer, server):
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class SensorPush(object):
|
||||
"""传感器数据推送服务 —— Rust 端作为 gRPC client 推送实时帧
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def Upload(request_iterator,
|
||||
@@ -83,12 +80,12 @@ class SensorPush(object):
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.stream_unary(
|
||||
return grpc.experimental.stream_stream(
|
||||
request_iterator,
|
||||
target,
|
||||
'/sensor_stream.SensorPush/Upload',
|
||||
sensor__stream__pb2.SensorFrame.SerializeToString,
|
||||
sensor__stream__pb2.UploadResponse.FromString,
|
||||
sensor__stream__pb2.PztAngleResponse.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
@@ -101,8 +98,7 @@ class SensorPush(object):
|
||||
|
||||
|
||||
class ExportProcessorStub(object):
|
||||
"""导出后处理服务 —— Rust 导出 CSV 后将路径发给 Python 做梯度过滤
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
@@ -118,8 +114,7 @@ class ExportProcessorStub(object):
|
||||
|
||||
|
||||
class ExportProcessorServicer(object):
|
||||
"""导出后处理服务 —— Rust 导出 CSV 后将路径发给 Python 做梯度过滤
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def ProcessFile(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
@@ -144,8 +139,7 @@ def add_ExportProcessorServicer_to_server(servicer, server):
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class ExportProcessor(object):
|
||||
"""导出后处理服务 —— Rust 导出 CSV 后将路径发给 Python 做梯度过滤
|
||||
"""
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def ProcessFile(request,
|
||||
|
||||
Reference in New Issue
Block a user