Compare commits
5 Commits
b31bb18dfe
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c27e250fad | |||
| 66c0b71b89 | |||
| 5c7b73b7a4 | |||
| 9690971f43 | |||
| 5a5f103ef6 |
@@ -157,8 +157,8 @@ class Decoder_main(threading.Thread):
|
|||||||
# self.blink_b, self.blink_a = signal.butter(4, [self.l_freq / (self.device_info['sample_rate'] / 2), self.h_freq / (self.device_info['sample_rate'] / 2)], btype='band')
|
# self.blink_b, self.blink_a = signal.butter(4, [self.l_freq / (self.device_info['sample_rate'] / 2), self.h_freq / (self.device_info['sample_rate'] / 2)], btype='band')
|
||||||
|
|
||||||
def parameter_init(self,bandPass_low,bandPass_high):
|
def parameter_init(self,bandPass_low,bandPass_high):
|
||||||
self.interval_epoch = [int(i * self.device_info['sample_rate']) for i in self.interval_epoch] # epoch截取信息
|
self.interval_epoch = [int(i * self.device_info['sample_rate']) for i in self.interval_epoch] # epoch截取信息 ssmvep [50, 550]
|
||||||
self.train_epoch = [int(self.interval_epoch[0]), int(self.interval_epoch[1] + 0.1 * self.device_info['sample_rate'])] # 训练样本epoch
|
self.train_epoch = [int(self.interval_epoch[0]), int(self.interval_epoch[1] + 0.1 * self.device_info['sample_rate'])] # 训练样本epoch ssmevep [50, 575]
|
||||||
self.trainData = [] #训练数据
|
self.trainData = [] #训练数据
|
||||||
self.trainLabel = [] #训练标签
|
self.trainLabel = [] #训练标签
|
||||||
self.plotData = [] #报告分析数据
|
self.plotData = [] #报告分析数据
|
||||||
@@ -236,7 +236,7 @@ class Decoder_main(threading.Thread):
|
|||||||
if self.zmqServer.open_Impedance: # 阻抗检测状态不解码
|
if self.zmqServer.open_Impedance: # 阻抗检测状态不解码
|
||||||
return
|
return
|
||||||
data = self.zmqServer.paradigmBuffer.getDataViaSSVEP(50)
|
data = self.zmqServer.paradigmBuffer.getDataViaSSVEP(50)
|
||||||
# algo_log(f"SSVEP取出的:{data.shape}, data = {data[:20]}", level="DEBUG")
|
# algo_log(f"SSVEP取出的:{data.shape}, data = {data[:, :10]}", level="DEBUG")
|
||||||
data = data[:self.n_chan, :]
|
data = data[:self.n_chan, :]
|
||||||
if self.decodingSteps == 1 and hasattr(self,'dw'): # 开始预热
|
if self.decodingSteps == 1 and hasattr(self,'dw'): # 开始预热
|
||||||
self.dw.onlineInit() # 刺激闪烁的第1s重置 --在线数据采集时
|
self.dw.onlineInit() # 刺激闪烁的第1s重置 --在线数据采集时
|
||||||
@@ -289,6 +289,7 @@ class Decoder_main(threading.Thread):
|
|||||||
and self.trainLabel.count(self.currentLabel) < self.single_train:
|
and self.trainLabel.count(self.currentLabel) < self.single_train:
|
||||||
self.trainData.append(trainTrial)
|
self.trainData.append(trainTrial)
|
||||||
self.trainLabel.append(self.currentLabel)
|
self.trainLabel.append(self.currentLabel)
|
||||||
|
algo_log(f"SSMVEP训练集:{np.shape(self.trainData)}", level="DEBUG")
|
||||||
else:
|
else:
|
||||||
time.sleep(0.0001)
|
time.sleep(0.0001)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -35,3 +35,9 @@ python upperHost_stimmock/MI_headless.py
|
|||||||
# TODO
|
# TODO
|
||||||
1. mvep是否要把list freq 开放到config
|
1. mvep是否要把list freq 开放到config
|
||||||
2. 滤波器参数 放到config文件
|
2. 滤波器参数 放到config文件
|
||||||
|
|
||||||
|
# debug log
|
||||||
|
## MI
|
||||||
|
Epoch采集完成|收到命令: {'method': 'train'|取出的
|
||||||
|
|
||||||
|
收到命令: {'method': 'train'|收到命令: {'method': 'train'|收到命令: {'method': 'predict'|事件检测到
|
||||||
@@ -152,9 +152,7 @@ class zmqServer(threading.Thread):
|
|||||||
msg = {'method': method, 'params': params}
|
msg = {'method': method, 'params': params}
|
||||||
msg_bytes = json.dumps(msg).encode('utf-8')
|
msg_bytes = json.dumps(msg).encode('utf-8')
|
||||||
|
|
||||||
if msg['method'] == 'beta_psd':
|
if msg['method'] != 'beta_psd':
|
||||||
algo_log(f"发送命令结果: {msg}", level="DEBUG", record_once=True)
|
|
||||||
else:
|
|
||||||
algo_log(f"发送命令结果: {msg}", level="DEBUG")
|
algo_log(f"发送命令结果: {msg}", level="DEBUG")
|
||||||
|
|
||||||
# 广播到所有命令客户端
|
# 广播到所有命令客户端
|
||||||
@@ -276,6 +274,22 @@ class zmqServer(threading.Thread):
|
|||||||
elif params == 2: #停止解码
|
elif params == 2: #停止解码
|
||||||
self.IsExitApp = True
|
self.IsExitApp = True
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
|
resp = {
|
||||||
|
"method": "predict_response",
|
||||||
|
"params": {
|
||||||
|
"code": 200,
|
||||||
|
"message": "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp_bytes = json.dumps(resp, ensure_ascii=False).encode("utf-8")
|
||||||
|
self.cmd_socket.send_multipart([ident, b"", resp_bytes])
|
||||||
|
algo_log(f"predict 命令已即时回复客户端 {ident}", level="DEBUG")
|
||||||
|
except Exception as e:
|
||||||
|
algo_log(f"predict 命令回复失败: {e}", level="ERROR")
|
||||||
|
return
|
||||||
|
|
||||||
elif method == "rest":
|
elif method == "rest":
|
||||||
self.state_mode = 'rest'
|
self.state_mode = 'rest'
|
||||||
elif method == "impedance":
|
elif method == "impedance":
|
||||||
@@ -359,8 +373,8 @@ class zmqServer(threading.Thread):
|
|||||||
def detect_event(self, samples):
|
def detect_event(self, samples):
|
||||||
self.pack_contain_event = False
|
self.pack_contain_event = False
|
||||||
# 第65通道为事件通道
|
# 第65通道为事件通道
|
||||||
event = int(samples[-2][0])
|
events = np.array(samples[-2], dtype=np.int32).tolist()
|
||||||
# for idx, event in enumerate(events):
|
for idx, event in enumerate(events):
|
||||||
if event in self.events:
|
if event in self.events:
|
||||||
new_key = "".join(
|
new_key = "".join(
|
||||||
[
|
[
|
||||||
@@ -374,8 +388,8 @@ class zmqServer(threading.Thread):
|
|||||||
self.count_events[new_key] = self.latency + 1
|
self.count_events[new_key] = self.latency + 1
|
||||||
else:
|
else:
|
||||||
self.count_events[new_key] = self.train_latency + 1
|
self.count_events[new_key] = self.train_latency + 1
|
||||||
self.event_inner_idx = self.device_info['frame_points'] - 1
|
self.event_inner_idx = idx
|
||||||
# algo_log(f"事件检测到: {event},索引: {idx}", level="DEBUG")
|
algo_log(f"事件检测到: {events},索引: {idx}", level="DEBUG")
|
||||||
self.pack_contain_event = True
|
self.pack_contain_event = True
|
||||||
|
|
||||||
# 倒计时并清理过期事件
|
# 倒计时并清理过期事件
|
||||||
|
|||||||
17
config.ini
17
config.ini
@@ -18,12 +18,23 @@ Upper_Port = 8088
|
|||||||
Decoder_Host = 127.0.0.1
|
Decoder_Host = 127.0.0.1
|
||||||
Decoder_Port = 8099
|
Decoder_Port = 8099
|
||||||
Serial_port = COM44
|
Serial_port = COM44
|
||||||
algo_log_path = d:/Program Files/64chn_Decoder/logs
|
|
||||||
algo_log_level = DEBUG
|
|
||||||
console_output = 1
|
|
||||||
save_train_data = 0
|
save_train_data = 0
|
||||||
zmqServer_host = 127.0.0.1
|
zmqServer_host = 127.0.0.1
|
||||||
|
|
||||||
|
[algo_log]
|
||||||
|
# ========== 文件日志配置 ==========
|
||||||
|
file_log_enable = true
|
||||||
|
file_log_level = DEBUG
|
||||||
|
log_path = exe
|
||||||
|
retention_days = 3
|
||||||
|
|
||||||
|
# ========== 控制台/黑框配置 ==========
|
||||||
|
console_enable = true
|
||||||
|
console_show_window = true
|
||||||
|
console_log_level = DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; 64 导设备配置
|
; 64 导设备配置
|
||||||
[device_type_1]
|
[device_type_1]
|
||||||
sample_rate = 250
|
sample_rate = 250
|
||||||
|
|||||||
154
logs/log.py
154
logs/log.py
@@ -1,122 +1,172 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import inspect
|
import inspect
|
||||||
|
try:
|
||||||
|
import win32gui
|
||||||
|
import win32con
|
||||||
|
WIN32_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
WIN32_AVAILABLE = False
|
||||||
|
|
||||||
from PubLibrary.InifileHelper import IniRead
|
from PubLibrary.InifileHelper import IniRead
|
||||||
|
|
||||||
|
# ===================== 新增:获取 EXE 同级目录 =====================
|
||||||
|
def get_app_root():
|
||||||
|
"""获取 runDecoder.exe 所在的真实根目录(兼容 onefile / standalone)"""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# Nuitka / PyInstaller 打包后走这里
|
||||||
|
app_path = sys.executable
|
||||||
|
else:
|
||||||
|
# 本地源码运行时,取当前脚本目录
|
||||||
|
app_path = os.path.abspath(__file__)
|
||||||
|
return os.path.dirname(app_path)
|
||||||
|
|
||||||
# 全局配置
|
# 程序根目录(exe 同级)
|
||||||
console_output = IniRead('system', 'console_output', '1')
|
APP_ROOT = Path(get_app_root())
|
||||||
log_level = IniRead('system', 'algo_log_level', 'INFO')
|
# 日志文件夹名:exe 同级下 logs 目录
|
||||||
|
DEFAULT_LOG_DIR = APP_ROOT / "logs"
|
||||||
|
|
||||||
|
# ===================== 读取 [algo_log] 配置 =====================
|
||||||
|
# 文件日志
|
||||||
|
FILE_LOG_ENABLE = IniRead("algo_log", "file_log_enable", "true").lower() == "true"
|
||||||
|
FILE_LOG_LEVEL = IniRead("algo_log", "file_log_level", "DEBUG").upper()
|
||||||
|
# 优先级:配置文件 > 默认exe同级logs
|
||||||
|
CFG_LOG_PATH = IniRead("algo_log", "log_path", "").strip()
|
||||||
|
if CFG_LOG_PATH == "exe":
|
||||||
|
LOG_DIR = DEFAULT_LOG_DIR
|
||||||
|
else:
|
||||||
|
LOG_DIR = Path(CFG_LOG_PATH)
|
||||||
|
|
||||||
|
LOG_RETENTION_DAYS = int(IniRead("algo_log", "retention_days", 3))
|
||||||
|
|
||||||
|
# 控制台日志 + 黑框控制
|
||||||
|
CONSOLE_ENABLE = IniRead("algo_log", "console_enable", "true").lower() == "true"
|
||||||
|
CONSOLE_SHOW_WINDOW = IniRead("algo_log", "console_show_window", "true").lower() == "true"
|
||||||
|
CONSOLE_LOG_LEVEL = IniRead("algo_log", "console_log_level", "INFO").upper()
|
||||||
|
|
||||||
|
# ===================== 全局常量与缓存 =====================
|
||||||
log_once_cache = set()
|
log_once_cache = set()
|
||||||
logger_cache = {}
|
logger_cache = {}
|
||||||
LOG_RETENTION_DAYS = 3
|
|
||||||
|
|
||||||
LOG_PATH_STR = IniRead('system', 'algo_log_path', "d:/Program Files/64chn_Decoder/logs")
|
|
||||||
LOG_DIR = Path(LOG_PATH_STR)
|
|
||||||
# 自动补全路径分隔符,创建目录(不存在则新建,避免写日志报错)
|
|
||||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
# 如需字符串格式路径
|
|
||||||
LOG_DIR_STR = str(LOG_DIR) + "\\"
|
|
||||||
LOG_FILE_PREFIX = 'algo_log_'
|
LOG_FILE_PREFIX = 'algo_log_'
|
||||||
|
# 确保日志目录存在
|
||||||
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
LOG_DIR_STR = str(LOG_DIR) + "\\"
|
||||||
|
|
||||||
# 日志格式:时间 - 日志器名 - 级别 - 文件名:行号 - 函数名 - 日志内容
|
# 日志格式
|
||||||
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||||
|
|
||||||
|
# 日志级别映射
|
||||||
|
LEVEL_MAP = {
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"FATAL": logging.FATAL
|
||||||
|
}
|
||||||
|
FILE_LOG_LEVEL_INT = LEVEL_MAP.get(FILE_LOG_LEVEL, logging.INFO)
|
||||||
|
CONSOLE_LOG_LEVEL_INT = LEVEL_MAP.get(CONSOLE_LOG_LEVEL, logging.INFO)
|
||||||
|
|
||||||
def clean_old_logs():
|
# ===================== Windows 控制台黑框显示/隐藏 =====================
|
||||||
"""清理超过指定天数的旧日志文件"""
|
def control_console_window():
|
||||||
|
if not sys.platform.startswith("win") or not WIN32_AVAILABLE:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(LOG_DIR):
|
hwnd = win32gui.GetForegroundWindow()
|
||||||
|
if CONSOLE_SHOW_WINDOW:
|
||||||
|
win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
|
||||||
|
else:
|
||||||
|
win32gui.ShowWindow(hwnd, win32con.SW_HIDE)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
control_console_window()
|
||||||
|
|
||||||
|
# ===================== 清理过期日志 =====================
|
||||||
|
def clean_old_logs():
|
||||||
|
try:
|
||||||
|
if not LOG_DIR.exists():
|
||||||
return
|
return
|
||||||
expire_date = datetime.now() - timedelta(days=LOG_RETENTION_DAYS)
|
expire_date = datetime.now() - timedelta(days=LOG_RETENTION_DAYS)
|
||||||
for filename in os.listdir(LOG_DIR):
|
for filename in os.listdir(LOG_DIR):
|
||||||
if not filename.startswith(LOG_FILE_PREFIX) or not filename.endswith('.log'):
|
if not (filename.startswith(LOG_FILE_PREFIX) and filename.endswith('.log')):
|
||||||
continue
|
continue
|
||||||
date_str = filename[len(LOG_FILE_PREFIX):-4]
|
date_str = filename[len(LOG_FILE_PREFIX):-4]
|
||||||
try:
|
try:
|
||||||
file_date = datetime.strptime(date_str, '%Y-%m-%d')
|
file_date = datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
if file_date < expire_date:
|
if file_date < expire_date:
|
||||||
file_path = os.path.join(LOG_DIR, filename)
|
file_path = LOG_DIR / filename
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print(f"清理过期日志: {file_path}")
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(f"清理旧日志异常: {str(e)}")
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ===================== 初始化日志器 =====================
|
||||||
def init_module_logger(logger_name):
|
def init_module_logger(logger_name):
|
||||||
"""初始化日志器 + 清理旧日志"""
|
|
||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
|
||||||
clean_old_logs()
|
|
||||||
|
|
||||||
current_date = datetime.now().strftime("%Y-%m-%d")
|
|
||||||
log_file = os.path.join(LOG_DIR, f"{LOG_FILE_PREFIX}{current_date}.log")
|
|
||||||
|
|
||||||
if logger_name in logger_cache:
|
if logger_name in logger_cache:
|
||||||
return logger_cache[logger_name]
|
return logger_cache[logger_name]
|
||||||
|
|
||||||
|
clean_old_logs()
|
||||||
|
|
||||||
logger = logging.getLogger(logger_name)
|
logger = logging.getLogger(logger_name)
|
||||||
logger.setLevel(log_level)
|
logger.setLevel(logging.DEBUG)
|
||||||
if logger.handlers:
|
if logger.handlers:
|
||||||
logger_cache[logger_name] = logger
|
logger_cache[logger_name] = logger
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
# 文件输出处理器
|
formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT)
|
||||||
|
|
||||||
|
# 文件日志
|
||||||
|
if FILE_LOG_ENABLE:
|
||||||
|
current_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
log_file = LOG_DIR / f"{LOG_FILE_PREFIX}{current_date}.log"
|
||||||
file_handler = RotatingFileHandler(
|
file_handler = RotatingFileHandler(
|
||||||
log_file,
|
log_file,
|
||||||
maxBytes=10 * 1024 * 1024,
|
maxBytes=10 * 1024 * 1024,
|
||||||
backupCount=10,
|
backupCount=10,
|
||||||
encoding='utf-8'
|
encoding='utf-8'
|
||||||
)
|
)
|
||||||
formatter = logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT)
|
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
|
file_handler.setLevel(FILE_LOG_LEVEL_INT)
|
||||||
logger.addHandler(file_handler)
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
# 控制台输出
|
# 控制台日志
|
||||||
if console_output:
|
if CONSOLE_ENABLE:
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
console_handler.setFormatter(formatter)
|
console_handler.setFormatter(formatter)
|
||||||
|
console_handler.setLevel(CONSOLE_LOG_LEVEL_INT)
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
logger_cache[logger_name] = logger
|
logger_cache[logger_name] = logger
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
# ===================== 对外日志入口函数 =====================
|
||||||
def algo_log(content, level="INFO", record_once=False):
|
def algo_log(content, level="INFO", record_once=False):
|
||||||
"""
|
frame = inspect.currentframe()
|
||||||
日志入口函数
|
if frame:
|
||||||
自动记录:调用文件名、代码行号、所在函数
|
frame = frame.f_back.f_back
|
||||||
"""
|
file_name = os.path.basename(frame.f_code.co_filename) if frame else "unknown"
|
||||||
# 回溯栈帧,获取真正调用 algo_log 的代码位置
|
|
||||||
# f_back(1) -> algo_log 自身,f_back(2) -> 业务调用处
|
|
||||||
frame = inspect.currentframe().f_back.f_back
|
|
||||||
if not frame:
|
|
||||||
file_name = "unknown"
|
|
||||||
else:
|
|
||||||
file_name = os.path.basename(frame.f_code.co_filename)
|
|
||||||
|
|
||||||
logger = init_module_logger(file_name)
|
logger = init_module_logger(file_name)
|
||||||
|
|
||||||
# 单次日志去重
|
|
||||||
if record_once:
|
if record_once:
|
||||||
log_key = f"{level.upper()}_{content}"
|
log_key = f"{level.upper()}_{content}"
|
||||||
if log_key in log_once_cache:
|
if log_key in log_once_cache:
|
||||||
return
|
return
|
||||||
log_once_cache.add(log_key)
|
log_once_cache.add(log_key)
|
||||||
|
|
||||||
# 日志级别分发
|
|
||||||
level_upper = level.upper()
|
level_upper = level.upper()
|
||||||
log_map = {
|
log_func_map = {
|
||||||
"DEBUG": logger.debug,
|
"DEBUG": logger.debug,
|
||||||
|
"INFO": logger.info,
|
||||||
"WARNING": logger.warning,
|
"WARNING": logger.warning,
|
||||||
"ERROR": logger.error,
|
"ERROR": logger.error,
|
||||||
"FATAL": logger.fatal,
|
"FATAL": logger.fatal
|
||||||
"INFO": logger.info
|
|
||||||
}
|
}
|
||||||
log_func = log_map.get(level_upper, logger.info)
|
log_func = log_func_map.get(level_upper, logger.info)
|
||||||
log_func(content)
|
log_func(content)
|
||||||
@@ -28,7 +28,6 @@ echo "输出目录:${OUT_DIR}"
|
|||||||
python -m nuitka \
|
python -m nuitka \
|
||||||
--standalone \
|
--standalone \
|
||||||
--msvc=latest \
|
--msvc=latest \
|
||||||
--windows-console-mode=force \
|
|
||||||
--module-parameter=torch-disable-jit=yes \
|
--module-parameter=torch-disable-jit=yes \
|
||||||
--enable-plugin=no-qt \
|
--enable-plugin=no-qt \
|
||||||
--include-package=numpy \
|
--include-package=numpy \
|
||||||
|
|||||||
Reference in New Issue
Block a user