1336 lines
58 KiB
Python
Executable File
1336 lines
58 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# -*-coding:utf-8-*-
|
||
|
||
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||
# SPDX-License-Identifier: Apache-2.0
|
||
#
|
||
|
||
# WARNING: we don't check for Python build-time dependencies until
|
||
# check_environment() function below. If possible, avoid importing
|
||
# any external libraries here - put in external script, or import in
|
||
# their specific function instead.
|
||
|
||
import sys
|
||
import csv
|
||
import json
|
||
import re
|
||
import os
|
||
import argparse
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
import serial
|
||
from os import path
|
||
from os import mkdir
|
||
from io import StringIO
|
||
|
||
from PyQt5.Qt import *
|
||
from pyqtgraph import PlotWidget
|
||
from PyQt5 import QtCore
|
||
import pyqtgraph as pq
|
||
|
||
from PyQt5.QtCore import QDate, QDate, QTime, QDateTime
|
||
|
||
import threading
|
||
import base64
|
||
import time
|
||
from datetime import datetime
|
||
from multiprocessing import Process, Queue
|
||
|
||
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
|
||
# from PyQt5.QtChart import QChart, QLineSeries,QValueAxis
|
||
from esp_csi_tool_gui import Ui_MainWindow
|
||
|
||
from scipy import signal
|
||
import signal as signal_key
|
||
import socket
|
||
|
||
|
||
CSI_SAMPLE_RATE = 100
|
||
|
||
# Remove invalid subcarriers
|
||
# secondary channel : below, HT, 40 MHz, non STBC, v, HT-LTF: 0~63, -64~-1, 384
|
||
CSI_VAID_SUBCARRIER_INTERVAL = 5
|
||
csi_vaid_subcarrier_index = []
|
||
csi_vaid_subcarrier_color = []
|
||
color_step = 255 // (28 // CSI_VAID_SUBCARRIER_INTERVAL + 1)
|
||
|
||
# LLTF: 52
|
||
# 26 red
|
||
csi_vaid_subcarrier_index += [i for i in range(
|
||
0, 26, CSI_VAID_SUBCARRIER_INTERVAL)]
|
||
csi_vaid_subcarrier_color += [(i * color_step, 0, 0)
|
||
for i in range(1, 26 // CSI_VAID_SUBCARRIER_INTERVAL + 2)]
|
||
# 26 green
|
||
csi_vaid_subcarrier_index += [i for i in range(
|
||
26, 52, CSI_VAID_SUBCARRIER_INTERVAL)]
|
||
csi_vaid_subcarrier_color += [(0, i * color_step, 0)
|
||
for i in range(1, 26 // CSI_VAID_SUBCARRIER_INTERVAL + 2)]
|
||
|
||
DEVICE_INFO_COLUMNS_NAMES = ['type', 'timestamp', 'compile_time', 'chip_name', 'chip_revision',
|
||
'app_revision', 'idf_revision', 'total_heap', 'free_heap', 'router_ssid', 'ip', 'port']
|
||
g_device_info_series = None
|
||
|
||
CSI_DATA_INDEX = 500 # buffer size
|
||
CSI_DATA_COLUMNS = len(csi_vaid_subcarrier_index)
|
||
CSI_DATA_COLUMNS_NAMES = ['type', 'seq', 'timestamp', 'taget_seq', 'taget', 'mac', 'rssi', 'rate', 'sig_mode', 'mcs',
|
||
'cwb', 'smoothing', 'not_sounding', 'aggregation', 'stbc', 'fec_coding','sgi', 'noise_floor',
|
||
'ampdu_cnt', 'channel_primary', 'channel_secondary', 'local_timestamp', 'ant', 'sig_len',
|
||
'rx_state', 'agc_gain', 'fft_gain', 'len', 'first_word_invalid', 'data']
|
||
CSI_DATA_TARGETS = ['unknown', 'train', 'none', 'someone', 'static', 'move', 'front',
|
||
'after', 'left', 'right', 'go', 'jump', 'sit down', 'stand up', 'climb up', 'wave', 'applause']
|
||
RADAR_DATA_COLUMNS_NAMES = ['type', 'seq', 'timestamp',
|
||
'waveform_wander', 'wander_average', 'waveform_wander_threshold', 'someone_status',
|
||
'waveform_jitter', 'jitter_midean', 'waveform_jitter_threshold', 'move_status']
|
||
|
||
g_csi_amplitude_array = np.zeros(
|
||
[CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.int32)
|
||
g_rssi_array = np.zeros(CSI_DATA_INDEX, dtype=np.int8)
|
||
# 使用 object 类型以支持混合数据类型(字符串、数字、时间戳等)
|
||
g_radio_header_pd = pd.DataFrame(np.empty([10, len(
|
||
CSI_DATA_COLUMNS_NAMES[1:-1])], dtype=object), columns=CSI_DATA_COLUMNS_NAMES[1:-1])
|
||
|
||
RADAR_STATUS_RECORD = ['room', 'human', 'spend_time', 'start_time', 'stop_time']
|
||
g_status_record_pd = pd.DataFrame(np.zeros(
|
||
[20, len(RADAR_STATUS_RECORD)], dtype=np.str_), columns=RADAR_STATUS_RECORD)
|
||
|
||
RADAR_MOVE_RECORD = ['date', 'hour', 'minute', 'second', 'count']
|
||
g_move_record_pd = pd.DataFrame(columns=RADAR_MOVE_RECORD)
|
||
|
||
RADAR_DATA = ['status', 'threshold', 'value', 'max', 'min', 'mean', 'std']
|
||
g_radar_data_room_pd = pd.DataFrame(
|
||
np.zeros([10, len(RADAR_DATA)], dtype=np.float32), columns=RADAR_DATA)
|
||
g_radar_data_human_pd = pd.DataFrame(
|
||
np.zeros([10, len(RADAR_DATA)], dtype=np.float32), columns=RADAR_DATA)
|
||
|
||
g_current_time = QDateTime.currentDateTime()
|
||
|
||
RADAR_DATA_INDEX = 100 # buffer size
|
||
|
||
ROOM_STATUS_NAMES = ['none', 'someone']
|
||
HUMAN_STATUS_NAMES = ['static', 'move']
|
||
RADAR_WAVEFORM_NAMES = ['wander', 'jitter']
|
||
RADAR_targetS_NAMES = ['someone', 'move']
|
||
RADAR_targetS_LEN = len(RADAR_targetS_NAMES)
|
||
g_radar_eigenvalue_color = [(0, 0, 255), (0, 255, 0)]
|
||
g_radar_eigenvalue_threshold_color = [(255, 255, 0), (255, 0, 255)]
|
||
g_radar_eigenvalue_array = np.zeros(
|
||
[RADAR_DATA_INDEX, RADAR_targetS_LEN], dtype=np.float32)
|
||
g_radar_eigenvalue_threshold_array = np.zeros(
|
||
[RADAR_DATA_INDEX, RADAR_targetS_LEN], dtype=np.float32)
|
||
g_radar_status_array = np.zeros(
|
||
[RADAR_DATA_INDEX, RADAR_targetS_LEN], dtype=np.int32)
|
||
g_evaluate_statistics_array = np.zeros(
|
||
[RADAR_targetS_LEN, RADAR_targetS_LEN], dtype=np.int32)
|
||
|
||
g_display_raw_data = False
|
||
g_display_radar_model = False
|
||
g_display_eigenvalues_table = False
|
||
|
||
def base64_decode_bin(str_data):
|
||
try:
|
||
# 清理数据:移除换行符、回车符、空格等无效字符
|
||
str_data = str(str_data).strip().replace('\n', '').replace('\r', '').replace(' ', '')
|
||
|
||
# 如果数据包含日志信息(如 "->valid_len: 104"),只取 base64 部分
|
||
# 使用更智能的方式检测日志信息:查找常见的日志模式
|
||
# 例如:">valid_len", ": 104", "(120933)" 等
|
||
log_patterns = [
|
||
r'->valid_len',
|
||
r':\s*\d+', # 冒号后跟数字(如 ": 104")
|
||
r'\(\d+\)', # 括号中的数字(如 "(120933)")
|
||
r'[DIWE]\s*\(', # 日志级别(如 "I (")
|
||
]
|
||
|
||
# 查找日志模式的起始位置,只保留 base64 部分
|
||
min_log_start = len(str_data)
|
||
for pattern in log_patterns:
|
||
import re
|
||
match = re.search(pattern, str_data)
|
||
if match:
|
||
min_log_start = min(min_log_start, match.start())
|
||
|
||
# 如果找到日志信息,只保留 base64 部分(至少保留一定长度,避免误判)
|
||
if min_log_start < len(str_data) and min_log_start > 10:
|
||
str_data = str_data[:min_log_start]
|
||
|
||
# 如果数据长度不是4的倍数,尝试添加填充
|
||
missing_padding = len(str_data) % 4
|
||
if missing_padding:
|
||
str_data += '=' * (4 - missing_padding)
|
||
|
||
bin_data = base64.b64decode(str_data, validate=True)
|
||
except Exception as e:
|
||
print(f'Exception: {e}, data: {str_data[:100]}')
|
||
# 解码失败时返回空列表,避免后续错误
|
||
return []
|
||
|
||
list_data = list(bin_data)
|
||
|
||
for i in range(len(list_data)):
|
||
if list_data[i] > 127:
|
||
list_data[i] = list_data[i] - 256
|
||
|
||
return list_data
|
||
|
||
|
||
def base64_encode_bin(list_data):
|
||
for i in range(len(list_data)):
|
||
if list_data[i] < 0:
|
||
list_data[i] = 256 + list_data[i]
|
||
# print(list_data)
|
||
|
||
str_data = 'test'
|
||
try:
|
||
str_data = base64.b64encode(bytes(list_data)).decode('utf-8')
|
||
except Exception as e:
|
||
print(f'Exception: {e}, data: {list_data}')
|
||
return str_data
|
||
|
||
|
||
def get_label(folder_path):
|
||
parts = str.split(folder_path, os.path.sep)
|
||
return parts[-1]
|
||
|
||
|
||
def evaluate_data_send(serial_queue_write, folder_path):
|
||
label = get_label(folder_path)
|
||
if label == 'train':
|
||
command = f'radar --train_start'
|
||
serial_queue_write.put(command)
|
||
|
||
tcpCliSock = socket.socket()
|
||
device_info_series = pd.read_csv('log/device_info.csv').iloc[-1]
|
||
|
||
print(f"connect:{device_info_series['ip']},{device_info_series['port']}")
|
||
tcpCliSock.connect((device_info_series['ip'], device_info_series['port']))
|
||
|
||
file_name_list = sorted(os.listdir(folder_path))
|
||
print(file_name_list)
|
||
for file_name in file_name_list:
|
||
file_path = folder_path + os.path.sep + file_name
|
||
data_pd = pd.read_csv(file_path)
|
||
for index, data_series in enumerate(data_pd.iloc):
|
||
csi_raw_data = json.loads(data_series['data'])
|
||
data_pd.loc[index, 'data'] = base64_encode_bin(csi_raw_data)
|
||
temp_list = base64_decode_bin(data_pd.loc[index, 'data'])
|
||
# print(f"temp_list: {temp_list}")
|
||
data_str = ','.join(str(value)
|
||
for value in data_pd.loc[index]) + '\n'
|
||
data_str = data_str.encode('utf-8')
|
||
# print(f"data_str: {data_str}")
|
||
tcpCliSock.send(data_str)
|
||
|
||
tcpCliSock.close()
|
||
time.sleep(1)
|
||
|
||
if label == 'train':
|
||
command = 'radar --train_stop'
|
||
serial_queue_write.put(command)
|
||
|
||
sys.exit(0)
|
||
|
||
|
||
class DataGraphicalWindow(QMainWindow, Ui_MainWindow):
|
||
def __init__(self, serial_queue_write, csi_output_type='LLTF', parent=None):
|
||
super(DataGraphicalWindow, self).__init__(parent)
|
||
self.setupUi(self)
|
||
self.csi_output_type = csi_output_type
|
||
global g_display_raw_data
|
||
global g_display_radar_model
|
||
global display_eigenvalues_table
|
||
|
||
with open('./config/gui_config.json') as file:
|
||
gui_config = json.load(file)
|
||
# print(f"gui_config: {gui_config}")
|
||
if len(gui_config['router_ssid']) > 0:
|
||
self.lineEdit_router_ssid.setText(gui_config['router_ssid'])
|
||
if len(gui_config['router_password']) >= 8:
|
||
self.lineEdit_router_password.setText(
|
||
gui_config['router_password'])
|
||
|
||
g_display_raw_data = gui_config['display_raw_data']
|
||
if g_display_raw_data:
|
||
self.checkBox_raw_data.setChecked(True)
|
||
else:
|
||
self.checkBox_raw_data.setChecked(False)
|
||
|
||
g_display_radar_model = gui_config['display_radar_model']
|
||
if g_display_radar_model:
|
||
self.checkBox_radar_model.setChecked(True)
|
||
else:
|
||
self.checkBox_radar_model.setChecked(False)
|
||
|
||
g_display_eigenvalues_table = gui_config['display_eigenvalues_table']
|
||
if g_display_eigenvalues_table:
|
||
self.checkBox_display_eigenvalues_table.setChecked(True)
|
||
else:
|
||
self.checkBox_display_eigenvalues_table.setChecked(False)
|
||
|
||
if gui_config['router_auto_connect']:
|
||
self.checkBox_router_auto_connect.setChecked(True)
|
||
else:
|
||
self.checkBox_router_auto_connect.setChecked(False)
|
||
|
||
self.curve_subcarrier_range = np.array([10, 20])
|
||
self.graphicsView_subcarrier.setYRange(
|
||
self.curve_subcarrier_range[0], self.curve_subcarrier_range[1])
|
||
self.graphicsView_subcarrier.addLegend()
|
||
|
||
self.curve_subcarrier = []
|
||
self.serial_queue_write = serial_queue_write
|
||
|
||
for i in range(CSI_DATA_COLUMNS):
|
||
curve = self.graphicsView_subcarrier.plot(
|
||
g_csi_amplitude_array[:, i], name=str(i), pen=csi_vaid_subcarrier_color[i])
|
||
self.curve_subcarrier.append(curve)
|
||
|
||
self.curve_rssi = self.graphicsView_rssi.plot(
|
||
g_rssi_array, name='rssi', pen=(255, 255, 255))
|
||
|
||
self.wave_filtering_flag = self.checkBox_wave_filtering.isCheckable()
|
||
self.checkBox_wave_filtering.released.connect(
|
||
self.show_curve_subcarrier_filter)
|
||
# self.graphicsView_rssi.setYRange(-10, -65)
|
||
self.checkBox_router_auto_connect.released.connect(
|
||
self.show_router_auto_connect)
|
||
|
||
self.curve_radar_eigenvalue = []
|
||
self.curve_radar_eigenvalue_threshold = []
|
||
# self.graphicsView_eigenvalues.setYRange(0.95, 1.0)
|
||
self.graphicsView_eigenvalues.addLegend()
|
||
self.graphicsView_eigenvalues.showGrid(x=True, y=True)
|
||
|
||
self.dateTimeEdit_statistics_time.setDateTime(g_current_time)
|
||
self.statistic_config = pd.Series(index=['time', 'mode', 'auto_update'],
|
||
data=[
|
||
self.dateTimeEdit_statistics_time.dateTime().toPyDateTime(),
|
||
self.comboBox_statistics_mode.currentText(),
|
||
self.checkBox_statistics_auto_update.isChecked()
|
||
])
|
||
|
||
for i in range(RADAR_targetS_LEN):
|
||
curve_eigenvalue = self.graphicsView_eigenvalues.plot(
|
||
g_radar_eigenvalue_array[:, i], name=RADAR_WAVEFORM_NAMES[i], pen=g_radar_eigenvalue_color[i])
|
||
curve_eigenvalue_threshold = self.graphicsView_eigenvalues.plot(
|
||
g_radar_eigenvalue_threshold_array[:, i],
|
||
name=RADAR_WAVEFORM_NAMES[i] + '_' + RADAR_targetS_NAMES[i] + '_threshold',
|
||
pen=g_radar_eigenvalue_threshold_color[i])
|
||
self.curve_radar_eigenvalue.append(curve_eigenvalue)
|
||
self.curve_radar_eigenvalue_threshold.append(
|
||
curve_eigenvalue_threshold)
|
||
|
||
self.model_radio_header = QStandardItemModel(
|
||
1, len(g_radio_header_pd.columns.values))
|
||
self.model_radio_header.setHorizontalHeaderLabels(
|
||
g_radio_header_pd.columns.values)
|
||
self.tableView_radioHeader.setModel(self.model_radio_header)
|
||
self.tableView_radioHeader.horizontalHeader(
|
||
).setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
self.tableView_values = {
|
||
'sig_mode': ['non HT(11bg)', 'HT(11n)', 'VHT(11ac)'],
|
||
'mcs': [f'MSC{i}' for i in range(76)],
|
||
'cwb': ['20MHz', '40MHz'],
|
||
'aggregation': ['MPDU', 'AMPDU'],
|
||
'channel_secondary': ['none', 'above', 'below'],
|
||
'stbc': ['non STBC', 'STBC'],
|
||
'sgi': ['Long GI', 'Short GI'],
|
||
}
|
||
|
||
self.model_device_info = QStandardItemModel(
|
||
len(DEVICE_INFO_COLUMNS_NAMES), 2)
|
||
self.tableView_device_info.setModel(self.model_device_info)
|
||
self.tableView_device_info.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
|
||
self.tableView_device_info.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
self.model_status_record = QStandardItemModel(
|
||
1, len(g_status_record_pd.columns.values))
|
||
self.model_status_record.setHorizontalHeaderLabels(
|
||
g_status_record_pd.columns.values)
|
||
self.tableView_status_record.setModel(self.model_status_record)
|
||
self.tableView_status_record.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
|
||
self.tableView_status_record.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
self.model_radar_data_room = QStandardItemModel(len(
|
||
g_radar_data_room_pd.columns.values), len(g_radar_data_room_pd.columns.values))
|
||
self.model_radar_data_room.setHorizontalHeaderLabels(g_radar_data_room_pd.columns.values)
|
||
self.tableView_radar_data_room.setModel(self.model_radar_data_room)
|
||
self.tableView_radar_data_room.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
self.model_radar_data_human = QStandardItemModel(len(
|
||
g_radar_data_human_pd.columns.values), len(g_radar_data_human_pd.columns.values))
|
||
self.model_radar_data_human.setHorizontalHeaderLabels(
|
||
g_radar_data_human_pd.columns.values)
|
||
self.tableView_radar_data_human.setModel(self.model_radar_data_human)
|
||
self.tableView_radar_data_human.horizontalHeader(
|
||
).setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
evaluate_STATISTICS_COLUMNS_NAMES = ['type', 'count', 'rate']
|
||
self.model_evaluate_statistics = QStandardItemModel(
|
||
len(evaluate_STATISTICS_COLUMNS_NAMES), 3)
|
||
self.model_evaluate_statistics.setHorizontalHeaderLabels(
|
||
evaluate_STATISTICS_COLUMNS_NAMES)
|
||
self.model_evaluate_statistics.setItem(
|
||
0, 0, QStandardItem('none static'))
|
||
self.model_evaluate_statistics.setItem(1, 0, QStandardItem('none move'))
|
||
self.model_evaluate_statistics.setItem(
|
||
2, 0, QStandardItem('someone static'))
|
||
self.model_evaluate_statistics.setItem(
|
||
3, 0, QStandardItem('someone move'))
|
||
|
||
self.pushButton_predict_config.released.connect(self.command_predict_config)
|
||
self.pushButton_statistics_config.released.connect(
|
||
self.show_statistics_status_record_move)
|
||
self.comboBox_statistics_mode.activated.connect(
|
||
self.show_statistics_status_record_move)
|
||
# self.pushButton_evaluate_config.released.connect(self.command_evaluate_config)
|
||
|
||
self.pushButton_router_connect.released.connect(
|
||
self.command_router_connect)
|
||
self.pushButton_command.released.connect(self.command_custom)
|
||
|
||
self.comboBox_command.activated.connect(self.comboBox_command_show)
|
||
|
||
self.pushButton_collect_start.released.connect(
|
||
self.pushButton_collect_show)
|
||
self.pushButton_train_start.released.connect(
|
||
self.pushButton_train_show)
|
||
|
||
self.pushButton_collect_clean.released.connect(
|
||
self.pushButton_collect_clean_show)
|
||
|
||
self.checkBox_raw_data.released.connect(self.checkBox_raw_data_show)
|
||
self.checkBox_radar_model.released.connect(self.checkBox_radar_model_show)
|
||
self.checkBox_display_eigenvalues_table.released.connect(
|
||
self.checkBox_display_eigenvalues_table_show)
|
||
|
||
self.splitter_eigenvalues.setStretchFactor(0, 8)
|
||
self.splitter_eigenvalues.setStretchFactor(1, 1)
|
||
|
||
self.splitter_status_record.setStretchFactor(0, 10)
|
||
self.splitter_status_record.setStretchFactor(1, 1)
|
||
|
||
self.splitter_raw_data.setStretchFactor(0, 1)
|
||
self.splitter_raw_data.setStretchFactor(1, 8)
|
||
self.splitter_raw_data.setStretchFactor(2, 2)
|
||
|
||
self.textBrowser_log.setStyleSheet('background:black')
|
||
|
||
self.timer_boot_command = QTimer()
|
||
self.timer_boot_command.timeout.connect(self.command_boot)
|
||
self.timer_boot_command.setInterval(3000)
|
||
self.timer_boot_command.start()
|
||
|
||
self.timer_curve_subcarrier = QTimer()
|
||
self.timer_curve_subcarrier.timeout.connect(self.show_curve_subcarrier)
|
||
self.timer_curve_subcarrier.setInterval(300)
|
||
|
||
self.timer_curve_radar = QTimer()
|
||
self.timer_curve_radar.timeout.connect(self.show_curve_eigenvalue)
|
||
self.timer_curve_radar.setInterval(100)
|
||
|
||
self.timer_eigenvalues_table = QTimer()
|
||
self.timer_eigenvalues_table.timeout.connect(
|
||
self.show_eigenvalue_table)
|
||
self.timer_eigenvalues_table.setInterval(100)
|
||
|
||
self.label_number = self.spinBox_collect_number.value()
|
||
self.timer_collect_duration = QTimer()
|
||
self.timer_collect_duration.timeout.connect(
|
||
self.spinBox_collect_number_show)
|
||
|
||
self.label_delay = self.timeEdit_collect_delay.time()
|
||
self.timer_collect_delay = QTimer()
|
||
self.timer_collect_delay.setInterval(1000)
|
||
self.timer_collect_delay.timeout.connect(self.timeEdit_collect_delay_show)
|
||
|
||
self.train_duration = self.timeEdit_train_duration.time()
|
||
self.timer_train_duration = QTimer()
|
||
self.timer_train_duration.setInterval(1000)
|
||
self.timer_train_duration.timeout.connect(
|
||
self.spinBox_train_duration_show)
|
||
|
||
self.train_delay = self.timeEdit_train_delay.time()
|
||
self.timer_train_delay = QTimer()
|
||
self.timer_train_delay.setInterval(1000)
|
||
self.timer_train_delay.timeout.connect(self.timeEdit_train_delay_show)
|
||
|
||
self.timer_status_record = QTimer()
|
||
self.timer_status_record.timeout.connect(self.show_statistics_status_record)
|
||
self.timer_status_record.setInterval(1000)
|
||
|
||
self.timer_evaluate_statistics = QTimer()
|
||
self.timer_evaluate_statistics.timeout.connect(self.show_evaluate_statistics)
|
||
self.timer_evaluate_statistics.setInterval(1000)
|
||
|
||
self.checkBox_raw_data_show()
|
||
self.checkBox_radar_model_show()
|
||
self.checkBox_display_eigenvalues_table_show()
|
||
|
||
def checkBox_raw_data_show(self):
|
||
global g_display_raw_data
|
||
g_display_raw_data = self.checkBox_raw_data.isChecked()
|
||
|
||
if self.checkBox_raw_data.isChecked():
|
||
self.groupBox_raw_data.show()
|
||
self.timer_curve_subcarrier.start()
|
||
else:
|
||
self.groupBox_raw_data.hide()
|
||
self.timer_curve_subcarrier.stop()
|
||
|
||
with open('./config/gui_config.json', 'r') as file:
|
||
gui_config = json.load(file)
|
||
gui_config['display_raw_data'] = self.checkBox_raw_data.isChecked()
|
||
with open('./config/gui_config.json', 'w') as file:
|
||
json.dump(gui_config, file)
|
||
|
||
def checkBox_radar_model_show(self):
|
||
global g_display_radar_model
|
||
|
||
g_display_radar_model = self.checkBox_radar_model.isChecked()
|
||
|
||
if self.checkBox_radar_model.isChecked():
|
||
self.groupBox_radar_model.show()
|
||
self.timer_status_record.start()
|
||
self.timer_curve_radar.start()
|
||
else:
|
||
self.groupBox_radar_model.hide()
|
||
self.timer_status_record.stop()
|
||
self.timer_curve_radar.stop()
|
||
|
||
with open('./config/gui_config.json', 'r') as file:
|
||
gui_config = json.load(file)
|
||
gui_config['display_radar_model'] = self.checkBox_radar_model.isChecked()
|
||
with open('./config/gui_config.json', 'w') as file:
|
||
json.dump(gui_config, file)
|
||
|
||
def checkBox_display_eigenvalues_table_show(self):
|
||
global g_display_eigenvalues_table
|
||
g_display_eigenvalues_table = self.checkBox_display_eigenvalues_table.isChecked()
|
||
|
||
if self.checkBox_display_eigenvalues_table.isChecked():
|
||
self.tableView_eigenvalues.show()
|
||
self.timer_eigenvalues_table.start()
|
||
else:
|
||
self.tableView_eigenvalues.hide()
|
||
self.timer_eigenvalues_table.stop()
|
||
|
||
with open('./config/gui_config.json', 'r') as file:
|
||
gui_config = json.load(file)
|
||
gui_config['display_eigenvalues_table'] = self.checkBox_display_eigenvalues_table.isChecked()
|
||
with open('./config/gui_config.json', 'w') as file:
|
||
json.dump(gui_config, file)
|
||
|
||
def show_router_auto_connect(self):
|
||
with open('./config/gui_config.json', 'r') as file:
|
||
gui_config = json.load(file)
|
||
gui_config['router_auto_connect'] = self.checkBox_router_auto_connect.isChecked()
|
||
with open('./config/gui_config.json', 'w') as file:
|
||
json.dump(gui_config, file)
|
||
|
||
def median_filtering(self, waveform):
|
||
tmp = waveform
|
||
for i in range(1, waveform.shape[0] - 1):
|
||
outliers_count = 0
|
||
for j in range(waveform.shape[1]):
|
||
if ((waveform[i - 1, j] - waveform[i, j] > 2 and waveform[i + 1, j] - waveform[i, j] > 2)
|
||
or (waveform[i - 1, j] - waveform[i, j] < -2 and waveform[i + 1, j] - waveform[i, j] < -2)):
|
||
outliers_count += 1
|
||
continue
|
||
|
||
if outliers_count > 16:
|
||
for x in range(1, waveform.shape[1] - 1):
|
||
tmp[i, x] = (waveform[i - 1, x] + waveform[i + 1, x]) / 2
|
||
waveform = tmp
|
||
|
||
def show_curve_subcarrier(self):
|
||
wn = 20 / (CSI_SAMPLE_RATE / 2)
|
||
b, a = signal.butter(8, wn, 'lowpass')
|
||
|
||
if self.wave_filtering_flag:
|
||
self.median_filtering(g_csi_amplitude_array)
|
||
csi_filtfilt_data = signal.filtfilt(b, a, g_csi_amplitude_array.T).T
|
||
else:
|
||
csi_filtfilt_data = g_csi_amplitude_array
|
||
|
||
if self.curve_subcarrier_range[0] > csi_filtfilt_data.min() or self.curve_subcarrier_range[1] < csi_filtfilt_data.max():
|
||
if csi_filtfilt_data.min() > 0 and self.curve_subcarrier_range[0] > csi_filtfilt_data.min():
|
||
self.curve_subcarrier_range[0] = csi_filtfilt_data.min() - 1
|
||
if self.curve_subcarrier_range[1] < csi_filtfilt_data.max():
|
||
self.curve_subcarrier_range[1] = csi_filtfilt_data.max() + 1
|
||
self.graphicsView_subcarrier.setYRange(
|
||
self.curve_subcarrier_range[0], self.curve_subcarrier_range[1])
|
||
|
||
for i in range(CSI_DATA_COLUMNS):
|
||
self.curve_subcarrier[i].setData(csi_filtfilt_data[:, i])
|
||
|
||
if self.wave_filtering_flag:
|
||
csi_filtfilt_rssi = signal.filtfilt(
|
||
b, a, g_rssi_array).astype(np.int32)
|
||
else:
|
||
csi_filtfilt_rssi = g_rssi_array
|
||
self.curve_rssi.setData(csi_filtfilt_rssi)
|
||
|
||
for i in range(g_radio_header_pd.shape[0]):
|
||
for j in range(g_radio_header_pd.shape[1]):
|
||
cell_value = g_radio_header_pd.iloc[i, j]
|
||
if cell_value is None or (isinstance(cell_value, float) and np.isnan(cell_value)):
|
||
item = QStandardItem('')
|
||
elif g_radio_header_pd.columns.values[j] in self.tableView_values.keys():
|
||
try:
|
||
str_values = self.tableView_values[g_radio_header_pd.columns.values[j]][int(cell_value)]
|
||
item = QStandardItem(str_values)
|
||
except (ValueError, TypeError, KeyError):
|
||
item = QStandardItem(str(cell_value))
|
||
else:
|
||
# print(j, g_radio_header_pd.columns.values[j])
|
||
item = QStandardItem(str(cell_value) if cell_value is not None else '')
|
||
self.model_radio_header.setItem(i, j, item)
|
||
|
||
def show_curve_subcarrier_filter(self):
|
||
self.wave_filtering_flag = self.checkBox_wave_filtering.isChecked()
|
||
|
||
def show_curve_eigenvalue(self):
|
||
for i in range(RADAR_targetS_LEN):
|
||
self.curve_radar_eigenvalue[i].setData(
|
||
g_radar_eigenvalue_array[:, i])
|
||
self.curve_radar_eigenvalue_threshold[i].setData(
|
||
g_radar_eigenvalue_threshold_array[:, i])
|
||
|
||
curve_radar_title = f'{ROOM_STATUS_NAMES[g_radar_status_array[-1,0]]} {HUMAN_STATUS_NAMES[g_radar_status_array[-1,1]]}'
|
||
self.graphicsView_eigenvalues.setTitle(curve_radar_title)
|
||
|
||
def show_eigenvalue_table(self):
|
||
for i in range(g_radar_data_room_pd.shape[0]):
|
||
for j in range(g_radar_data_room_pd.shape[1]):
|
||
# data_str = "%.5f" % 0.01
|
||
# print(f"{g_radar_data_room_pd.iloc[i,j]}, {type(g_radar_data_room_pd.iloc[i,j])}")
|
||
data_str = '%.5f' % g_radar_data_room_pd.iloc[i, j]
|
||
item = QStandardItem(data_str)
|
||
self.model_radar_data_room.setItem(i, j, item)
|
||
|
||
for i in range(g_radar_data_human_pd.shape[0]):
|
||
for j in range(g_radar_data_human_pd.shape[1]):
|
||
# print(f"{g_radar_data_human_pd.iloc[i,j]}, {type(g_radar_data_human_pd.iloc[i,j])}")
|
||
data_str = '%.5f' % g_radar_data_human_pd.iloc[i, j]
|
||
item = QStandardItem(data_str)
|
||
self.model_radar_data_human.setItem(i, j, item)
|
||
|
||
def show_device_info(self, device_info_series):
|
||
# print(device_info_series)
|
||
for i in range(len(device_info_series)):
|
||
self.model_device_info.setItem(
|
||
i, 0, QStandardItem(device_info_series.index[i]))
|
||
self.model_device_info.setItem(
|
||
i, 1, QStandardItem(str(device_info_series.values[i])))
|
||
# self.tableView_device_info.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||
|
||
def get_statistic_config(self):
|
||
self.statistic_config['time'] = self.dateTimeEdit_statistics_time.dateTime().toPyDateTime()
|
||
self.statistic_config['mode'] = self.comboBox_statistics_mode.currentText()
|
||
self.statistic_config['auto_update'] = self.checkBox_statistics_auto_update.isChecked()
|
||
|
||
def show_statistics_status_record_move(self):
|
||
self.get_statistic_config()
|
||
|
||
if self.statistic_config['mode'] == 'day':
|
||
statistic_move_array = np.zeros(24, dtype=np.int32)
|
||
statistic_move_title = datetime.strftime(
|
||
self.statistic_config['time'], '%Y-%m-%d')
|
||
statistic_move_pd = g_move_record_pd[g_move_record_pd['date']
|
||
== self.statistic_config['time'].date()]
|
||
|
||
if len(statistic_move_pd):
|
||
statistic_move_hour = statistic_move_pd.groupby(by='hour')[
|
||
'count'].sum()
|
||
|
||
for index, value in statistic_move_hour.items():
|
||
statistic_move_array[index] = value
|
||
|
||
elif self.statistic_config['mode'] == 'hour':
|
||
statistic_move_array = np.zeros(60, dtype=np.int32)
|
||
statistic_move_title = datetime.strftime(
|
||
self.statistic_config['time'], '%Y-%m-%d %H')
|
||
|
||
statistic_move_pd = g_move_record_pd[(g_move_record_pd['date'] == self.statistic_config['time'].date()) & (
|
||
g_move_record_pd['hour'] == self.statistic_config['time'].time().hour)]
|
||
|
||
if len(statistic_move_pd):
|
||
statistic_move_minute = statistic_move_pd.groupby(by='minute')[
|
||
'count'].sum()
|
||
for index, value in statistic_move_minute.items():
|
||
statistic_move_array[index] = value
|
||
|
||
elif self.statistic_config['mode'] == 'minute':
|
||
statistic_move_array = np.zeros(60, dtype=np.int32)
|
||
statistic_move_title = datetime.strftime(
|
||
self.statistic_config['time'], '%Y-%m-%d %H:%M')
|
||
|
||
statistic_move_pd = g_move_record_pd[(g_move_record_pd['date'] == self.statistic_config['time'].date())
|
||
& (g_move_record_pd['hour'] == self.statistic_config['time'].time().hour)
|
||
& (g_move_record_pd['hour'] == self.statistic_config['time'].time().hour)
|
||
& (g_move_record_pd['hour'] == self.statistic_config['time'].time().hour)
|
||
& (g_move_record_pd['minute'] == self.statistic_config['time'].time().minute)]
|
||
|
||
if len(statistic_move_pd):
|
||
statistic_move_second = statistic_move_pd.groupby(by='second')[
|
||
'count'].sum()
|
||
for index, value in statistic_move_second.items():
|
||
statistic_move_array[index] = value
|
||
else:
|
||
print(f"fail mode: {self.statistic_config['mode']}")
|
||
|
||
try:
|
||
self.graphicsView_status_record.removeItem(self.bg)
|
||
except Exception as e:
|
||
# print(e)
|
||
pass
|
||
|
||
try:
|
||
self.bg = pq.BarGraphItem(x=range(
|
||
len(statistic_move_array)), height=statistic_move_array, width=0.3, brush='g')
|
||
self.graphicsView_status_record.addItem(self.bg)
|
||
|
||
self.graphicsView_status_record.showGrid(x=True, y=True)
|
||
self.graphicsView_status_record.setXRange(
|
||
0, len(statistic_move_array))
|
||
# print(statistic_move_title)
|
||
self.graphicsView_status_record.setTitle(
|
||
statistic_move_title + ' Move Count')
|
||
except Exception as e:
|
||
print(e)
|
||
|
||
def show_statistics_status_record(self):
|
||
if self.checkBox_statistics_auto_update.isChecked():
|
||
self.dateTimeEdit_statistics_time.setDateTime(g_current_time)
|
||
self.show_statistics_status_record_move()
|
||
|
||
# print(f"g_status_record table: {g_status_record_pd.shape}")
|
||
|
||
for i in range(g_status_record_pd.shape[0]):
|
||
for j in range(g_status_record_pd.shape[1]):
|
||
item = QStandardItem(g_status_record_pd.iloc[i, j])
|
||
self.model_status_record.setItem(i, j, item)
|
||
|
||
def show_evaluate_statistics(self):
|
||
if not g_evaluate_statistics_array.sum():
|
||
return
|
||
|
||
for i in range(g_evaluate_statistics_array.shape[0]):
|
||
for j in range(g_evaluate_statistics_array.shape[1]):
|
||
row = i * 2 + j
|
||
|
||
item = QStandardItem(str(g_evaluate_statistics_array[i][j]))
|
||
self.model_evaluate_statistics.setItem(row, 1, item)
|
||
|
||
data_str = '%.2f%%' % (
|
||
g_evaluate_statistics_array[i][j] * 100 / g_evaluate_statistics_array.sum())
|
||
item = QStandardItem(data_str)
|
||
self.model_evaluate_statistics.setItem(row, 2, item)
|
||
|
||
def command_boot(self):
|
||
command = f'radar --csi_output_type {self.csi_output_type} --csi_output_format base64'
|
||
self.serial_queue_write.put(command)
|
||
|
||
if self.checkBox_router_auto_connect.isChecked() and len(self.lineEdit_router_ssid.text()) > 0:
|
||
self.command_router_connect()
|
||
|
||
self.timer_boot_command.stop()
|
||
|
||
def command_predict_config(self):
|
||
command = (f'radar --predict_someone_sensitivity {self.doubleSpinBox_predict_someone_sensitivity.value()}' +
|
||
f' --predict_move_sensitivity {self.doubleSpinBox_predict_move_sensitivity.value()}')
|
||
self.serial_queue_write.put(command)
|
||
command = (f'radar --predict_buff_size {self.spinBox_predict_buffer_size.text()}' +
|
||
f' --predict_outliers_number {self.spinBox_predict_outliers_number.text()}')
|
||
self.serial_queue_write.put(command)
|
||
|
||
def command_router_connect(self):
|
||
if self.pushButton_router_connect.text() == 'connect':
|
||
self.pushButton_router_connect.setText('disconnect')
|
||
self.pushButton_router_connect.setStyleSheet('color: red')
|
||
|
||
command = 'wifi_config --ssid ' + ("\"%s\"" % self.lineEdit_router_ssid.text())
|
||
if len(self.lineEdit_router_password.text()) >= 8:
|
||
command += ' --password ' + self.lineEdit_router_password.text()
|
||
self.serial_queue_write.put(command)
|
||
else:
|
||
self.pushButton_router_connect.setText('connect')
|
||
self.pushButton_router_connect.setStyleSheet('color: black')
|
||
command = 'ping --abort'
|
||
self.serial_queue_write.put(command)
|
||
command = 'wifi_config --disconnect'
|
||
self.serial_queue_write.put(command)
|
||
time.sleep(3)
|
||
command = 'radar --csi_output_type ' + self.csi_output_type + ' --csi_output_format base64'
|
||
self.serial_queue_write.put(command)
|
||
|
||
with open('./config/gui_config.json', 'r') as file:
|
||
gui_config = json.load(file)
|
||
gui_config['router_ssid'] = self.lineEdit_router_ssid.text()
|
||
gui_config['router_password'] = self.lineEdit_router_password.text()
|
||
with open('./config/gui_config.json', 'w') as file:
|
||
json.dump(gui_config, file)
|
||
|
||
def command_custom(self):
|
||
command = self.lineEdit_command.text()
|
||
self.serial_queue_write.put(command)
|
||
|
||
def command_collect_target_start(self):
|
||
command = (f'radar --collect_number {self.spinBox_collect_number.value()}' +
|
||
f' --collect_tagets {self.comboBox_collect_target.currentText()}' +
|
||
f' --collect_duration {self.spinBox_collect_duration.value()}')
|
||
self.serial_queue_write.put(command)
|
||
|
||
def command_collect_target_stop(self):
|
||
command = 'radar --collect_number 0 --collect_tagets unknown'
|
||
self.serial_queue_write.put(command)
|
||
|
||
def spinBox_collect_number_show(self):
|
||
number = self.spinBox_collect_number.value()
|
||
if number >= 1:
|
||
self.spinBox_collect_number.setValue(number - 1)
|
||
self.timer_collect_duration.start()
|
||
# self.command_collect_target_start()
|
||
else:
|
||
self.timer_collect_duration.stop()
|
||
# self.command_collect_target_stop()
|
||
self.spinBox_collect_number.setValue(self.label_number)
|
||
self.timeEdit_collect_delay.setTime(self.label_delay)
|
||
self.pushButton_collect_start.setStyleSheet('color: black')
|
||
self.spinBox_collect_number.setStyleSheet('color: black')
|
||
self.pushButton_collect_start.setText('start')
|
||
|
||
def timeEdit_collect_delay_show(self):
|
||
time_temp = self.timeEdit_collect_delay.time()
|
||
second = time_temp.hour() * 3600 + time_temp.minute() * 60 + time_temp.second()
|
||
if second > 0:
|
||
self.timeEdit_collect_delay.setTime(time_temp.addSecs(-1))
|
||
self.timer_collect_delay.start()
|
||
else:
|
||
self.timer_collect_delay.stop()
|
||
|
||
duration = self.spinBox_collect_duration.value()
|
||
self.timer_collect_duration.setInterval(duration)
|
||
self.timer_collect_duration.start()
|
||
self.command_collect_target_start()
|
||
self.spinBox_collect_number.setStyleSheet('color: red')
|
||
self.timeEdit_collect_delay.setStyleSheet('color: black')
|
||
|
||
def pushButton_collect_show(self):
|
||
if self.pushButton_collect_start.text() == 'start':
|
||
if self.comboBox_collect_target.currentIndex() == 0 or self.spinBox_collect_number.value() == 0:
|
||
err = QErrorMessage(self)
|
||
err.setWindowTitle('Label parameter error')
|
||
err.showMessage(
|
||
"Please check whether 'taget' or 'number' is set")
|
||
err.show()
|
||
return
|
||
|
||
self.pushButton_collect_start.setText('stop')
|
||
self.pushButton_collect_start.setStyleSheet('color: red')
|
||
self.timeEdit_collect_delay.setStyleSheet('color: red')
|
||
self.timer_collect_delay.start()
|
||
self.label_number = self.spinBox_collect_number.value()
|
||
self.label_delay = self.timeEdit_collect_delay.time()
|
||
else:
|
||
self.spinBox_collect_number.setValue(self.label_number)
|
||
self.timeEdit_collect_delay.setTime(self.label_delay)
|
||
self.spinBox_collect_number.setStyleSheet('color: black')
|
||
self.pushButton_collect_start.setStyleSheet('color: black')
|
||
self.timer_collect_delay.stop()
|
||
self.timer_collect_duration.stop()
|
||
self.command_collect_target_stop()
|
||
self.pushButton_collect_start.setText('start')
|
||
|
||
def pushButton_collect_clean_show(self):
|
||
folder_path = 'data'
|
||
|
||
message = QMessageBox.warning(
|
||
self, 'Warning', "will delete all files in 'data'", QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok)
|
||
if message == QMessageBox.Cancel:
|
||
return
|
||
|
||
for root, dirs, files in os.walk(folder_path, topdown=False):
|
||
for name in files:
|
||
os.remove(os.path.join(root, name))
|
||
for name in dirs:
|
||
os.rmdir(os.path.join(root, name))
|
||
|
||
def command_train_start(self):
|
||
command = f'radar --train_start'
|
||
|
||
if self.checkBox_train_add.isChecked():
|
||
command += ' --train_add'
|
||
|
||
self.serial_queue_write.put(command)
|
||
|
||
def command_train_stop(self):
|
||
command = 'radar --train_stop'
|
||
self.serial_queue_write.put(command)
|
||
|
||
def spinBox_train_duration_show(self):
|
||
time_temp = self.timeEdit_train_duration.time()
|
||
second = time_temp.hour() * 3600 + time_temp.minute() * 60 + time_temp.second()
|
||
if second > 0:
|
||
self.timeEdit_train_duration.setTime(time_temp.addSecs(-1))
|
||
self.timer_train_duration.start()
|
||
else:
|
||
self.timer_train_duration.stop()
|
||
self.command_train_stop()
|
||
self.timeEdit_train_delay.setTime(self.train_delay)
|
||
self.timeEdit_train_duration.setTime(self.train_duration)
|
||
self.timeEdit_train_duration.setStyleSheet('color: black')
|
||
self.pushButton_train_start.setText('start')
|
||
self.pushButton_train_start.setStyleSheet('color: black')
|
||
|
||
def timeEdit_train_delay_show(self):
|
||
time_temp = self.timeEdit_train_delay.time()
|
||
second = time_temp.hour() * 3600 + time_temp.minute() * 60 + time_temp.second()
|
||
if second > 0:
|
||
self.timeEdit_train_delay.setTime(time_temp.addSecs(-1))
|
||
self.timer_train_delay.start()
|
||
else:
|
||
self.timer_train_delay.stop()
|
||
self.command_train_start()
|
||
self.spinBox_train_duration_show()
|
||
self.timeEdit_train_duration.setStyleSheet('color: red')
|
||
self.timeEdit_train_delay.setStyleSheet('color: black')
|
||
|
||
def pushButton_train_show(self):
|
||
if self.pushButton_train_start.text() == 'start':
|
||
reply = QMessageBox.question(self, 'Note', 'During environmental calibration, it must be ensured that no one is in the room',
|
||
QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Yes)
|
||
if reply == QMessageBox.Cancel:
|
||
return
|
||
|
||
self.train_delay = self.timeEdit_train_delay.time()
|
||
self.train_duration = self.timeEdit_train_duration.time()
|
||
self.pushButton_train_start.setText('stop')
|
||
self.pushButton_train_start.setStyleSheet('color: red')
|
||
self.timeEdit_train_delay.setStyleSheet('color: red')
|
||
|
||
self.timeEdit_train_delay_show()
|
||
else:
|
||
self.timer_train_delay.stop()
|
||
self.timer_train_duration.stop()
|
||
self.command_train_stop()
|
||
self.timeEdit_train_delay.setTime(self.train_delay)
|
||
self.timeEdit_train_duration.setTime(self.train_duration)
|
||
self.pushButton_train_start.setText('start')
|
||
self.pushButton_train_start.setStyleSheet('color: black')
|
||
|
||
def comboBox_command_show(self):
|
||
self.lineEdit_command.setText(self.comboBox_command.currentText())
|
||
|
||
def show_textBrowser_log(self, str):
|
||
self.textBrowser_log.append(str)
|
||
# self.textBrowser_log.moveCursor(self.textBrowser_log.textCursor().End)
|
||
|
||
def closeEvent(self, event):
|
||
self.serial_queue_write.put('exit')
|
||
time.sleep(0.5)
|
||
event.accept()
|
||
|
||
try:
|
||
os._exit(0)
|
||
except Exception as e:
|
||
print(f'GUI closeEvent: {e}')
|
||
|
||
|
||
def csi_data_handle(self, data):
|
||
# Rotate data
|
||
g_csi_amplitude_array[:-1] = g_csi_amplitude_array[1:]
|
||
g_rssi_array[:-1] = g_rssi_array[1:]
|
||
g_radio_header_pd.iloc[1:] = g_radio_header_pd.iloc[:-1]
|
||
|
||
csi_raw_data = data['data']
|
||
data_len = int(data['len']) if 'len' in data else len(csi_raw_data)
|
||
|
||
if data_len == 104:
|
||
for i in range(CSI_DATA_COLUMNS):
|
||
idx = csi_vaid_subcarrier_index[i]
|
||
|
||
if idx * 4 + 3 >= len(csi_raw_data):
|
||
return
|
||
real_high = csi_raw_data[idx * 4 + 1]
|
||
imag_high = csi_raw_data[idx * 4 + 3]
|
||
real_low = csi_raw_data[idx * 4] & 0xFF
|
||
imag_low = csi_raw_data[idx * 4 + 2] & 0xFF
|
||
|
||
real = (((int(real_high) << 12) >> 4) | real_low)
|
||
imag = (((int(imag_high) << 12) >> 4) | imag_low)
|
||
if real > 32767:
|
||
real = real - 65536
|
||
if imag > 32767:
|
||
imag = imag - 65536
|
||
|
||
g_csi_amplitude_array[-1][i] = np.abs(complex(real, imag))
|
||
else:
|
||
for i in range(CSI_DATA_COLUMNS):
|
||
if csi_vaid_subcarrier_index[i]*2+1 >= len(csi_raw_data):
|
||
return
|
||
g_csi_amplitude_array[-1][i] = np.abs(complex(csi_raw_data[csi_vaid_subcarrier_index[i]*2], csi_raw_data[csi_vaid_subcarrier_index[i]*2+1]))
|
||
|
||
g_rssi_array[-1] = data['rssi']
|
||
g_radio_header_pd.loc[0] = data[1:len(CSI_DATA_COLUMNS_NAMES)-1]
|
||
# print(g_radio_header_pd.loc[0])
|
||
|
||
return
|
||
|
||
|
||
def radar_data_handle(self, data):
|
||
g_radar_eigenvalue_array[:-1] = g_radar_eigenvalue_array[1:]
|
||
g_radar_eigenvalue_threshold_array[:-
|
||
1] = g_radar_eigenvalue_threshold_array[1:]
|
||
g_radar_status_array[:-1] = g_radar_status_array[1:]
|
||
|
||
global g_current_time
|
||
|
||
current_time_str = data['timestamp']
|
||
g_current_time = datetime.strptime(
|
||
data['timestamp'], '%Y-%m-%d %H:%M:%S.%f')
|
||
|
||
for taget_index in range(RADAR_targetS_LEN):
|
||
g_radar_eigenvalue_array[-1][taget_index] = data[f'waveform_{RADAR_WAVEFORM_NAMES[taget_index]}']
|
||
g_radar_eigenvalue_threshold_array[-1][taget_index] = data[
|
||
f'waveform_{RADAR_WAVEFORM_NAMES[taget_index]}_threshold']
|
||
g_radar_status_array[-1][taget_index] = data[f'{RADAR_targetS_NAMES[taget_index]}_status']
|
||
|
||
if g_radar_status_array[-1][0] != g_radar_status_array[-2][0] or g_radar_status_array[-1][1] != g_radar_status_array[-2][1]:
|
||
g_status_record_pd[1:] = g_status_record_pd[:-1]
|
||
|
||
g_status_record_pd.loc[0, 'start_time'] = current_time_str
|
||
g_status_record_pd.loc[0, 'room'] = ROOM_STATUS_NAMES[g_radar_status_array[-1][0]]
|
||
g_status_record_pd.loc[0, 'human'] = HUMAN_STATUS_NAMES[g_radar_status_array[-1][1]]
|
||
|
||
if len(g_status_record_pd.loc[1, 'start_time']):
|
||
g_status_record_pd.loc[1, 'stop_time'] = current_time_str
|
||
|
||
if len(g_status_record_pd.loc[0, 'start_time']):
|
||
temp_time = g_current_time - \
|
||
datetime.strptime(
|
||
g_status_record_pd.loc[0, 'start_time'], '%Y-%m-%d %H:%M:%S.%f')
|
||
g_status_record_pd.loc[0, 'spend_time'] = f'{temp_time}'[:-3]
|
||
|
||
if g_radar_status_array[-1][1]:
|
||
index = len(g_move_record_pd)
|
||
g_move_record_pd.loc[index] = [g_current_time.date(), g_current_time.time(
|
||
).hour, g_current_time.time().minute, g_current_time.time().second, 1]
|
||
|
||
g_evaluate_statistics_array[g_radar_status_array[-1,
|
||
0], g_radar_status_array[-1, 1]] += 1
|
||
if (g_radar_eigenvalue_threshold_array[-1, 0] != g_radar_eigenvalue_threshold_array[-2, 0]
|
||
or g_radar_eigenvalue_threshold_array[-1, 1] != g_radar_eigenvalue_threshold_array[-2, 1]):
|
||
self.signal_wareform_threshold.emit()
|
||
|
||
if g_display_eigenvalues_table:
|
||
try:
|
||
g_radar_data_room_pd[1:] = g_radar_data_room_pd[:-1]
|
||
g_radar_data_room_pd.loc[0]['status'] = g_radar_status_array[-1, 0]
|
||
g_radar_data_room_pd.loc[0]['threshold'] = g_radar_eigenvalue_threshold_array[-1, 0]
|
||
g_radar_data_room_pd.loc[0]['value'] = g_radar_eigenvalue_array[-1, 0]
|
||
|
||
value_buff = g_radar_eigenvalue_array[-10:, 0]
|
||
g_radar_data_room_pd.loc[0]['max'] = np.max(value_buff)
|
||
g_radar_data_room_pd.loc[0]['min'] = np.min(value_buff)
|
||
g_radar_data_room_pd.loc[0]['mean'] = np.mean(value_buff)
|
||
g_radar_data_room_pd.loc[0]['std'] = np.std(value_buff)
|
||
|
||
g_radar_data_human_pd[1:] = g_radar_data_human_pd[:-1]
|
||
g_radar_data_human_pd.loc[0]['status'] = g_radar_status_array[-1, 1]
|
||
g_radar_data_human_pd.loc[0]['threshold'] = g_radar_eigenvalue_threshold_array[-1, 1]
|
||
g_radar_data_human_pd.loc[0]['value'] = g_radar_eigenvalue_array[-1, 1]
|
||
|
||
value_buff = g_radar_eigenvalue_array[-10:, 1]
|
||
g_radar_data_human_pd.loc[0]['max'] = np.max(value_buff)
|
||
g_radar_data_human_pd.loc[0]['min'] = np.min(value_buff)
|
||
g_radar_data_human_pd.loc[0]['mean'] = np.mean(value_buff)
|
||
g_radar_data_human_pd.loc[0]['std'] = np.std(value_buff)
|
||
except Exception as e:
|
||
print(e)
|
||
return
|
||
|
||
class DataHandleThread(QThread):
|
||
signal_log_msg = pyqtSignal(str)
|
||
signal_radar_time_domain = pyqtSignal(str)
|
||
signal_exit = pyqtSignal()
|
||
signal_device_info = pyqtSignal(pd.Series)
|
||
signal_wareform_threshold = pyqtSignal()
|
||
|
||
def __init__(self, serial_queue_read):
|
||
super().__init__()
|
||
|
||
self.serial_queue_read = serial_queue_read
|
||
self.taget_count = 0
|
||
|
||
def run(self):
|
||
while True:
|
||
# print(f"g_display_raw_data: {g_display_raw_data}")
|
||
series = self.serial_queue_read.get()
|
||
if series['type'] == 'DEVICE_INFO' and g_display_raw_data:
|
||
g_device_info_series = series.copy()
|
||
self.signal_device_info.emit(series)
|
||
elif series['type'] == 'CSI_DATA' and g_display_raw_data:
|
||
csi_data_handle(self, series)
|
||
elif series['type'] == 'RADAR_DADA' and g_display_radar_model:
|
||
radar_data_handle(self, series)
|
||
elif series['type'] == 'LOG_DATA' and g_display_raw_data:
|
||
if series['tag'] == 'I':
|
||
color = 'green'
|
||
elif series['tag'] == 'W':
|
||
color = 'yellow'
|
||
elif series['tag'] == 'E':
|
||
color = 'red'
|
||
else:
|
||
color = 'white'
|
||
|
||
data = "<font color=\'%s\'>%s (%s) %s <font>" % (
|
||
color, series['tag'], series['timestamp'], series['data'])
|
||
self.signal_log_msg.emit(data)
|
||
elif series['type'] == 'FAIL_EVENT':
|
||
print(f"Failed: {series['data']}")
|
||
|
||
self.signal_exit.emit()
|
||
time.sleep(1)
|
||
sys.exit(0)
|
||
else:
|
||
# print("error: ", series)
|
||
pass
|
||
|
||
QApplication.processEvents()
|
||
|
||
def serial_handle(queue_read, queue_write, port):
|
||
try:
|
||
set = serial.Serial(port=port, baudrate=2000000,
|
||
bytesize=8, parity='N', stopbits=1, timeout=0.1)
|
||
except Exception as e:
|
||
print(f'serial_handle: {e}')
|
||
data_series = pd.Series(index=['type', 'data'],
|
||
data=['FAIL_EVENT', 'Failed to open serial port'])
|
||
queue_read.put(data_series)
|
||
sys.exit()
|
||
return
|
||
|
||
print('open serial port: ', port)
|
||
|
||
# Wait a second to let the port initialize
|
||
set.flushInput()
|
||
|
||
folder_list = ['log', 'data']
|
||
for folder in folder_list:
|
||
if not path.exists(folder):
|
||
mkdir(folder)
|
||
|
||
data_valid_list = pd.DataFrame(columns=['type', 'columns_names', 'file_name', 'file_fd', 'file_writer'],
|
||
data=[['CSI_DATA', CSI_DATA_COLUMNS_NAMES, 'log/csi_data.csv', None, None],
|
||
['RADAR_DADA', RADAR_DATA_COLUMNS_NAMES, 'log/radar_data.csv', None, None],
|
||
['DEVICE_INFO', DEVICE_INFO_COLUMNS_NAMES, 'log/device_info.csv', None, None]])
|
||
|
||
for data_valid in data_valid_list.iloc:
|
||
# print(type(data_valid), data_valid)
|
||
# print(f"file_name: {data_valid['file_name']}")
|
||
data_valid['file_fd'] = open(data_valid['file_name'], 'w')
|
||
data_valid['file_writer'] = csv.writer(data_valid['file_fd'])
|
||
data_valid['file_writer'].writerow(data_valid['columns_names'])
|
||
|
||
log_data_writer = open('log/log_data.txt', 'w+')
|
||
taget_last = 'unknown'
|
||
taget_seq_last = 0
|
||
|
||
set.write('restart\r\n'.encode('utf-8'))
|
||
time.sleep(0.01)
|
||
|
||
while True:
|
||
if not queue_write.empty():
|
||
command = queue_write.get()
|
||
|
||
if command == 'exit':
|
||
sys.exit()
|
||
break
|
||
|
||
command = command + '\r\n'
|
||
set.write(command.encode('utf-8'))
|
||
print(f'{datetime.now()}, serial write: {command}')
|
||
continue
|
||
|
||
try:
|
||
strings = str(set.readline())
|
||
if not strings:
|
||
continue
|
||
except Exception as e:
|
||
data_series = pd.Series(index=['type', 'data'],
|
||
data=['FAIL_EVENT', 'Failed to read serial'])
|
||
queue_read.put(data_series)
|
||
sys.exit()
|
||
|
||
strings = strings.lstrip('b\'').rstrip('\\r\\n\'')
|
||
if not strings:
|
||
continue
|
||
|
||
# print(f"fail: {strings}")
|
||
for data_valid in data_valid_list.iloc:
|
||
index = strings.find(data_valid['type'])
|
||
if index >= 0:
|
||
strings = strings[index:]
|
||
# 只取第一行,避免日志信息混入
|
||
first_line_end = strings.find('\n')
|
||
if first_line_end > 0:
|
||
strings = strings[:first_line_end]
|
||
# 移除可能的回车符
|
||
strings = strings.rstrip('\r')
|
||
|
||
csv_reader = csv.reader(StringIO(strings))
|
||
try:
|
||
data = next(csv_reader)
|
||
except StopIteration:
|
||
continue
|
||
|
||
if len(data) == len(data_valid['columns_names']):
|
||
# 清理 base64 数据字段(最后一个字段),移除可能的日志信息
|
||
if data_valid['type'] == 'CSI_DATA' and len(data) > 0:
|
||
data_field = data[-1]
|
||
# 使用正则表达式查找日志模式,只保留 base64 部分
|
||
log_patterns = [
|
||
r'->valid_len',
|
||
r':\s*\d+', # 冒号后跟数字
|
||
r'\(\d+\)', # 括号中的数字
|
||
r'[DIWE]\s*\(', # 日志级别
|
||
]
|
||
|
||
min_log_start = len(data_field)
|
||
for pattern in log_patterns:
|
||
match = re.search(pattern, data_field)
|
||
if match:
|
||
min_log_start = min(min_log_start, match.start())
|
||
|
||
# 如果找到日志信息且位置合理(避免误判),只保留 base64 部分
|
||
if min_log_start < len(data_field) and min_log_start > 10:
|
||
data[-1] = data_field[:min_log_start]
|
||
|
||
data_series = pd.Series(
|
||
data, index=data_valid['columns_names'])
|
||
|
||
try:
|
||
datetime.strptime(
|
||
data_series['timestamp'], '%Y-%m-%d %H:%M:%S.%f')
|
||
except Exception as e:
|
||
data_series['timestamp'] = datetime.now().strftime(
|
||
'%Y-%m-%d %H:%M:%S.%f')[:-3]
|
||
# print(e)
|
||
|
||
# print(data_series)
|
||
if data_series['type'] == 'CSI_DATA':
|
||
try:
|
||
# csi_raw_data = json.loads(data_series['data'])
|
||
csi_raw_data = base64_decode_bin(
|
||
data_series['data'])
|
||
# 如果解码失败返回空列表,跳过这条数据
|
||
if len(csi_raw_data) == 0:
|
||
print(f"CSI_DATA decode failed, skipping data: {data_series['data'][:50]}...")
|
||
break
|
||
if len(csi_raw_data) != int(data_series['len']):
|
||
# if len(csi_raw_data) != 104 and len(csi_raw_data) != 216 and len(csi_raw_data) != 328 and len(csi_raw_data) != 552:
|
||
print(
|
||
f"CSI_DATA, expected: {data_series['len']}, actual: {len(csi_raw_data)}")
|
||
break
|
||
except Exception as e:
|
||
print(
|
||
f"json.JSONDecodeError: {e}, data: {data_series['data']}")
|
||
break
|
||
|
||
data_series['data'] = csi_raw_data
|
||
|
||
if data_series['taget'] != 'unknown':
|
||
if data_series['taget'] != taget_last or data_series['taget_seq'] != taget_seq_last:
|
||
folder = f"data/{data_series['taget']}"
|
||
if not path.exists(folder):
|
||
mkdir(folder)
|
||
|
||
csi_target_data_file_name = f"{folder}/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f')[:-3]}_{data_series['len']}_{data_series['taget_seq']}.csv"
|
||
print(csi_target_data_file_name)
|
||
csi_target_data_file_fd = open(
|
||
csi_target_data_file_name, 'w+')
|
||
taget_data_writer = csv.writer(
|
||
csi_target_data_file_fd)
|
||
taget_data_writer.writerow(data_series.index)
|
||
|
||
taget_data_writer.writerow(
|
||
data_series.astype(str))
|
||
|
||
taget_last = data_series['taget']
|
||
taget_seq_last = data_series['taget_seq']
|
||
|
||
if queue_read.full():
|
||
# print('============== queue_full ==========')
|
||
pass
|
||
else:
|
||
# print("data_series", len(data_series), type(data_series), data_series)
|
||
queue_read.put(data_series)
|
||
else:
|
||
queue_read.put(data_series)
|
||
|
||
data_valid['file_writer'].writerow(data_series.astype(str))
|
||
data_valid['file_fd'].flush()
|
||
break
|
||
else:
|
||
strings = re.sub(r'\\x1b.*?m', '', strings)
|
||
log_data_writer.writelines(strings + '\n')
|
||
|
||
log = re.match(r'.*([DIWE]) \((\d+)\) (.*)', strings, re.I)
|
||
|
||
if not log:
|
||
continue
|
||
|
||
data_series = pd.Series(index=['type', 'tag', 'timestamp', 'data'],
|
||
data=['LOG_DATA', log.group(1), log.group(2), log.group(3)])
|
||
if not queue_read.full():
|
||
queue_read.put(data_series)
|
||
|
||
|
||
def quit(signum, frame):
|
||
print('Exit the system')
|
||
sys.exit()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
if sys.version_info < (3, 6):
|
||
print(' Python version should >= 3.6')
|
||
exit()
|
||
|
||
parser = argparse.ArgumentParser(
|
||
description='Read CSI data from serial port and display it graphically')
|
||
parser.add_argument('-p', '--port', dest='port', action='store', required=True,
|
||
help='Serial port number of csv_recv device')
|
||
parser.add_argument('-t', '--csi_output_type', dest='csi_output_type', action='store',
|
||
choices=['LLTF', 'HT_LTF', 'HE_LTF', 'STBC-HT-LTF', 'STBC-HE-LTF'], default='LLTF',
|
||
help='CSI output type: LLTF, HT_LTF, HE_LTF, STBC-HT-LTF, or STBC-HE-LTF (default: LLTF)')
|
||
|
||
args = parser.parse_args()
|
||
serial_port = args.port
|
||
csi_output_type = args.csi_output_type
|
||
|
||
serial_queue_read = Queue(maxsize=64)
|
||
serial_queue_write = Queue(maxsize=64)
|
||
|
||
signal_key.signal(signal_key.SIGINT, quit)
|
||
signal_key.signal(signal_key.SIGTERM, quit)
|
||
|
||
serial_handle_process = Process(target=serial_handle, args=(
|
||
serial_queue_read, serial_queue_write, serial_port))
|
||
serial_handle_process.start()
|
||
|
||
app = QApplication(sys.argv)
|
||
app.setWindowIcon(QIcon('../../../docs/_static/icon.png'))
|
||
|
||
window = DataGraphicalWindow(serial_queue_write, csi_output_type)
|
||
data_handle_thread = DataHandleThread(serial_queue_read)
|
||
data_handle_thread.signal_device_info.connect(window.show_device_info)
|
||
data_handle_thread.signal_log_msg.connect(window.show_textBrowser_log)
|
||
data_handle_thread.signal_exit.connect(window.close)
|
||
data_handle_thread.start()
|
||
|
||
window.show()
|
||
sys.exit(app.exec())
|
||
serial_handle_process.join()
|