修改代码与文档

This commit is contained in:
jiang 2024-09-12 17:14:01 +08:00
parent 7c63e11132
commit 3a1c18c29d
5 changed files with 424 additions and 233 deletions

View File

@ -2,7 +2,7 @@ import redis
import os import os
# Redis config # Redis config
redis_host = str(os.getenv("REDIS_HOST", 'localhost')) redis_host = str(os.getenv("REDIS_HOST", '192.168.0.14'))
redis_port = int(os.getenv("REDIS_PORT", 2012)) redis_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!')) redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
num_workers = int(os.getenv("NUM_WORKERS", 10)) num_workers = int(os.getenv("NUM_WORKERS", 10))

View File

@ -20,17 +20,16 @@ from models import FaceRecoger, FaceBoxesV2, Landmark5er, FaceAlign, QualityOfCl
so = onnxruntime.SessionOptions() so = onnxruntime.SessionOptions()
so.log_severity_level = 3 # 0=VERBOSE, 1=INFO, 2=WARNING, 3=ERROR, 4=FATAL so.log_severity_level = 3 # 0=VERBOSE, 1=INFO, 2=WARNING, 3=ERROR, 4=FATAL
# # 获取workers
# 获取workers # if "NUM_WORKERS" not in os.environ:
if "NUM_WORKERS" not in os.environ: # raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
NUM_WORKERS = int(os.getenv("NUM_WORKERS", 10)) NUM_WORKERS = int(os.getenv("NUM_WORKERS", 10))
# max readers # max readers
max_readers = int(os.getenv("MAX_READERS", 60)) max_readers = int(os.getenv("MAX_READERS", 60))
# 连接到 Redis # 连接到 Redis
redis_host = str(os.getenv("REDIS_HOST", 'localhost')) redis_host = str(os.getenv("REDIS_HOST", '192.168.0.14'))
redis_port = int(os.getenv("REDIS_PORT", 2012)) redis_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!')) redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
# connected # connected
@ -73,26 +72,31 @@ ErrorMsg = {
"30019": "identity already exists; for database protection, operation rejected at this time" "30019": "identity already exists; for database protection, operation rejected at this time"
} }
class FileError(Exception): class FileError(Exception):
def __init__(self, arg: str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class NotImpltError(Exception): class NotImpltError(Exception):
def __init__(self, arg: str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class FaceError(Exception): class FaceError(Exception):
def __init__(self, arg: str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class UpdatedbError(Exception): class UpdatedbError(Exception):
def __init__(self, arg: str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
# setting Logger # setting Logger
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
log_dir = f"{os.path.dirname(os.path.abspath(__file__))}/log" log_dir = f"{os.path.dirname(os.path.abspath(__file__))}/log"
@ -102,6 +106,7 @@ logging.basicConfig(filename=f'{log_dir}/{current_time}.log', level=logging.INFO
logger = logging.getLogger(__name__) # @@@@ logger = logging.getLogger(__name__) # @@@@
print(log_dir) print(log_dir)
def list_images(path: str): def list_images(path: str):
""" """
List images in a given path List images in a given path
@ -150,6 +155,7 @@ def find_image_hash(file_path: str) -> str:
hasher.update(properties.encode("utf-8")) hasher.update(properties.encode("utf-8"))
return hasher.hexdigest() return hasher.hexdigest()
# 支持base64 local-path url 等多种检索图片的方式,返回 numpy # 支持base64 local-path url 等多种检索图片的方式,返回 numpy
def load_img(img_path: str): def load_img(img_path: str):
image = None image = None
@ -206,16 +212,21 @@ class FaceHelper:
config = yaml.safe_load(f) config = yaml.safe_load(f)
self.sim_threshold = config['faceReg']['sim_threshold'] # 0.7 self.sim_threshold = config['faceReg']['sim_threshold'] # 0.7
self.rotclsifer = onnxruntime.InferenceSession( config['ck_paths']['rotifer'], so) # "./checkpoints/model_gray_mobilenetv2_rotcls.onnx" self.rotclsifer = onnxruntime.InferenceSession(config['ck_paths']['rotifer'],
so) # "./checkpoints/model_gray_mobilenetv2_rotcls.onnx"
self.db_path = os.path.join(db_dir, "seetaface6.pkl").lower() self.db_path = os.path.join(db_dir, "seetaface6.pkl").lower()
self.fd = FaceBoxesV2(config['ck_paths']['FcBx'], config['ck_paths']['num_threads'] ) # r"./checkpoints/faceboxesv2-640x640.onnx" 4 self.fd = FaceBoxesV2(config['ck_paths']['FcBx'],
self.ld5er = Landmark5er( onnx_path1 = config['ck_paths']['landmk1'], # "./checkpoints/face_landmarker_pts5_net1.onnx", config['ck_paths']['num_threads']) # r"./checkpoints/faceboxesv2-640x640.onnx" 4
onnx_path2 = config['ck_paths']['landmk2'], # "./checkpoints/face_landmarker_pts5_net2.onnx", self.ld5er = Landmark5er(onnx_path1=config['ck_paths']['landmk1'],
# "./checkpoints/face_landmarker_pts5_net1.onnx",
onnx_path2=config['ck_paths']['landmk2'],
# "./checkpoints/face_landmarker_pts5_net2.onnx",
num_threads=config['ck_paths']['num_threads'] # 4 num_threads=config['ck_paths']['num_threads'] # 4
) )
self.fa = FaceAlign() self.fa = FaceAlign()
self.fr = FaceRecoger(onnx_path = config['ck_paths']['FcReg'], num_threads= config['ck_paths']['num_threads'] ) # "./checkpoints/face_recognizer.onnx" 4 self.fr = FaceRecoger(onnx_path=config['ck_paths']['FcReg'],
num_threads=config['ck_paths']['num_threads']) # "./checkpoints/face_recognizer.onnx" 4
self.qc = QualityChecker(config['brightness']['v0'], config['brightness']['v1'], self.qc = QualityChecker(config['brightness']['v0'], config['brightness']['v1'],
config['brightness']['v2'], config['brightness']['v3'], config['brightness']['v2'], config['brightness']['v3'],
hw=(config['resolution']['height'], config['resolution']['width']) hw=(config['resolution']['height'], config['resolution']['width'])
@ -224,7 +235,8 @@ class FaceHelper:
var_onnx_path=config['pose']['var_onnx_path'], # './checkpoints/fsanet-var.onnx', var_onnx_path=config['pose']['var_onnx_path'], # './checkpoints/fsanet-var.onnx',
conv_onnx_path=config['pose']['conv_onnx_path'], # './checkpoints/fsanet-conv.onnx' conv_onnx_path=config['pose']['conv_onnx_path'], # './checkpoints/fsanet-conv.onnx'
) )
self.qclarity = QualityOfClarity(low_thresh=config['clarity']['low_thrd'], high_thresh=config['clarity']['high_thrd']) self.qclarity = QualityOfClarity(low_thresh=config['clarity']['low_thrd'],
high_thresh=config['clarity']['high_thrd'])
# refresh the db # refresh the db
try: try:
@ -316,10 +328,8 @@ class FaceHelper:
# 只是提取人脸特征,不要对别的有任何影响 # 只是提取人脸特征,不要对别的有任何影响
def featureDetect(self, img_path: str): def featureDetect(self, img_path: str):
rw_lock.acquire_read() rw_lock.acquire_read()
try: try:
image = load_img(img_path) # get bgr numpy image image = load_img(img_path) # get bgr numpy image
unknown_embeddings = [] unknown_embeddings = []
image = self.rotadjust(image) # 调整角度 image = self.rotadjust(image) # 调整角度
detect_result = self.fd.detect(image) detect_result = self.fd.detect(image)
@ -341,7 +351,7 @@ class FaceHelper:
ret_data = [] ret_data = []
for _, (facebox, feature) in enumerate(zip(detect_result, unknown_embeddings)): for _, (facebox, feature) in enumerate(zip(detect_result, unknown_embeddings)):
code = self.check_face("None", image, facebox, prefix='featuredetect') code = self.check_face("None", image, facebox, prefix='featuredetect')
ret_data.append({'code':code, 'msg': ErrorMsg[code], 'data': [f"{feature.tobytes()},{str(feature.dtype)}", facebox]}) ret_data.append({'code': code, 'msg': ErrorMsg[code], 'data': [{str(num) for num in feature}, facebox]})
if len(ret_data) != 1: if len(ret_data) != 1:
ret_data = {'code': "30008", 'msg': ErrorMsg["30008"], 'data': ret_data} ret_data = {'code': "30008", 'msg': ErrorMsg["30008"], 'data': ret_data}
@ -371,14 +381,16 @@ class FaceHelper:
self.db_embeddings, self.db_identities = None, None self.db_embeddings, self.db_identities = None, None
else: else:
self.db_embeddings = np.array([rep["embedding"] for rep in representations], dtype=np.float32) self.db_embeddings = np.array([rep["embedding"] for rep in representations], dtype=np.float32)
self.db_identities = [os.path.splitext(os.path.basename(rep["identity"]))[0] for rep in representations] self.db_identities = [os.path.splitext(os.path.basename(rep["identity"]))[0] for rep in
representations]
redis_client.set(f"worker_{self.pid}", 1) # 同步完毕 redis_client.set(f"worker_{self.pid}", 1) # 同步完毕
img = load_img(img_path) img = load_img(img_path)
img = self.rotadjust(img) # 调整角度 img = self.rotadjust(img) # 调整角度
if opt in ["add", "replace"]: if opt in ["add", "replace"]:
if opt == "add" and self.db_identities is not None and identity in self.db_identities: if opt == "add" and self.db_identities is not None and identity in self.db_identities:
raise UpdatedbError("30019") # push: identity has exist. to pretect the db, reject opt of this time raise UpdatedbError(
"30019") # push: identity has exist. to pretect the db, reject opt of this time
else: else:
detect_result = self.fd.detect(img) detect_result = self.fd.detect(img)
@ -391,12 +403,15 @@ class FaceHelper:
areas = [box.area() for box in detect_result] areas = [box.area() for box in detect_result]
max_idx = areas.index(max(areas)) max_idx = areas.index(max(areas))
facebox = detect_result[max_idx] facebox = detect_result[max_idx]
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point facebox = (
FaceError_number = self.check_face(img_path=img_path[:200], img=img, facebox=facebox, prefix='update') facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
FaceError_number = self.check_face(img_path=img_path[:200], img=img, facebox=facebox,
prefix='update')
if FaceError_number != "30002": if FaceError_number != "30002":
raise FaceError(FaceError_number) raise FaceError(FaceError_number)
cv2.imwrite(os.path.join(self.db_dir, identity+'.jpg'),img,[cv2.IMWRITE_JPEG_QUALITY, 100]) # 如果file已经存在则会替换它 cv2.imwrite(os.path.join(self.db_dir, identity + '.jpg'), img,
[cv2.IMWRITE_JPEG_QUALITY, 100]) # 如果file已经存在则会替换它
elif opt == "delete": elif opt == "delete":
try: try:
@ -405,7 +420,6 @@ class FaceHelper:
pass pass
else: else:
raise NotImpltError("30007") # push: this updateDB type is not support raise NotImpltError("30007") # push: this updateDB type is not support
print("end the updateDB") print("end the updateDB")
logger.info(f"end the updateDB") logger.info(f"end the updateDB")
@ -446,7 +460,9 @@ class FaceHelper:
storage_images[idx] = base_path + '.jpg' storage_images[idx] = base_path + '.jpg'
must_save_pickle = False must_save_pickle = False
new_images = []; old_images = []; replaced_images = [] new_images = [];
old_images = [];
replaced_images = []
new_images = list(set(storage_images) - set(pickle_images)) new_images = list(set(storage_images) - set(pickle_images))
old_images = list(set(pickle_images) - set(storage_images)) old_images = list(set(pickle_images) - set(storage_images))
@ -492,7 +508,8 @@ class FaceHelper:
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
if check: if check:
FaceError_number = self.check_face(img_path=new_image[:200], img=image, facebox=facebox, prefix='refreshdb') FaceError_number = self.check_face(img_path=new_image[:200], img=image, facebox=facebox,
prefix='refreshdb')
if FaceError_number != "30002": if FaceError_number != "30002":
continue continue
@ -631,3 +648,70 @@ class FaceHelper:
return "30017" # poor clarity of face in the image return "30017" # poor clarity of face in the image
return "30002" return "30002"
def imageComparison(self, firstImage, secondImage):
rw_lock.acquire_read()
try:
# Load and adjust images
imageFirst = self.rotadjust(load_img(firstImage))
imageSecond = self.rotadjust(load_img(secondImage))
print("1")
# Detect faces
detect_result_first = self.fd.detect(imageFirst)
detect_result_second = self.fd.detect(imageSecond)
detect_result_first = [(box.x1, box.y1, box.x2, box.y2) for box in detect_result_first]
detect_result_second = [(box.x1, box.y1, box.x2, box.y2) for box in detect_result_second]
if len(detect_result_first) < 1:
raise FaceError("30018")
if len(detect_result_second) < 1:
raise FaceError("30018")
if len(detect_result_first) > 1:
raise FaceError("30008")
if len(detect_result_second) > 1:
raise FaceError("30008")
# Validate detected faces
self._validate_face_detection(detect_result_first, "firstImage")
self._validate_face_detection(detect_result_second, "secondImage")
# 提取人脸特征
features_first = self._extract_single_feature(imageFirst, detect_result_first)
features_second = self._extract_single_feature(imageSecond, detect_result_second)
# Calculate the Euclidean distance between features
distance = calculate_euclidean_distance(features_first, features_second)
# Compare distance with threshold
return distance >= 0.75
except Exception as e:
logger.error(f"Error occurred: {e}")
raise e
finally:
rw_lock.release_read()
def _validate_face_detection(self, detect_result, image_name):
if not detect_result:
logger.warning(f"No face detected in the image: {image_name}")
raise FaceError("30018") # no face in the image
def extract_features(self, image, detect_result):
return [
self._extract_single_feature(image, facebox)
for facebox in detect_result
]
def _extract_single_feature(self, image, detect_result):
for facebox in detect_result:
landmarks5 = self.ld5er.inference(image, facebox)
landmarks5 = np.array([[ld5[0], ld5[1]] for ld5 in landmarks5])
cropped_face = self.fa.align(image, landmarks5=landmarks5)
return self.fr.inference(cropped_face)
def calculate_euclidean_distance(features1, features2):
if len(features1) != len(features2):
raise ValueError("Feature arrays must have the same length")
# Calculate the Euclidean distance
total = 0
for f1, f2 in zip(features1, features2):
total += f1 * f2
return total

View File

@ -0,0 +1,36 @@
@echo off
setlocal enabledelayedexpansion
rem 从 redis.yaml 文件中读取 Redis 相关配置信息
set "REDIS_CONFIG="
for /f "delims=" %%i in (redis.yaml) do (
set "REDIS_CONFIG=!REDIS_CONFIG! %%i"
)
rem 工作进程数 workers
set "NUM_WORKERS=%3"
rem 提取 Redis 配置信息
for %%i in (!REDIS_CONFIG!) do (
for /f "tokens=1,* delims==" %%j in (%%i) do (
if "%%j"=="REDIS_HOST" set "REDIS_HOST=%%k"
if "%%j"=="REDIS_PORT" set "REDIS_PORT=%%k"
if "%%j"=="REDIS_PASSWORD" set "REDIS_PASSWORD=%%k"
)
)
echo num_workers: %NUM_WORKERS%
echo redis_host: %REDIS_HOST%; redis_port: %REDIS_PORT%
rem 运行 Python 脚本
set REDIS_HOST=%REDIS_HOST%
set REDIS_PORT=%REDIS_PORT%
set REDIS_PASSWORD=%REDIS_PASSWORD%
set NUM_WORKERS=%NUM_WORKERS%
rem 确保 Python 脚本能够正确读取环境变量
python flushredis.py
rem 启动 uvicorn
set MAX_READERS=%4%
uvicorn webmain:app --host %1 --port %2 --workers %NUM_WORKERS%

View File

@ -1,83 +1,154 @@
from fastapi import FastAPI from fastapi import FastAPI, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from process import FaceHelper, FileError, ErrorMsg from process import FaceHelper, FileError, ErrorMsg
import threading import threading
import os
import time import time
# 创建 FastAPI 实例
app = FastAPI() app = FastAPI()
# 创建线程锁,确保在多线程环境中对共享资源的访问是线程安全的
lock = threading.Lock() lock = threading.Lock()
# 全局变量 facehelper稍后将用于管理人脸识别相关操作
facehelper = None
facehelper = FaceHelper(
db_dir="./dbface",
)
class face(BaseModel): def get_facehelper():
"""
懒加载模式初始化 facehelper 对象
只有在第一次调用时才会创建 FaceHelper 实例
使用线程锁以确保 facehelper 实例在多线程环境下的安全初始化
"""
global facehelper
if facehelper is None:
with lock:
if facehelper is None:
facehelper = FaceHelper(db_dir="./dbface")
return facehelper
# 定义请求模型,用于接收前端传递的图像数据
class FaceRequest(BaseModel):
img: str img: str
class dbface(BaseModel):
# 定义请求模型,用于接收前端传递的数据库操作数据
class DBFaceRequest(BaseModel):
img: str img: str
optMode: str optMode: str
uniqueKey: str uniqueKey: str
# 接两图片对比的参数
class ImageComparison(BaseModel):
firstImage: str
secondImage: str
@app.post("/refreshdb") @app.post("/refreshdb")
@app.post("/refreshdb/")
def refresh(): def refresh():
global facehelper """
刷新人脸数据库的接口
该接口将调用 facehelper updateDB 方法刷新数据库内容
"""
facehelper = get_facehelper()
try: try:
# 加锁确保数据库更新操作的线程安全性
with lock: with lock:
facehelper.updateDB(None, None, None, Onlyrefresh=True) facehelper.updateDB(None, None, None, Onlyrefresh=True)
except FileError as e: except FileError as e:
# return {"status":e.code, "detail":f"{e}"} # 处理文件错误,返回相应的错误代码和信息
return {'code': e.code, 'msg': f"{e}", 'data': 'None'} return {'code': e.code, 'msg': str(e), 'data': None}
else: else:
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': 'None'} # 成功刷新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': None}
@app.post("/facerecognition") @app.post("/facerecognition")
@app.post("/facerecognition/") def faceRecognition(input: FaceRequest):
def faceRecognition(input:face): """
start = time.time() 进行人脸识别的接口
global facehelper 接收前端传递的图像数据并使用 facehelper 进行人脸识别
返回识别结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try: try:
# 调用 facehelper 的 faceRecognition 方法进行人脸识别
ret_data = facehelper.faceRecognition(input.img) ret_data = facehelper.faceRecognition(input.img)
print("finished recognition...")
end = time.time()
print("runtime: ", end-start)
except Exception as e: except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'} # 处理异常,返回相应的错误代码和信息
# return {"status":f"{e.code}", "detail":f"{e}"} raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else: finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Recognition finished. Runtime: {end - start:.2f} seconds")
# 返回识别结果
return ret_data return ret_data
return {"status":1, "name":identity, "resImg":res_img_base64}
@app.post("/featuredetect") @app.post("/featuredetect")
@app.post("/featuredetect/") def featureDetect(input: FaceRequest):
def featureDetect(input:face): """
start = time.time() 特征检测接口
global facehelper 接收前端传递的图像数据并使用 facehelper 进行特征检测
返回检测结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try: try:
# 调用 facehelper 的 featureDetect 方法进行特征检测
ret_data = facehelper.featureDetect(input.img) ret_data = facehelper.featureDetect(input.img)
print("finished featuredetect...")
end = time.time()
print("runtime: ", end-start)
except Exception as e: except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'} # 处理异常,返回相应的错误代码和信息
# return {"status":f"{e.code}", "detail":f"{e}"} raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else: finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Feature detection finished. Runtime: {end - start:.2f} seconds")
# 返回检测结果
return ret_data return ret_data
@app.post("/updatedb")
@app.post("/updatedb/")
def updateDB(input:dbface):
global facehelper
# input.uniqueKey = os.path.splitext(os.path.basename(input.uniqueKey))[0]
@app.post("/updatedb")
def updateDB(input: DBFaceRequest):
"""
更新人脸数据库的接口
接收前端传递的数据库操作数据并使用 facehelper 更新数据库
"""
facehelper = get_facehelper()
try: try:
# 加锁确保数据库更新操作的线程安全性
with lock: with lock:
facehelper.updateDB(input.img, input.optMode, input.uniqueKey) facehelper.updateDB(input.img, input.optMode, input.uniqueKey)
except Exception as e: except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'} # 处理异常,返回相应的错误代码和信息
# return {"status":f"{e.code}", "detail":f"{e}"} raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else: else:
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': 'None'} # 成功更新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': None}
@app.post("/imageComparison")
def imageComparison(input: ImageComparison):
"""
更新人脸数据库的接口
接收前端传递的数据库操作数据并使用 facehelper 更新数据库
"""
facehelper = get_facehelper()
try:
# 加锁确保数据库更新操作的线程安全性
with lock:
state = facehelper.imageComparison(input.firstImage, input.secondImage)
except Exception as e:
# 处理异常,返回相应的错误代码和信息
raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else:
# 成功更新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': bool(state)}