444 lines
18 KiB
Python
444 lines
18 KiB
Python
# coding=gb2312
|
||
import json
|
||
import os.path
|
||
import sys
|
||
|
||
from Database.manager_database import *
|
||
from Speaker.speak_base import SpeakServer, beep
|
||
from UWB.positioning_standalone_v2 import *
|
||
|
||
FINISH_LINE_SIZE1 = 0
|
||
FINISH_LINE_SIZE2 = 1
|
||
RUNNING_COUNTING = "cnt"
|
||
RUNNING_STATUS = "stat"
|
||
RUNNING_MES = "mes"
|
||
STATUS_ACROSS = "across"
|
||
STATUS_EXIT = "exit"
|
||
RUNNING_LAST_STATUS_TIME = "lst"
|
||
RUNNING_COST = "cost"
|
||
RUNNING_DONE = 'done'
|
||
|
||
PATH = sys.path[0]
|
||
ANCHOR_RSSI_ADJUSTMENT_PATH = f"{PATH}/Exercise3/anchor_adjustment.json"
|
||
|
||
|
||
def score_compute(age, cost, score_data):
|
||
my_age_index = 0
|
||
for age_index in range(len(score_data["age"])):
|
||
candidate_age = score_data["age"][age_index]
|
||
if candidate_age[0] <= age <= candidate_age[1]:
|
||
my_age_index = age_index
|
||
break
|
||
my_score_index = len(score_data["values"]) - 1
|
||
for value_index in range(len(score_data["values"]) - 1, 0 - 1, -1):
|
||
value = score_data["values"][value_index][my_age_index]
|
||
if cost <= value[0] * 60 + value[1]:
|
||
my_score_index = value_index
|
||
else:
|
||
break
|
||
if cost > score_data["values"][-1][my_age_index][0] * 60 + score_data["values"][-1][my_age_index][1]:
|
||
my_base_score = 0
|
||
else:
|
||
my_base_score = score_data["score"][my_score_index]
|
||
if my_base_score == 100:
|
||
value = score_data["values"][my_score_index][my_age_index]
|
||
base_score = value[0] * 60 + value[1]
|
||
my_score = my_base_score + (base_score - cost) // 5 * 1
|
||
else:
|
||
my_score = my_base_score
|
||
|
||
return my_score
|
||
|
||
|
||
class Running:
|
||
|
||
def __init__(self, positioning: Positioning, round_num=3, min_round_time=30) -> None:
|
||
self.min_round_time = float(min_round_time)
|
||
self.round_num = int(round_num)
|
||
self.positioning = positioning
|
||
self.positioning_tag = None
|
||
self.finish_line = {}
|
||
# 起跑时间
|
||
self.start_time = 0
|
||
# 结束时间
|
||
self.stop_time = 0
|
||
# 跑步记录
|
||
self.running_record = {}
|
||
# 跑步流水记录
|
||
self.running_record_list = {}
|
||
# 跑步圈时记录
|
||
self.round_record = {}
|
||
# 记录手环
|
||
self.test_tag = []
|
||
# 手环对应人员信息
|
||
self.person_mes = {}
|
||
# 是否起跑状态
|
||
self.__start_running = threading.Event()
|
||
self.__start_running.clear()
|
||
# 说话
|
||
self.speak_driver = SpeakServer()
|
||
self.speak_driver.start()
|
||
# 成绩文件
|
||
file = open(f"{PATH}/Exercise3/running_score.json")
|
||
self.score_data = json.load(file)
|
||
# 基站校准数据
|
||
anchor_list = list(positioning.multi_uwb.uwb_driver_table.keys())
|
||
default_effective_rssi = -84
|
||
if os.path.exists(ANCHOR_RSSI_ADJUSTMENT_PATH):
|
||
with open(ANCHOR_RSSI_ADJUSTMENT_PATH, "r", encoding="utf-8_sig") as file:
|
||
self.anchor_rssi_adjustment = json.load(file)
|
||
if len(self.anchor_rssi_adjustment) < len(anchor_list):
|
||
self.anchor_rssi_adjustment = {
|
||
anchor: default_effective_rssi
|
||
for anchor in anchor_list
|
||
}
|
||
with open(ANCHOR_RSSI_ADJUSTMENT_PATH, "w", encoding="utf-8_sig") as file:
|
||
json.dump(self.anchor_rssi_adjustment, file)
|
||
else:
|
||
self.anchor_rssi_adjustment = {
|
||
anchor: default_effective_rssi
|
||
for anchor in anchor_list
|
||
}
|
||
with open(ANCHOR_RSSI_ADJUSTMENT_PATH, "w", encoding="utf-8_sig") as file:
|
||
json.dump(self.anchor_rssi_adjustment, file)
|
||
|
||
# 跑步记录数据
|
||
self.final_data = {}
|
||
# 是否播报
|
||
self.is_play = False
|
||
|
||
def set_play(self, is_play):
|
||
self.is_play = is_play
|
||
|
||
def update_and_play(self, tag, new_record):
|
||
if self.is_play:
|
||
this_record = self.running_record[tag]
|
||
if this_record[RUNNING_COUNTING] != new_record[RUNNING_COUNTING]:
|
||
person_name = self.person_mes[tag][NAME]
|
||
person_id = self.person_mes[tag][ID]
|
||
if new_record[RUNNING_DONE]:
|
||
self.speak_driver.add_speak(f"{person_name}完成考试!")
|
||
else:
|
||
self.speak_driver.add_speak(f"{person_name}通过第{new_record[RUNNING_COUNTING]}圈!")
|
||
self.running_record[tag] = new_record
|
||
|
||
def set_config(self, round_num, min_round_time):
|
||
self.min_round_time = float(min_round_time)
|
||
self.round_num = int(round_num)
|
||
|
||
def add_tag(self, tag):
|
||
self.test_tag.append(tag)
|
||
|
||
def del_tag(self, tag):
|
||
self.test_tag.remove(tag)
|
||
del self.person_mes[tag]
|
||
|
||
def add_tag_mes(self, tag, mes):
|
||
self.person_mes[tag] = mes
|
||
|
||
def reset(self):
|
||
self.test_tag.clear()
|
||
self.person_mes.clear()
|
||
self.start_time = 0
|
||
|
||
def start(self):
|
||
# 清除缓存
|
||
self.__start_running.clear()
|
||
self.round_record.clear()
|
||
self.positioning.clear_information()
|
||
self.positioning.resume()
|
||
# 初始化记录
|
||
self.running_record = {
|
||
tag: {
|
||
RUNNING_COUNTING: -1,
|
||
RUNNING_STATUS: STATUS_EXIT,
|
||
RUNNING_MES: "未起始",
|
||
RUNNING_COST: 0,
|
||
RUNNING_DONE: False,
|
||
}
|
||
for tag in self.test_tag
|
||
}
|
||
# 起跑播报
|
||
self.speak_driver.add_speak("准备开始考试,请就位,倒计时:")
|
||
counting = 5
|
||
for i in range(counting):
|
||
start_time = time.time()
|
||
self.speak_driver.add_speak(counting - i)
|
||
while time.time() - start_time < 1:
|
||
time.sleep(0.05)
|
||
self.speak_driver.wait_4_speak()
|
||
self.start_time = time.time()
|
||
self.positioning.set_valid_time(self.start_time)
|
||
beep(duration=700, freq=640)
|
||
self.__start_running.set()
|
||
# 启动计算线程
|
||
threading.Thread(target=self._thread_running, daemon=True).start()
|
||
threading.Thread(target=self._thread_processing, daemon=True).start()
|
||
|
||
def kill(self):
|
||
# 记录手环
|
||
self.test_tag.clear()
|
||
self.running_record_list.clear()
|
||
self.__start_running.clear()
|
||
|
||
def stop(self):
|
||
# # 起跑时间
|
||
self.stop_time = time.time()
|
||
# self.start_time = 0
|
||
self.kill()
|
||
# 计算最终成绩
|
||
self.final_data.clear()
|
||
raw_score = self.get_score()
|
||
for score in raw_score:
|
||
band_id = score["band_id"]
|
||
person_mes = self.person_mes[band_id]
|
||
tag_mes = self.running_record[band_id]
|
||
score.update(person_mes)
|
||
score.update(tag_mes)
|
||
person_id = score["id"]
|
||
round_time = self.round_record.get(person_id)
|
||
round_time_record = round_time if round_time else []
|
||
score.update({"round_time": round_time_record})
|
||
score.update({
|
||
"fix": False, "final_result": round_time_record
|
||
})
|
||
self.final_data[person_id] = score
|
||
|
||
# 获得当前所有成绩
|
||
def get_all_score(self):
|
||
return list(self.final_data.values())
|
||
|
||
# 成绩修复
|
||
def fix_score(self, person_id):
|
||
if not self.final_data[person_id][RUNNING_DONE]:
|
||
detected_round = len(self.final_data[person_id]["round_time"])
|
||
lacked_round = self.round_num - detected_round + 1
|
||
result = [
|
||
self.final_data[person_id]["round_time"][i]
|
||
for i in range(detected_round)
|
||
]
|
||
# 如果没有成绩,按最终完成时间去计算
|
||
if len(result) <= 1:
|
||
total_time = self.stop_time - self.start_time
|
||
result = [total_time / (self.round_num + 1) for i in range(self.round_num + 1)]
|
||
# 如果有成绩
|
||
else:
|
||
for _ in range(lacked_round):
|
||
max_result = max(result)
|
||
index = result.index(max_result)
|
||
result = result[0:index:] + [max_result / 2, max_result / 2] + result[index + 1::]
|
||
self.final_data[person_id]["final_result"] = {
|
||
i: result[i] for i in range(len(result))
|
||
}
|
||
self.final_data[person_id]["fix"] = True
|
||
self.final_data[person_id]["total_time"] = sum(result)
|
||
|
||
# 修复撤销
|
||
def fix_withdraw(self, person_id):
|
||
self.final_data[person_id]["final_result"] = self.final_data[person_id]["round_time"]
|
||
self.final_data[person_id]["fix"] = False
|
||
|
||
def _thread_processing(self):
|
||
while self.__start_running.is_set():
|
||
data = self.positioning.get_last_information()
|
||
try:
|
||
# 过滤无效操作
|
||
if data:
|
||
record_time, mes = data
|
||
# print(self.start_time - record_time, len(self.positioning.tag_information))
|
||
# if record_time < self.start_time - 3:
|
||
# continue
|
||
tag = mes[TAG]
|
||
if tag not in self.test_tag:
|
||
continue
|
||
distance = mes[DIST]
|
||
rssi = mes[RSSI]
|
||
anchor = mes[ANCHOR_ID]
|
||
self.running_record_list.setdefault(tag, [])
|
||
self.running_record_list[tag].append(
|
||
{RECORD: record_time, DIST: distance, RSSI: rssi, ANCHOR_ID: anchor}
|
||
)
|
||
except Exception as e:
|
||
traceback.format_exc()
|
||
print(f"获取数据时发生错误:{e.args}")
|
||
|
||
def _thread_running(self):
|
||
# 提前开始检测时间
|
||
bias = 3
|
||
# 判定周期时间
|
||
judge_window_time = 6
|
||
detect_time_range = judge_window_time / 2
|
||
# 有效进入距离
|
||
effective_distance = 300
|
||
# effective_rssi = -84
|
||
# 开始判定距离
|
||
judge_distance = 700
|
||
# 离开判定时间
|
||
leave_detect_time = 10
|
||
while self.__start_running.is_set():
|
||
this_time = time.time()
|
||
for tag, record in self.running_record_list.copy().items():
|
||
if tag not in self.test_tag or self.running_record[tag][RUNNING_DONE]:
|
||
continue
|
||
# 获取测试人员id
|
||
person_id = self.person_mes[tag]["id"]
|
||
sorted_record = sorted(record, key=lambda x: x[RECORD])
|
||
this_tag = {
|
||
RUNNING_COUNTING: -1,
|
||
RUNNING_STATUS: STATUS_EXIT,
|
||
RUNNING_MES: "离开",
|
||
RUNNING_COST: 0,
|
||
RUNNING_DONE: False
|
||
}
|
||
# Cache
|
||
# 上次进入检测区域的时间
|
||
last_enter_time = self.start_time
|
||
# 上次离开检测区域的时间
|
||
last_leave_time = -1
|
||
# 最后一条记录的时间
|
||
record_time = self.start_time
|
||
# print("===============================================================================================")
|
||
for one_record in sorted_record:
|
||
# print(one_record)
|
||
try:
|
||
# 消除无效数据
|
||
if one_record[RECORD] < self.start_time - bias or this_time - one_record[RECORD] < detect_time_range:
|
||
continue
|
||
record_time = one_record[RECORD]
|
||
distance = one_record[DIST]
|
||
rssi = one_record[RSSI]
|
||
anchor = one_record[ANCHOR_ID]
|
||
# 更新时间窗口中的数据,未来时间一段时间内的数据。
|
||
frontward_window = list(
|
||
filter(
|
||
lambda x: x[DIST] is not None and -detect_time_range <= record_time - x[RECORD] < 0,
|
||
sorted_record
|
||
)
|
||
)
|
||
# 离开判定1
|
||
if (
|
||
# 距离上次进入冲线条件的时间已经有一段时间
|
||
record_time - last_leave_time > leave_detect_time
|
||
and this_tag[RUNNING_STATUS] == STATUS_ACROSS
|
||
):
|
||
this_tag[RUNNING_STATUS] = STATUS_EXIT
|
||
this_tag[RUNNING_MES] = "离开"
|
||
# print("离开判定")
|
||
|
||
# 获取有效信号值
|
||
effective_rssi = self.anchor_rssi_adjustment[anchor]
|
||
|
||
# 冲线判定
|
||
if (
|
||
(
|
||
# CASE1:如果未来距离没有数据
|
||
len(frontward_window) == 0
|
||
and (
|
||
# 并且检测到距离小于判定距离
|
||
(distance is not None and distance < judge_distance)
|
||
# 或者检测到的信号强度强于检测强度
|
||
or rssi >= effective_rssi
|
||
)
|
||
) or (
|
||
# CASE2:如果未来还有数据
|
||
len(frontward_window) > 0
|
||
and distance is not None
|
||
and distance <= effective_distance
|
||
# 未来距离都比现在大
|
||
and distance - min([fw[DIST] for fw in frontward_window]) <= 20
|
||
)
|
||
):
|
||
# print("冲线判定")
|
||
if (
|
||
# 并且当前时间距离上次已经大于单圈时间,并且已经离开了
|
||
record_time - last_leave_time > self.min_round_time
|
||
and this_tag[RUNNING_STATUS] == STATUS_EXIT
|
||
):
|
||
# print("圈数判定")
|
||
this_tag[RUNNING_STATUS] = STATUS_ACROSS
|
||
this_tag[RUNNING_MES] = "冲线"
|
||
this_tag[RUNNING_COUNTING] += 1
|
||
# 圈数时间记录
|
||
self.round_record.setdefault(person_id, {})
|
||
this_round_cost = 0 if record_time - last_enter_time < 0 else record_time - last_enter_time
|
||
self.round_record[person_id][this_tag[RUNNING_COUNTING]] = this_round_cost
|
||
last_enter_time += this_round_cost
|
||
# 结束判定
|
||
if this_tag[RUNNING_COUNTING] == self.round_num:
|
||
this_tag[RUNNING_DONE] = True
|
||
this_tag[RUNNING_COST] = record_time - self.start_time
|
||
break
|
||
last_leave_time = record_time
|
||
# 如果还处于冲线状态,并且距离上次检测时间不超过离开判定周期,更新冲线时间
|
||
if (
|
||
this_tag[RUNNING_STATUS] == STATUS_ACROSS
|
||
# and record_time - last_leave_time < detect_time_range
|
||
):
|
||
last_leave_time = record_time
|
||
except Exception as e:
|
||
print(traceback.format_exc())
|
||
print(f"计算时发生错误:{e.args}")
|
||
|
||
# 离开判定2
|
||
if (
|
||
# 最后离开的时间已经超过了离开判定时间
|
||
this_time - last_leave_time > leave_detect_time
|
||
# 最后的一条数据距离现在已经超过了离开判定时间
|
||
and this_time - record_time > leave_detect_time
|
||
and this_tag[RUNNING_STATUS] == STATUS_ACROSS
|
||
):
|
||
this_tag[RUNNING_STATUS] = STATUS_EXIT
|
||
this_tag[RUNNING_MES] = "离开"
|
||
|
||
if this_time - last_leave_time > self.min_round_time and this_tag[RUNNING_STATUS] == STATUS_EXIT:
|
||
this_tag[RUNNING_MES] = "回程"
|
||
# print(this_tag)
|
||
# 更新记录
|
||
self.update_and_play(tag=tag, new_record=this_tag)
|
||
# 休息一下
|
||
time.sleep(0.5)
|
||
|
||
def get_person_round_time(self, person_id):
|
||
if person_id not in self.final_data.keys():
|
||
return {}
|
||
return self.final_data[person_id]["final_result"]
|
||
|
||
def get_valid_score(self):
|
||
result = []
|
||
for person_id, data in self.final_data.items():
|
||
if data["done"] or data["fix"]:
|
||
data["score"] = score_compute(data["age"], data["total_time"], self.score_data)
|
||
result.append(data)
|
||
return result
|
||
|
||
# 获取成绩接口
|
||
def get_score(self):
|
||
score = []
|
||
this_cost_time = time.time() - self.start_time
|
||
# print(self.running_record)
|
||
for tag, record in self.running_record.items():
|
||
if record[RUNNING_COUNTING] < 0:
|
||
finish_status = "未起始"
|
||
else:
|
||
if record[RUNNING_COUNTING] == 0:
|
||
finish_status = f"已开始({record[RUNNING_MES]})"
|
||
else:
|
||
finish_status = f"考试中({record[RUNNING_MES]})"
|
||
if record[RUNNING_COUNTING] == self.round_num:
|
||
finish_status = "已完成考试"
|
||
age = self.person_mes[tag]["age"]
|
||
one = {
|
||
"band_id": tag,
|
||
"score": score_compute(age, record[RUNNING_COST], self.score_data)
|
||
if record[RUNNING_COUNTING] == self.round_num else 0,
|
||
"total_time": record[RUNNING_COST]
|
||
if record[RUNNING_DONE] or self.start_time == 0 else this_cost_time,
|
||
"round": record[RUNNING_COUNTING] if record[RUNNING_COUNTING] > 0 else 0,
|
||
"finish": finish_status,
|
||
"percentage": record[RUNNING_COUNTING] / self.round_num * 100
|
||
if record[RUNNING_COUNTING] > 0 else 0
|
||
}
|
||
score.append(one)
|
||
# print(score)
|
||
return score
|