电池短路试验

main
slf 1 month ago
commit 177288317c

@ -0,0 +1 @@
电池短路试验

File diff suppressed because it is too large Load Diff

@ -0,0 +1,704 @@
import asyncio
import io
import os
import datetime
import os
import sqlite3
import sys
import time
import psutil
from fastapi import APIRouter, Request, Response, BackgroundTasks
from sqlalchemy import and_, desc
from app.lib import StaticData, usb
from app.lib.Camera import CvCamera
from app.lib.ModBus import ModbusConnection
from app.lib.StaticData import *
from app.lib.Utils import Utils
from app.lib.websocket_manageer import ConnectionManager
from app.models import *
from app.models.Business import PageParam, BllUser, BllRole, BllDeviceConfig, BllExperimentSpecimen, \
BllExperimentInfo, BllChannel, BllExperimentReport
from app.models.DateEntity import ExperimentSpecimen, ExperimentInfo, Channel, ExperimentReport
from app.utils.excel_util import ExcelExport
from app.validators.api import *
shiyan_time = 0
END_TIME = None
START_EXPERIMENT_ID = None
router = APIRouter(prefix='/api/shortcircuit', tags=['短路实验'])
#视频查询
class VideoModel(BaseModel):
id: str
@router.post('/add_user', summary='创建用户')
async def add_user(request: Request, body: SignUpdateModel):
"""
创建用户
:body SignUpdateModel
:return:
"""
user = BllUser().findEntity(and_(EntityUser.user_name == body.username, EntityUser.is_enabled == 0))
if user:
return Utils.false_return('', desc="账号已注册!")
entity = EntityUser()
entity.user_id = str(Utils.get_uuid())
entity.user_pwd = Utils.MD5(body.password)
entity.user_name = body.username
entity.real_name = body.name
entity.role_id = body.role_id
entity.user_sex = body.sex
entity.update_time = Utils.get_time()
BllUser().insert(entity)
return Utils.true_return('', desc="创建成功!")
@router.post('/login', summary='用户登录')
async def login(request: Request, body: SignInModel):
"""
用户登录
:body SignInModel
:return:
"""
user = BllUser().login(body.username, body.password)
if user:
CurrentUser["username"] = user.user_name
CurrentUser["user_id"] = user.user_id
return Utils.true_return(user, desc="登录成功!")
return Utils.false_return('', desc="登录失败,账号与密码不匹配!")
@router.post('/user_list', summary='用户列表')
async def user_list(request: Request, body: UserModel):
"""
用户列表
:return:
"""
queryOrm = BllUser().getUserList()
pageparam = PageParam(body.page_no, body.page_size)
user_list = BllUser().queryPage(queryOrm, pageparam)
pagination = {
"total_count": pageparam.totalRecords,
"page_no": body.page_no,
"page_size": body.page_size
}
return Utils.true_return_pagination(pagination, user_list, desc="获取成功!")
@router.put('/{user_id}', summary='编辑用户')
async def update_user(user_id: str, body: SignUpdateModel):
"""
编辑用户
:param user_id: str
:body SignUpdateModel
:return:
"""
user = BllUser().findEntity(and_(EntityUser.user_id == user_id, EntityUser.is_enabled == 0))
if user:
user.user_pwd = Utils.MD5(body.password)
user.user_name = body.username
user.real_name = body.name
user.role_id = body.role_id
user.user_sex = body.sex
user.update_time = Utils.get_time()
BllUser().update(user)
return Utils.true_return(user, desc="编辑成功!")
return Utils.false_return('', desc="查不到该账号!")
@router.delete('/delete_user/{user_id}', summary='删除用户')
async def del_user(user_id: str):
"""
删除用户
:param user_id: str
:return:
"""
user = BllUser().findEntity(and_(EntityUser.user_id == user_id, EntityUser.is_enabled == 0))
if user:
user.is_enabled = 1
user.update_time = Utils.get_time()
BllUser().update(user)
return Utils.true_return('', desc="删除成功!")
return Utils.false_return('', desc="查不到该账号!")
@router.get('/get_role_list', summary='获取角色列表')
async def get_role_list(request: Request):
"""
获取角色列表
:return:
"""
role_list = BllRole().findList(EntityRole.is_enabled == 0).all()
data_list = [{"role_name": i.role_name, "role_id": i.role_id} for i in role_list]
return Utils.true_return(data_list, desc="查询成功!")
@router.get("/shut_down", summary="关闭服务器")
def shut_down(request: Request):
# 提升权限
# if not ctypes.windll.shell32.IsUserAnAdmin():
# os.execl(sys.executable, sys.executable, *sys.argv)
kwargs_ = {
"channel_list": ['全部'],
}
response = modbus_server.StopExperiment(kwargs_)
camera.release()
if not response:
return Utils.false_return('', desc="复位失败,下位机通讯异常!")
# 关闭计算机
os.system("shutdown -s -t 0")
return Utils.true_return("", desc="关机成功")
@router.get("/exit", summary="退出服务")
def exit_server(request: Request):
"""退出服务"""
for proc in psutil.process_iter(['pid', 'name']):
print(proc.name())
if proc.name() == "chrome.exe":
try:
proc.kill()
print(f"进程 {proc.pid} 已被终止。")
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess) as e:
print(f"无法终止进程 {proc.pid}{e}")
sys.exit(0)
@router.get('/userfaceLogin', )
def userfaceLogin(request: Request, id: str):
try:
user = BllUser().facelogin(id)
end_time = datetime.datetime.strptime('2082-08-21 01:01', '%Y-%m-%d %H:%M')
now_time = datetime.datetime.now()
if now_time > end_time or os.path.exists('gq.txt'):
if not os.path.exists('gq.txt'):
gqFile = open('gq.txt', 'w+', encoding="utf-8")
gqFile.close()
return Utils.false_return('', "程序已过期!", 0)
if user is None:
return Utils.false_return('', "用户名或密码错误!", 0)
else:
return Utils.true_return(user, "登录成功!", 1)
except Exception as e:
print("设备初始化失败,请重启仪器")
return Utils.false_return('', '设备初始化失败,请重启仪器。' + str(e), 0)
@router.get('/getConfig' ,summary="获取设备配置")
async def getConfig(request: Request ):
config_info = dict(StaticData.DeviceConfig)
config_info.update(StaticData.DeviceStatus)
return config_info
@router.get('/getChannelList' ,summary="获取通道列表")
async def getChannelList(request: Request ,status :int = 0):
return [item for item in StaticData.ChannelDatas if item.status == status]
@router.post('/updateConfig' ,summary="更新设备配置")
async def updateConfig(request: Request , body :DeviceConfigModel):
StaticData.DeviceConfig.update(body)
device_config_list = BllDeviceConfig().findAllList()
device_config = device_config_list[0]
# 假设 device_config 是从数据库查询出来的模型实例
for key, value in body.__dict__.items():
if hasattr(device_config, key): # 确保属性存在
setattr(device_config, key, value)
BllDeviceConfig().update(device_config)
return StaticData.DeviceConfig
@router.get('/getChannelInfo' ,summary="获取通道详情")
async def getChannelInfo(request: Request ,channel_id :str):
chnanels = [item for item in StaticData.ChannelDatas if item.id == channel_id]
experiment_ids = [item.experiment_id for item in StaticData.ChannelDatas if item.id == channel_id and hasattr(item, 'experiment_id')]
report_list = []
if len(experiment_ids) > 0:
report_list = BllExperimentReport().findList(and_(ExperimentReport.channel_id == channel_id, ExperimentReport.experiment_id == experiment_ids[0])).all()
result = {
"base_info": chnanels[0],
"report_list": report_list
}
return result
@router.get('/getStatus', summary="获取下位机状态")
async def getStatus(request: Request, background_tasks: BackgroundTasks):
"""
该路由会返回一个立即的响应但不会阻塞周期性任务的执行
"""
background_tasks.add_task(run_periodic_task) # 启动后台任务
return {"message": "已启动周期性任务每30秒调用一次下位机状态获取。"}
@router.get('/experimentOver' ,summary="停止试验")
async def experimentOver(request: Request ,experiment_id :str, cause :str = ''):
path = Utils.get_db_path()
print(path)
# 获取数据库连接(假设你使用 sqlite
conn = sqlite3.connect(path) # 数据库路径
cursor = conn.cursor()
try:
# 开始事务
conn.execute("BEGIN TRANSACTION")
# 更新实验信息 状态 0未开始 1进行中 2已完成 3已取消
curr_time = Utils.get_time()
experimentInfo = BllExperimentInfo().findEntity(ExperimentInfo.id == experiment_id)
if experimentInfo.state != 1:
return Utils.false_return('', '试验未开始!')
channel_list = [item.name for item in StaticData.ChannelDatas if hasattr(item, 'experiment_id') and item.experiment_id == experiment_id]
kwargs_ = {
"channel_list": channel_list,
}
modbus_server.StopExperiment(kwargs_)
experimentInfo.state = 3
experimentInfo.end_time = curr_time
experimentInfo.desc = f'手动停止试验,原因:{cause}'
BllExperimentInfo().update(experimentInfo)
# 更新通道状态为空闲
channel_ids = experimentInfo.channel_id.split(',')
channel_experiment_dict = {experimentInfo.channel_id: None}
await update_channel_status(channel_ids, curr_time,0,channel_experiment_dict,None)
# 提交事务
conn.commit()
except ChannelStatusError as e:
# 捕获自定义异常并返回给前端
conn.rollback() # 回滚事务
print(f'发生异常:::::::{e}')
return Utils.false_return('', str(e))
except Exception as e:
# 捕获非自定义异常(如数据库错误等)
conn.rollback() # 回滚事务
print(f'发生异常:::::::{e}')
# 返回一个通用错误信息给前端
return Utils.false_return('', f"发生了系统错误: {str(e)}")
finally:
# 确保关闭数据库连接
conn.close()
return Utils.true_return('', '试验已停止!')
# @router.post('/startExperiment' ,summary="开始实验")
# async def startExperiment(request: Request , body :ExperimentParamModel):
# try:
# curr_time = Utils.get_time()
# # 调用检查通道状态的方法
# check_channel_status(body.channel_ids, StaticData.ChannelDatas)
# # md5值 样品名称+电池型号+样品编号+电池规格+电池厂家+备注
# await check_specimen_exist(body)
# # 保存实验信息
# await save_experiment_info(body)
#
# # 更新通道状态为正在实验
# channel_ids = body.channel_ids.split(',')
# await update_channel_status(channel_ids, curr_time,1)
#
#
#
# except ChannelStatusError as e:
# # 捕获异常并返回给前端
# return Utils.false_return('', str(e))
#
#
# return None
@router.post('/startExperiment', summary="开始实验")
async def startExperiment(request: Request, body: ExperimentParamModel):
path = Utils.get_db_path()
print(path)
# 获取数据库连接(假设你使用 sqlite
conn = sqlite3.connect(path) # 数据库路径
cursor = conn.cursor()
try:
# 开始事务
conn.execute("BEGIN TRANSACTION")
curr_time = Utils.get_time()
timestamp = time.time()
# 调用检查通道状态的方法
check_channel_status(body.channel_ids, StaticData.ChannelDatas)
# 下位机发送指令
channel_ids = body.channel_ids.split(',')
channel_list = [item.name for item in StaticData.ChannelDatas if channel_ids.__contains__(item.id)]
kwargs_ = {
"channel_list": channel_list,
"temperature": body.temperature,
"work_time": body.expose_time,
}
modbus_server.Experiment(kwargs_)
# md5值 样品名称+电池型号+样品编号+电池规格+电池厂家+备注
await check_specimen_exist(body)
# 保存实验信息
await save_experiment_info(body)
# 更新通道状态为正在实验
channel_ids = body.channel_ids.split(',')
await update_channel_status(channel_ids, curr_time,1,body.channel_experiment_dict,timestamp)
# 提交事务
conn.commit()
except ChannelStatusError as e:
# 捕获自定义异常并返回给前端
conn.rollback() # 回滚事务
print(f'发生异常:::::::{e}')
return Utils.false_return('', str(e))
except Exception as e:
# 捕获非自定义异常(如数据库错误等)
conn.rollback() # 回滚事务
print(f'发生异常:::::::{e}')
# 返回一个通用错误信息给前端
return Utils.false_return('', f"发生了系统错误: {str(e)}")
finally:
# 确保关闭数据库连接
conn.close()
return Utils.true_return('', '实验已开始!')
@router.get('/getAllSpecimen' ,summary="获取所有样本")
async def getAllSpecimen(request: Request ):
experiment_specimen_list = BllExperimentSpecimen().findAllList()
return experiment_specimen_list
@router.post('/pageExperimentInfo' ,summary="分页获取试验信息")
async def getAllSpecimen(request: Request ,body: SearchWordModel):
pageparam = PageParam(body.page_no, body.page_size)
queryOrm = BllExperimentInfo().getExperimentList(body)
experiment_list = BllExperimentInfo().queryPage(queryOrm, pageparam)
pagination = {
"total_count": pageparam.totalRecords,
"page_no": body.page_no,
"page_size": body.page_size
}
return Utils.true_return_pagination(pagination, experiment_list, desc="获取成功!")
@router.get('/getExperimentInfo' ,summary="获取实验详情")
async def getExperimentInfo(request: Request ,experiment_id :str):
experiment_info = BllExperimentInfo().findEntity(ExperimentInfo.id == experiment_id)
report_list = BllExperimentReport().findList((ExperimentReport.experiment_id == experiment_id)).all()
result = {
"base_info": experiment_info,
"report_list": report_list
}
return result
@router.get('/mergeGate' ,summary="合闸")
async def mergeGate(request: Request ,channel_id :str):
# experiment_specimen_list = BllExperimentSpecimen.findAllList()
return None
# 保存报表数据
async def save_report():
curr_time = time.time()
for item in StaticData.ChannelDatas:
if not hasattr(item, 'timestamp') or not item.timestamp:
continue
time_ratio = round(float((curr_time - item.timestamp)/60), 1)
if (item.status == 1 or item.status == 2) and item.id not in StaticData.StopIds: # 如果通道状态为正在实验或已实验且通道id不在停止列表中
if item.status == 2 :
StaticData.StopIds.append(item.id)
new_id = str(Utils.get_uuid())
experiment_report = ExperimentReport(id= new_id, experiment_id=item.experiment_id,
channel_id=item.id,
time=time_ratio, experiment_temperature=item.temperature,
battery_voltage=item.voltage, battery_electricity=item.electricity
)
BllExperimentReport().insert(experiment_report)
return None
@router.get('/updateDoor' ,summary="修改门状态")
async def updateDoor(request: Request ,status :int):
if StaticData.DeviceConfig['door_status'] == status:
return Utils.false_return('', "门状态未改变", 0)
StaticData.DeviceConfig['door_status'] = status
device_config_list = BllDeviceConfig().findAllList()
device_config = device_config_list[0]
device_config.door_status = status
BllDeviceConfig().update(device_config)
return Utils.true_return('', "修改成功")
# 获取指定日期的文件列表
@router.get("/files")
async def get_files_by_date():
current_date = datetime.datetime.now()
date = current_date.strftime("%Y-%m-%d")
pid_dir = f"/data/video"
if not os.path.exists(pid_dir):
return Utils.false_return(code=400, desc=f'没有找到{pid_dir}的目录')
files = os.listdir(pid_dir)
result = []
for file in files:
if file.startswith(date.replace('-', '')) and file.endswith(".mp4"):
result.append(file)
result.sort(reverse=True)
return Utils.true_return(data=result)
#实时相机显示
@router.get('/camera_img/')
def camera_img():
return Response(camera.generate(),
mimetype = "multipart/x-mixed-replace; boundary=frame")
@router.post("/view_video", summary="查看视频")
async def view_video(request: Request, body: VideoModel):
"""查看视频"""
record_id = body.id
record_obj = BllExperimentInfo().findEntity(and_(BllExperimentInfo.id == body.id))
if not record_obj:
return Utils.false_return('', desc="查不到该实验!")
video_path = 'http://127.0.0.1:8088/videoFold' + '/' + record_id + '.mp4'
return Utils.true_return(video_path, desc="查看成功")
@router.post("/export_video", summary="导出视频")
async def export_video(request: Request, body: VideoModel):
"""
导出视频
id 使用 试验id
"""
if not body.id:
return Utils.false_return("", desc="未选择导出文件")
try:
# 检查U盘挂载路径
upath = Utils.getUDiskPath()
if not upath:
return Utils.false_return("", desc="U盘未正确挂载")
source_file = 'D:/Project/UI/videoFold' + '/' + body.id + '.mp4'
# 检查源文件是否存在
if not os.path.isfile(source_file):
return Utils.false_return("", desc="指定的视频文件不存在")
# 复制视频文件到U盘
with open(source_file, mode="rb") as file:
file_content = file.read()
buffer = io.BytesIO(file_content)
usb.put_in(body.id + '.mp4', buffer)
return Utils.true_return("", desc="导出成功")
except Exception as e:
return Utils.false_return("", desc="导出失败")
@router.post("/export_table", summary="导出报表")
async def export_table(request: Request, body: ExportTableModel):
"""报表导出
Args:
request (Request): _description_
body (ExportTableModel):
ids: 实验id列表
download_type: 下载类型(usb:导出到u盘),传空为返回到浏览器
"""
experiment_ids = body.specimen_id.split(',')
experiments = BllExperimentInfo().findList(ExperimentInfo.id.in_(experiment_ids)).all()
all_data = []
file_name = ""
base_data_title = [
'实验时间', '实验人', '样品名称', '设置温度', '暴露时间', '实验结果'
]
data = {
"specimen_data": {},
"file_name": file_name,
"sheet_title": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S").replace(":", "-"),
"title_list": ["BAT05-A电池短路试验报告"],
"base_data_title": base_data_title
}
base_data_list = []
other_data = []
if len(experiment_ids) == 1:
experiment_info = BllExperimentInfo().findEntity(ExperimentInfo.id == experiment_ids[0])
other_data = BllExperimentReport().findList(and_(ExperimentReport.experiment_id == experiment_ids[0],ExperimentReport.channel_id == experiment_info.channel_id)).all()
for experiment in experiments:
base_data = {
"experiment_id": experiment.id,
"样品名称": experiment.specimen_name,
"实验人": experiment.user_name,
"实验时间": experiment.created_time,
"设置温度": experiment.temperature,
"暴露时间": experiment.expose_time,
"实验结果": '成功' if experiment.state == 2 else '失败',
'other_data': other_data
}
base_data_list.append(base_data)
data['base_data_list'] = base_data_list
all_data.append(data)
excel_obj = ExcelExport(file_name, all_data, body.download_type)
binary, encoded_filename = excel_obj.experiment_table()
if isinstance(binary, int):
if binary != 404:
return Response(content=binary.getvalue(),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={'Content-Disposition': f'attachment; filename={encoded_filename}'})
return Utils.false_return(desc=encoded_filename)
# 导出到浏览器
return Response(content=binary.getvalue(),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={'Content-Disposition': f'attachment; filename={encoded_filename}'})
async def app_start():
# 项目启动执行 初始化项目配置
device_config_list = BllDeviceConfig().findAllList()
StaticData.DeviceConfig.update(device_config_list[0].__dict__)
# 初始化通道信息
channel_list = BllChannel().findAllList()
StaticData.ChannelDatas = channel_list
# 调用下位机
asyncio.create_task(run_periodic_task())
async def run_periodic_task():
while True:
print("开始执行任务-------------")
modbus_server.get_status() # 调用获取状态的方法
await save_report()
await asyncio.sleep(30) # 每隔30秒执行一次
modbus_server = ModbusConnection()
manager = ConnectionManager()
camera =CvCamera()
camera.release()
# vk70_server = VK70xUMCDAQ()
async def update_channel_status(channel_ids, curr_time,status,channel_experiment_dict,timestamp):
channel_list = BllChannel().findList(Channel.id.in_(channel_ids)).all()
channel_dict = {item.id : item for item in StaticData.ChannelDatas }
for channel in channel_list:
# 更新通道状态为正在实验
channel.status = status
channel.last_start_time = curr_time
BllChannel().update(channel)
channel_dict.get(channel.id).status = status
channel_dict.get(channel.id).last_start_time = curr_time
channel_dict.get(channel.id).experiment_id = channel_experiment_dict.get(channel.id)
channel_dict.get(channel.id).timestamp = timestamp
if channel.id in StaticData.StopIds:
StaticData.StopIds.remove(channel.id)
async def save_experiment_info(body):
channel_ids = body.channel_ids.split(',')
for channel_id in channel_ids:
new_id = str(Utils.get_uuid())
experiment_info = ExperimentInfo(id= new_id, specimen_id=body.experiment_specimen_id,
channel_id=channel_id,
specimen_name=body.specimen_name, battery_type=body.battery_type,
specimen_num=body.specimen_num,
battery_specification=body.battery_specification,
battery_manufacturers=body.battery_manufacturers,
temperature=body.temperature, expose_time=body.expose_time, put_time=body.put_time,
short_circuit_param=body.short_circuit_param, user_id=CurrentUser['user_id'],
user_name=CurrentUser['username'],
state=1)
# 保存实验信息
BllExperimentInfo().insert(experiment_info)
body.channel_experiment_dict.update({channel_id: new_id})
camera.start(record_id=new_id)
class ChannelStatusError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
def check_channel_status(channel_ids, channel_datas):
# 将channel_ids字符串转换为列表
channel_ids = channel_ids.split(',')
# 获取 status == 0 的 id 集合
wait_ids = [item.id for item in channel_datas if item.status == 0]
# 判断选择的通道是否为空
if len(channel_ids) <= 0:
raise ChannelStatusError("请选择通道")
# 判断是否所有通道都在实验中
if len(wait_ids) == 0:
raise ChannelStatusError("所有通道都在实验中,请等待")
# 判断每个通道是否处于空闲状态
for channel_id in channel_ids:
if channel_id not in wait_ids:
raise ChannelStatusError(f"通道 {channel_id} 不在空闲状态,请重新选择")
# 如果都通过了检查,返回 True 或其他成功状态
return True
async def check_specimen_exist(body):
# 拼接字符串生成 MD5
source_str = body.specimen_name + body.battery_type + body.specimen_num + body.battery_specification + body.battery_manufacturers
md5_upper = Utils.get_md5(source_str).upper()
# 查询数据库中的样本
experiment_specimen = BllExperimentSpecimen().findEntity(ExperimentSpecimen.md5 == md5_upper)
# 如果样本不存在,则创建样本
if experiment_specimen :
# 将新创建的样本 ID 赋值给 body
body.experiment_specimen_id = experiment_specimen.id
else:
new_id = Utils.get_uuid()
body.experiment_specimen_id = new_id
experiment_specimen = ExperimentSpecimen(
id=str(new_id),
md5=md5_upper,
specimen_name=body.specimen_name,
battery_type=body.battery_type,
specimen_num=body.specimen_num,
battery_specification=body.battery_specification,
battery_manufacturers=body.battery_manufacturers,
user_id=CurrentUser['user_id'],
user_name=CurrentUser['username']
)
# 异步插入新样本
BllExperimentSpecimen().insert(experiment_specimen)

@ -0,0 +1,26 @@
from fastapi import FastAPI
from app.api import Server, ShortCircuit
from app.api.ShortCircuit import app_start
def register_blueprints(app):
'''
registration route (module)
'''
# app.include_router(Server.router, tags=["API接口"])
app.include_router(ShortCircuit.router, tags=["电池短路API接口"])
async def lifespan(app):
# 应用启动时执行
await app_start()
# 在应用关闭时执行的代码
yield # 等待应用关闭
def create_app():
app = FastAPI(lifespan=lifespan)
#注册路由
register_blueprints(app)
return app

@ -0,0 +1,44 @@
import os
from pydantic import BaseSettings
class Settings(BaseSettings):
NAME: str = "DG16-D"
DEBUG: bool = False
RELOAD: bool = False
ENV: str = "dev"
SERVER_HOST: str = "0.0.0.0"
SERVER_POST: int = 8099
LOGS_DIR: str = "logs"
RESOURCE_PATH: str = "resource"
TEMP_PATH: str = "temp"
DB_DIR: str = "db"
TEM: list = [40, 60, 80, 100] # 计算温度点
BASE_PATH: str = os.path.dirname(os.path.dirname((os.path.abspath(__file__))))
'''
数据库的配置
'''
SQLALCHEMY_DATABASE_URI: str = 'sqlite:///app/db/Data.db'
'''
分页信息配置
'''
PAGE_SIZE: int = 10
PAGE_MIN_SIZE: int = 6
PAGE_DEFAULT_OFFSET: int = 1
'''
modbus-rtu连接参数
'''
PORT: str = "COM1" # 串口号
BAUDRATE: int = 9600 # 波特率
BYTESIZE: int = 8 # 数据位
STOPBITS: int = 1 # 停止位
TIMEOUT: int = 5 # 超时时间
MACHINE: int = 1 # 设备 1沒有没有摄像头2有摄像头
settings = Settings()
for i in [settings.LOGS_DIR, settings.TEMP_PATH, settings.RESOURCE_PATH, settings.DB_DIR]:
dir = os.path.join(settings.BASE_PATH, i)
if not os.path.exists(dir):
os.mkdir(dir)

@ -0,0 +1,29 @@
from sqlalchemy.ext.declarative import DeclarativeMeta
import datetime
import json
#sqlalchemy对象编码转换类
class AlchemyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
#SQLAlchemy类
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
data = obj.__getattribute__(field)
try:
json.dumps(data) # 序列化对象
fields[field] = data
except TypeError: # 添加了对datetime的处理
if isinstance(data, datetime.datetime):
fields[field] = data.isoformat()
elif isinstance(data, datetime.date):
fields[field] = data.isoformat()
elif isinstance(data, datetime.timedelta):
fields[field] = (datetime.datetime.min + data).time().isoformat()
else:
fields[field] = None
# Json编码字典
return fields
return json.JSONEncoder.default(self, obj)

@ -0,0 +1,82 @@
import os
import cv2
import threading
outputFrame = None
lock = threading.Lock()
class CvCamera():
def __init__(self):
# 获取当前相机
self.cap = None
self.workFlag =False
self.record_id =None
self.video_path = 'D:/Project/UI/videoFold'
self.fourcc = cv2.VideoWriter_fourcc(*'XVID')
if not os.path.exists(self.video_path) or not os.path.isdir(self.video_path):
os.makedirs(self.video_path)
#打开相机
def start(self,record_id=None):
try:
if self.workFlag == False:
self.record_id =record_id
self.cap = cv2.VideoCapture(0)
self.workFlag = True
# 单独线程供相机使用
t = threading.Thread(target=self.videoWork)
t.start()
except Exception as e:
print(e)
# 释放资源
def release(self):
global outputFrame
try:
self.workFlag = False
self.record_id = None
if (self.cap):
outputFrame=None
self.cap.release()
except:
pass
# 相机图像分析
def videoWork(self):
global outputFrame, lock
self.cap.set(cv2.CAP_PROP_FPS, 30)
out = cv2.VideoWriter(self.video_path + '/' + self.record_id + '.mp4', self.fourcc, 20.0, (640, 480))
while (self.workFlag):
try:
ret, frame = self.cap.read()
# cv2.imshow('image',frame)
# cv2.waitKey(0)
with lock:
outputFrame = frame.copy()
out.write(frame)
except Exception as e:
print(e)
# 输出图像
def generate(self):
# 抓取对输出帧和锁定变量的全局引用
global outputFrame, lock
# 循环访问输出流中的帧
while True:
# 等到锁被获取
with lock:
# 检查输出帧是否可用,否则跳过
# 循环的迭代
if outputFrame is None:
continue
# 以 JPEG 格式对帧进行编码
(flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
# 确保帧已成功编码
if not flag:
continue
# 以字节格式生成输出帧
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + bytearray(encodedImage) + b'\r\n')

@ -0,0 +1,59 @@
import os
import logging.config
import time
from app.conf.Setting import settings
# 日志格式
format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
log_dir = os.path.join(settings.BASE_PATH, settings.LOGS_DIR) # log文件的目录
log_name = time.strftime("%Y-%m-%d") + ".log" # log文件名
# log文件的全路径
logfile_path = os.path.join(log_dir, log_name)
# log配置字典
LOGGING_DICT = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': format
},
'simple': {
'format': format
},
},
'filters': {},
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path, # 日志文件
'maxBytes': 1024 * 1024 * 20, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8'
},
},
'loggers': {
'': {
# 这里把上面定义的两个handler都加上即log数据既写入文件又打印到屏幕
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': True, # 向上更高level的logger传递
},
},
}
logging.config.dictConfig(LOGGING_DICT)
# 导入上面定义的logging配置
logger = logging.getLogger()

@ -0,0 +1,292 @@
import time
import serial
import struct
import modbus_tk.defines as cst
from app.lib import StaticData
from app.lib.StaticData import DeviceStatus
from app.lib.Utils import Utils
from modbus_tk import modbus_rtu
from app.conf.Setting import settings
from app.lib.Log import logger
'''
下位机地址定义
'''
EXPERIMENT = 801 #试验操作地址位
STATUS_ADDR_BEGIN =3701 #获取状态信息地址位
ELECT_SWITCH =102 #电磁阀
'''
工作模式定义
'''
STATUS_ADDR_LENGTH =24 #获取状态长度
State_dict={
"0": "空闲",
"1": "升温阶段",
"2": "结果判定中",
}
class Status(object):
'''
状态数据结构
'''
status: int = None #状态 0 1 2
status_text: str = None #状态
temp1: str = None #铜炉中心温度
temp2: str = None #加热铜块温度
temp3: str = None #样品温度
temp4: str = None #自燃温度
sy_work_time: str = None
switch_state = None
result = None
def keys(self):
return ["status", "status_text", "temp1", "temp2", "temp3", "temp4", "sy_work_time", "switch_state", "result"]
def __getitem__(self, key):
return getattr(self,key)
class ModbusConnection(object):
'''
Modbus连接管理器
'''
def __init__(self):
try:
self.serial =serial.Serial(port=settings.PORT,baudrate=settings.BAUDRATE, bytesize=settings.BYTESIZE, stopbits=settings.STOPBITS)
self.master = modbus_rtu.RtuMaster(self.serial)
self.master.set_timeout(1.0)
except Exception as e:
logger.error(f"连接串口异常: {e}")
def parser_status(self,data_bytes):
'''
解析状态
'''
channel1_status =struct.unpack('>H', data_bytes[0:2])[0]
channel2_status =struct.unpack('>H', data_bytes[2:4])[0]
channel3_status =struct.unpack('>H', data_bytes[4:6])[0]
channel4_status =struct.unpack('>H', data_bytes[6:8])[0]
channel5_status =struct.unpack('>H', data_bytes[8:10])[0]
channel1_voltage =float(int(struct.unpack('>H', data_bytes[10:12])[0]) /10) #电压 /10获取小数
channel2_voltage =float(int(struct.unpack('>H', data_bytes[12:14])[0]) /10)
channel3_voltage =float(int(struct.unpack('>H', data_bytes[14:16])[0]) /10)
channel4_voltage =float(int(struct.unpack('>H', data_bytes[16:18])[0]) /10)
channel5_voltage =float(int(struct.unpack('>H', data_bytes[18:20])[0]) /10)
channel1_electricity =float(int(struct.unpack('>H', data_bytes[20:22])[0]) /10) #电流
channel2_electricity =float(int(struct.unpack('>H', data_bytes[22:24])[0]) /10)
channel3_electricity =float(int(struct.unpack('>H', data_bytes[24:26])[0]) /10)
channel4_electricity =float(int(struct.unpack('>H', data_bytes[26:28])[0]) /10)
channel5_electricity =float(int(struct.unpack('>H', data_bytes[28:30])[0]) /10)
channel1_temperature =float(int(struct.unpack('>H', data_bytes[30:32])[0]) /10) #温度
channel2_temperature =float(int(struct.unpack('>H', data_bytes[32:34])[0]) /10)
channel3_temperature =float(int(struct.unpack('>H', data_bytes[34:36])[0]) /10)
channel4_temperature =float(int(struct.unpack('>H', data_bytes[36:38])[0]) /10)
channel5_temperature =float(int(struct.unpack('>H', data_bytes[38:40])[0]) /10)
zfm_temperature =float(int(struct.unpack('>H', data_bytes[40:42])[0]) /10) # 蒸发皿温度
xt_temperature =float(int(struct.unpack('>H', data_bytes[42:44])[0]) /10) # 箱体温度
kzzt_temperature =int(struct.unpack('>H', data_bytes[44:46])[0]) # 温度控制状态
exception_status =int(struct.unpack('>H', data_bytes[46:48])[0]) # 异常状态1 bit0--继电器通讯失败 bit2--加热功能异常 bit3--制冷功能异常
StaticData.DeviceStatus.zfm_temperature = zfm_temperature
StaticData.DeviceStatus.xt_temperature = xt_temperature
StaticData.DeviceStatus.kzzt_temperature = kzzt_temperature
StaticData.DeviceStatus.exception_status = exception_status
for item in StaticData.ChannelDatas:
if item.name == '通道一':
item.status = channel1_status
item.voltage = channel1_voltage
item.electricity = channel1_electricity
item.temperature = channel1_temperature
elif item.name == '通道二':
item.status = channel2_status
item.voltage = channel2_voltage
item.electricity = channel2_electricity
item.temperature = channel2_temperature
elif item.name == '通道三':
item.status = channel3_status
item.voltage = channel3_voltage
item.electricity = channel3_electricity
item.temperature = channel3_temperature
elif item.name == '通道四':
item.status = channel4_status
item.voltage = channel4_voltage
item.electricity = channel4_electricity
item.temperature = channel4_temperature
elif item.name == '通道五':
item.status = channel5_status
item.voltage = channel5_voltage
item.electricity = channel5_electricity
item.temperature = channel5_temperature
return None
# def parser_status(self,data_bytes):
# '''
# 解析状态
# '''
# status.status =struct.unpack('>H', data_bytes[0:2])[0]
#
# status.temp1 = round(struct.unpack('>f', data_bytes[8:10] + data_bytes[6:8])[0],2)
# status.temp2 = round(struct.unpack('>f', data_bytes[12:14] + data_bytes[10:12])[0],2)
# status.temp3 = round(struct.unpack('>f', data_bytes[16:18] + data_bytes[14:16])[0],2)
# status.temp4 = round(struct.unpack('>f', data_bytes[20:22] + data_bytes[18:20])[0],2)
#
# status.sy_work_time = round(struct.unpack('>f', data_bytes[24:26] + data_bytes[22:24])[0],2)
# status.switch_state = struct.unpack('>H', data_bytes[26:28])[0]
# status.result = struct.unpack('>H', data_bytes[28:30])[0]
# return status
def Experiment(self,**kwargs_):
'''
试验操作
'''
channel_dict = {'通道一':1, '通道二':10, '通道三':100, '通道四':1000, '通道五':10000 ,'全部': 11111}
channel_list = kwargs_["channel_list"]
channel = 0
for name in channel_list:
channel += channel_dict.get(name)
work_time = int(kwargs_["work_time"] * 60)
place_holding = struct.pack('>H', int(0))#0 占位符
d_work_status = struct.pack('>H', 1)#1开始试验0停止试验
d_sy_time = struct.pack('>H', work_time) # 实验时间
channel_list = struct.pack('>H', int(channel)) # 通道有效位 00111就代表前3个通道有效 关闭时通道位也传1 模式传0
temperature = struct.pack('>H', int(float(kwargs_["temperature"]) * 10)) # 目标温度 比如511就是51.1度
temperature_control = struct.pack('>H', int(kwargs_["temperature_control"] or 0)) # 是否开启控温 0关闭 1开启
output = (
d_work_status
+ d_sy_time
+ channel_list
+ temperature
+ temperature_control
)
print(f'开始实验参数::::::::::{d_work_status},{d_sy_time},{channel_list},{temperature},{temperature_control}')
vl_output = Utils.bytes_to_int_list(output)
try:
res = self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, starting_address=EXPERIMENT, output_value=vl_output) # [11,12,13,12]
except Exception as e:
print(f'试验操作异常: {e}')
res = None
print(f'开始实验返回值:::::::::::::{res}')
return res
# def Experiment(self,**kwargs_):
# '''
# 试验操作
# '''
# place_holding = struct.pack('>H', int(0))#0 占位符
# d_work_status = struct.pack('>H', int(kwargs_["work_status"]))#1开始试验2停止试验
# elect_switch = struct.pack('>H', int(kwargs_["elect_switch"]))
#
# d_natural_temp = struct.pack('>f', float(kwargs_["natural_temp"]))
# d_sy_time = struct.pack('>f', float(kwargs_["max_work_time"] or 30))
#
# output = (
# d_work_status
# + elect_switch
# + place_holding
# + d_natural_temp[2:] + d_natural_temp[:2]
# + place_holding + place_holding
# + d_sy_time[2:] + d_sy_time[:2]
# )
# vl_output = Utils.bytes_to_int_list(output)
# try:
# res = self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, starting_address=EXPERIMENT, output_value=vl_output) # [11,12,13,12]
# except Exception as e:
# print(f'试验操作异常: {e}')
# res = None
# return res
def StopExperiment(self, **kwargs_):
'''
停止试验操作
'''
channel_dict = {'通道一':1, '通道二':10, '通道三':100, '通道四':1000, '通道五':10000 ,'全部': 11111}
channel_list = kwargs_["channel_list"]
d_work_status = struct.pack('>H', 0)#1开始试验0停止试验
channel_list = struct.pack('>H', int(channel_dict.get(channel_list[0]))) # 通道有效位 00111就代表前3个通道有效 关闭时通道位也传1 模式传0 00001 一通道 00010 二通道 00100 三通道 01000 四通道 10000 五通道
print(f'结束实验参数::::::::::{d_work_status},{channel_list}')
try:
res = self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, starting_address=(EXPERIMENT+2), output_value=Utils.bytes_to_int_list(channel_list))
time.sleep(0.2)
res = self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, starting_address=EXPERIMENT, output_value=Utils.bytes_to_int_list(d_work_status))
except Exception as e:
res = None
print(f'结束实验返回值:::::::::::::{res}')
return res
def Send_Mes(self,Addr,status):
'''
Addr 地址位
'''
d_0 = struct.pack('>H', status)
output = (d_0)
vl_output = Utils.bytes_to_int_list(output)
try:
res =self.master.execute(1, cst.WRITE_MULTIPLE_REGISTERS,starting_address=Addr, output_value=vl_output)
except Exception as e:
print(e)
res = None
return res
def get_status(self):
'''
获取状态
'''
try:
status = self.master.execute(
1,
cst.READ_INPUT_REGISTERS,
STATUS_ADDR_BEGIN,
STATUS_ADDR_LENGTH
)
except Exception as e:
logger.error(f"获取状态异常: {e}")
return None
print('status:::::::::::::',status) # [12,4,4]
print(':int_list_to_bytes:::::',status) # [0xdd,0xll]
print('parser_status:::::::::::::',self.parser_status(Utils.int_list_to_bytes(status))) #
return self.parser_status(Utils.int_list_to_bytes(status))
modbus_obj = ModbusConnection()
# 获取状态
modbus_obj.get_status()
# 开始实验
# kwargs_={}
# kwargs_["work_status"] = 1
# kwargs_["elect_switch"] = 0
# kwargs_["natural_temp"] = 40
# kwargs_["max_work_time"] = 30
#
# modbus_obj.Experiment(**kwargs_)

@ -0,0 +1,113 @@
import csv
import json
import os
import pathlib
import shutil
from app.conf.Setting import settings
# from app.lib.StaticData import ServerStatus
class RecordStatus:
"""记录当然实验工作状态"""
def __init__(self):
self.file = pathlib.Path(settings.BASE_PATH, settings.TEMP_PATH, "last_experiment.json")
def load(self):
if not self.file.exists():
return {}
return json.loads(self.file.read_text())
def update(self, work_time, status):
data = {
"work_time": work_time,
"status": status
}
self.file.write_text(json.dumps(data))
def clear(self):
if self.file.exists():
self.file.unlink()
class ExperimentData:
"""实验数据"""
current_obj = None
id = None
def __init__(self, _id):
self._id = _id
self.dir_path = pathlib.Path(os.path.join(settings.BASE_PATH, settings.TEMP_PATH, self._id))
self.file_path = self.dir_path.joinpath("experiment_data.csv")
self.status_file = self.dir_path.joinpath("last_status.json")
def init(self):
if not self.dir_path.exists():
print(f"create dir {self.dir_path}")
self.dir_path.mkdir(parents=True, exist_ok=True)
self.file_path.touch(exist_ok=True)
self.status_file.touch(exist_ok=True)
ExperimentData.current_obj = self
ExperimentData.id = self._id
def update_status(self, status_data):
format_value = (f'{status_data["status"]},{status_data["status_text"]},'
f'{status_data["s_pressure"]},{status_data["temperature"]},'
f'{status_data["test_time"]}\n')
with open(self.file_path, "a+") as f:
f.write(format_value)
data = {
"status": status_data["status"],
"status_text": status_data["status_text"],
"s_pressure": status_data["s_pressure"],
"temperature": status_data["temperature"],
"test_time": status_data["test_time"]
}
self.status_file.write_text(json.dumps(data))
def get_last_status(self):
if not self.status_file.exists():
return {'status': 0} # 设置的初始值
raw = self.status_file.read_text()
if not raw:
return {'status': 0} # 设置的初始值
return json.loads(self.status_file.read_text())
def finish(self):
ExperimentData.current_obj = None
def remove(self):
if self.dir_path.exists():
print(f"remove dir {self.dir_path}")
shutil.rmtree(self.dir_path)
def judging_status(self, status_obj, work_time):
"""根据时间判断状态"""
cur_status = status_obj[0][0]
for status, time in status_obj:
if int(work_time) <= int(time):
return cur_status
else:
cur_status = status
return cur_status
def get_raw_data(self):
result = []
if not self.file_path.exists():
return result
with open(self.file_path, "r") as f:
reader = csv.reader(f)
for row in reader:
result.append(row)
return result

File diff suppressed because one or more lines are too long

@ -0,0 +1,479 @@
import uuid
import os
import json
import struct
import hashlib
import random
import copy
from idlelib.iomenu import encoding
import psutil
import platform
import datetime
import numpy as np
from app.lib.Log import logger
from app.lib.AlchemyJsonEncoder import *
from app.conf.Setting import settings
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
system_name = platform.system()
"""通用工具类"""
class Utils(object):
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
#MD5加密
def MD5(str):
hl = hashlib.md5()
hl.update(str.encode(encoding='utf-8'))
return hl.hexdigest()
#获取uuid
def get_uuid():
return str(uuid.uuid1())
def str_to_time(str):
return datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S')
#获取当前时间
def get_time():
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#请求成功返回
def true_return(data='', desc='请求成功', code=200):
data = {
"code": code,
"desc": desc,
"data": data
}
return data
def get_md5(input_str: str, encoding: str = 'utf-8') -> str:
"""
获取字符串的 MD5 哈希值
Args:
input_str: 输入字符串
encoding: 编码方式默认UTF-8
Returns:
32位小写十六进制MD5哈希值
"""
# 创建MD5哈希对象
md5_hash = hashlib.md5()
# 将字符串编码为字节并更新哈希对象
md5_hash.update(input_str.encode(encoding))
# 返回十六进制哈希值
return md5_hash.hexdigest()
def get_db_path():
"""
Returns:
数据库文件路径
"""
# 获取当前文件所在目录的绝对路径(即 'app/api'
base_dir = os.path.dirname(os.path.abspath(__file__))
# 获取项目根目录(假设 'app' 目录在项目根目录下)
project_root = os.path.dirname(base_dir) # 这个将得到 'instrument_short_circuit'
# 拼接数据库文件路径
db_path = os.path.join(project_root, 'db', 'Data.db')
return db_path
#分页请求成功返回
def true_return_pagination(pagination=None, data=None, desc='请求成功', code=200):
data = {
"pagination": pagination,
"code": code,
"desc": desc,
"data": data
}
return data
#请求失败返回
def false_return(data='', desc='请求失败', code=-1):
data = {
"code": code,
"desc": desc,
"data": data
}
return data
#生成统一格式接口数据
def ResultData(status,message,data=""):
return json.dumps({"status":status,"message":message,"data":data},cls=AlchemyEncoder)
#按时间生成随机文件名
def getFileName():
nowTime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
randomNum = random.randint(0,100)
if randomNum <= 10:
randomNum = str(0) + str(randomNum)
uniqueNum = str(nowTime) + str(randomNum)
return uniqueNum
#获取当前插入U盘路径
def getUDiskPath():
if system_name == 'Windows':
disk_list = psutil.disk_partitions()
# 获取U盘路径
u_path = [disk.device for disk in disk_list if disk.opts == 'rw,removable']
if u_path:
return u_path[0]
elif system_name == 'Linux':
import pyudev
context = pyudev.Context()
removable = [device for device in context.list_devices(subsystem='block', DEVTYPE='disk') if device.attributes.asstring('removable') == "1"]
for device in removable:
partitions = [device.device_node for device in context.list_devices(subsystem='block', DEVTYPE='partition', parent=device)]
for p in psutil.disk_partitions():
if p.device in partitions:
return p.mountpoint
return ""
#创建文件夹
def mkdir(path):
folder = os.path.exists(path)
if not folder:
os.makedirs(path)
#int列表转换为字节流
def int_list_to_bytes(int_list):
byte_stream = b''
for num in int_list:
byte_stream += struct.pack('>H', num)
return byte_stream
#字节流转换为int列表
def bytes_to_int_list(byte_stream):
int_list = []
for i in range(0, byte_stream.__len__(), 2):
int_list.append(struct.unpack('>H', byte_stream[i:i+2])[0])
return int_list
#获取某比特的值
def get_bit(data: int, bit: int) -> int:
if data & (1 << bit):
return 1
else:
return 0
#获取某比特的值,并转换为布尔值
def get_bit_bool(data: int, bit: int) -> bool:
if data & (1 << bit):
return True
else:
return False
#修改某比特的值,并返回新值
def set_bit(number: int, pos: int, value: int):
new_number = None
if value == 1:
new_number = number | (1 << pos)
elif value == 0:
new_number = number & ~(1 << pos)
return new_number
#获取温度数据
def str_to_list(str_list):
if str_list:
str_list = str_list[:-1].strip('[]')
result = [list(map(eval, sublist.split(','))) for sublist in str_list.split('],[')]
return result
return []
#上升速率,下降率,data数据列表,num时间间隔,tag(1罐体2炉体),t=0返回x轴为温度反之为时间
def calculate_temp_rate(data,num,tag,t=0):
result = []
x=[]
y1 = []
y2 = []
for i in range(0, len(data)-num, 30):
time_interval = data[i+num][0] - data[i][0]
if time_interval == 0:
continue
temp_rate = (data[i+num][1] - data[i][1]) / time_interval * 3600
if t==0:
result.append([data[i+num][tag],abs(temp_rate)])
else:
result.append([data[i+num][0],data[i+num][tag],abs(temp_rate)])
x.append(data[i+num][0])
y1.append(data[i+num][tag])
y2.append(abs(temp_rate))
p_fit = Utils.get_quadratic_curve(result)
return result,p_fit,x,y1,y2
# 定义二次函数模型
def quadratic_func(x, a, b, c):
return a * x**2 + b * x + c
#二次函数导出公式
def derivative_func(x, a, b):
return 2* a * x + b
#比热计算公式Cp2
def specific_heat_func(E2, K, C,M2,H):
return 3600*(E2+K)/C*M2-H/M2
#发热率计算公式
def heat_generation_rate_func(M2, Cp2, H, D ,K):
return ((M2*Cp2+H)*D/3600-K)/M2
#获取二次曲线p_fit为二次曲线系数
def get_quadratic_curve(data):
x_data = np.array([i[0] for i in data])
y_data = np.array([i[1] for i in data])
degree = 2 # 二次多项式拟合
p_fit = np.polyfit(x_data, y_data, degree)
y_fit = np.polyval(p_fit, x_data)
# plt.figure()
# plt.scatter(x_data, y_data, label='Original Data')
# plt.plot(x_data, y_fit, 'r-', label='Fitted Curve')
# plt.xlabel('x')
# plt.ylabel('y')
# plt.title('Polynomial Fitting')
# plt.legend()
# plt.grid()
# plt.show()
return p_fit
# 热容量计算公式
def heat_capacity_func(E1, A, B, M1,Cp1):
# print(type(E1), type(A), type(B), type(M1),type(Cp1))
return (3600*E1)/(A+B)-(M1*Cp1)
#热损失计算公式
def heat_loss_func(H, A, M1,Cp1):
return A*(H+M1*Cp1)/3600
# 计算杜瓦瓶热容量
def calculate_heat_capacity(data, data1, E1, M1, Cp1):
capacity = 0
result, p_fit, x, r1, r2 = Utils.calculate_temp_rate(data, 30, 2)
result1, p_fit1, x, r1, r2 = Utils.calculate_temp_rate(data1, 30, 2)
for i in range(len(result)):
A = abs(Utils.quadratic_func(result[i][0], p_fit[0], p_fit[1], p_fit[2]))
B = abs(Utils.quadratic_func(result[i][0], p_fit1[0], p_fit1[1], p_fit1[2]))
H = abs(Utils.heat_capacity_func(E1, A, B, M1, Cp1))
capacity += H
return round(capacity/len(result), 2)
# 计算热容量及热损失(上升数据与下降数据不等长)
def calculate_heat_capacity_loss(data,data1,E1,M1,Cp1):
capacity=[]
capacity_sum = 0
loss=[]
temp_data=[]#点位上升速率
temp_data1=[]#点位下降速率
result,p_fit,x,r1,r2 = Utils.calculate_temp_rate(data,30,1)
result1,p_fit1,x,r1,r2 = Utils.calculate_temp_rate(data1,30,1)
for i in range(len(result)):
A = abs(Utils.quadratic_func(result[i][0],p_fit[0],p_fit[1],p_fit[2]))
B = abs(Utils.quadratic_func(result[i][0],p_fit1[0],p_fit1[1],p_fit1[2]))
H = abs(Utils.heat_capacity_func(E1, A, B, M1,Cp1))
capacity.append([result[i][0],H])
loss.append([result[i][0],Utils.heat_loss_func(H, A, M1,Cp1)])
for n in settings.TEM:
A = abs(Utils.quadratic_func(n,p_fit[0],p_fit[1],p_fit[2]))
B = abs(Utils.quadratic_func(n,p_fit1[0],p_fit1[1],p_fit1[2]))
temp_data.append(round(A, 4))
temp_data1.append(round(B, 4))
H = abs(Utils.heat_capacity_func(E1, A, B, M1, Cp1))
capacity_sum += H
capacity_value = round(capacity_sum/len(settings.TEM), 2)
return result,p_fit,result1,p_fit1,capacity,loss,temp_data,temp_data1,capacity_value
#获取K热损失H热容量2次曲线
def get_K_func(loss,capacity):
p_fit = Utils.get_quadratic_curve(loss)
p_fit1 = Utils.get_quadratic_curve(capacity)
return p_fit,p_fit1
# 计算比热Cp2与发热率data加热data1自加热
def calculate_heat_generation_rate(data,data1,data2,E2,M2,loss,capacity):
Cp2_list=[]
Qt_list=[]
r2,p_fit2,x,r1,r2 = Utils.calculate_temp_rate(data1,30,1)#校准上升率曲线
r3,p_fit3,x,r1,r2 = Utils.calculate_temp_rate(data2,30,1)#试验自加热斜率曲线
p_fit,p_fit1 =Utils.get_K_func(loss,capacity)#热损失热容量2次曲线
# print(p_fit,p_fit1,p_fit2,p_fit3)
result = data2
for i in range(len(result)):
C =Utils.quadratic_func(result[i][0],p_fit2[0],p_fit2[1],p_fit2[2])
K =Utils.quadratic_func(result[i][0],p_fit[0],p_fit[1],p_fit[2])
H =Utils.quadratic_func(result[i][0],p_fit1[0],p_fit1[1],p_fit1[2])
D =Utils.quadratic_func(result[i][0],p_fit3[0],p_fit3[1],p_fit3[2])
Cp2 =Utils.specific_heat_func(E2, K, C,M2,H)
Qt =Utils.heat_generation_rate_func(M2, Cp2, H, D ,K)
Cp2_list.append([result[i][0],Cp2])
Qt_list.append([result[i][1],Qt])
return Cp2_list,Qt_list
def format_status_data(status_data):
result_data = copy.deepcopy(status_data)
# if "g_temp" in result_data:
# result_data["g_temp"] = round(status_data["g_temp"], 2)
#
# if "l_temp" in result_data:
# result_data["l_temp"] = round(status_data["l_temp"], 2)
#
# if "last_g_temp" in result_data:
# result_data["last_g_temp"] = round(status_data["last_g_temp"], 2)
return result_data
def sample_by_interval(arr, interval=30):
if len(arr) <= interval:
return arr
indices = range(0, len(arr), interval)
sampled_array = [arr[i] for i in indices]
return sampled_array
class AlgorithmUtils:
@staticmethod
def heat_loss_func(H, A, M1,Cp1):
return A*(H+M1*Cp1)/3600
@staticmethod
def heat_capacity_func(E1, A, B, M1,Cp1):
return (3600*E1)/(A+B)-(M1*Cp1)
# 定义二次函数模型
@staticmethod
def quadratic_func(x, a, b, c):
return a * x**2 + b * x + c
@staticmethod
def specific_heat_func(E2, K, C,M2,H):
return 3600*(E2+K)/C*M2-H/M2
@staticmethod
def heat_generation_rate_func(M2, Cp2, H, D ,K):
return ((M2*Cp2+H)*D/3600-K)/M2
@staticmethod
def get_quadratic_curve(data):
x_data = np.array([i[0] for i in data])
y_data = np.array([i[1] for i in data])
degree = 2 # 二次多项式拟合
return np.polyfit(x_data, y_data, degree)
def capacity_specify_temperature(self, jz_raw_data, rise_p_fit, drop_p_fit, E1, M1, Cp1):
"""计算特定温度下的值"""
capacity, loss, capacity_p_fit, loss_p_fit = self.capacity_loss(jz_raw_data, rise_p_fit, drop_p_fit, E1, M1, Cp1)
rise_temp_values = []
drop_temp_values = []
capacity_values = []
for t in [40, 60, 80, 100]:
B = self.quadratic_func(t, rise_p_fit[0], rise_p_fit[1], rise_p_fit[2]) # 升温速率
rise_temp_values.append(round(B, 4))
A = self.quadratic_func(t, drop_p_fit[0], drop_p_fit[1], drop_p_fit[2]) # 降温
drop_temp_values.append(round(A, 4))
H = self.heat_capacity_func(E1, A, B, M1, Cp1)
capacity_values.append(H) # 杜瓦瓶热容量
capacity_value = sum(capacity_values)/len(capacity_values)
return capacity, loss, rise_temp_values, drop_temp_values, capacity_value
def calculate_temp_rate(self, data, num=30):
result = []
for i in range(0, len(data)-num, num):
time_interval = int(data[i+num][0]) - int(data[i][0])
if time_interval == 0:
continue
temp_rate = (float(data[i+num][1]) - float(data[i][1])) / time_interval * 3600
result.append([float(data[i+num][1]), abs(temp_rate)])
return result
def time_temp_rate(self, raw_data, num=60):
r = []
x = []
y1 = []
y2 = []
for i in range(0, len(raw_data) - num, num):
time_interval = int(raw_data[i + num][0]) - int(raw_data[i][0])
if time_interval == 0:
continue
temp_rate = (float(raw_data[i + num][1]) - float(raw_data[i][1])) / time_interval * 3600
r.append([int(raw_data[i+num][0]), float(raw_data[i+num][1]), abs(temp_rate)])
x.append(int(raw_data[i + num][0]))
y1.append(float(raw_data[i + num][1]))
y2.append(abs(temp_rate))
return r, x, y1, y2
def capacity_loss(self, jz_raw_data, rise_p_fit, drop_p_fit, E1, M1, Cp1):
"""计算热容量和热损失曲线"""
capacity = []
loss = []
raw_data = self.calculate_temp_rate(jz_raw_data, num=60)
for item in raw_data:
B = self.quadratic_func(item[0], rise_p_fit[0], rise_p_fit[1], rise_p_fit[2]) # 升温速率
A = self.quadratic_func(item[0], drop_p_fit[0], drop_p_fit[1], drop_p_fit[2]) # 降温速率 TODO
H = abs(self.heat_capacity_func(E1, A, B, M1, Cp1)) # 杜瓦瓶热容量
K = self.heat_loss_func(H, A, M1, Cp1) # 热损失
capacity.append([item[0], H])
loss.append([item[0], K])
capacity_p_fit = self.get_quadratic_curve(capacity)
loss_p_fit = self.get_quadratic_curve(loss)
return capacity, loss, capacity_p_fit, loss_p_fit
def calculate_heat_generation_rate(self, zs_data, capacity_p_fit, loss_p_fit, E2, M2):
# 自加速
self_heatingTemp_data = zs_data.get("self_heatingTemp")
self_heatingTemp_rate = self.calculate_temp_rate(self_heatingTemp_data, num=2)
self_heatingTemp_p_fit = self.get_quadratic_curve(self_heatingTemp_rate)
# 样品
riseTemp_data = zs_data.get("riseTemp")
riseTemp_rate = self.calculate_temp_rate(riseTemp_data, num=60)
rise_p_fit = self.get_quadratic_curve(riseTemp_rate)
Cp2_list = []
Qt_list = []
for item in self_heatingTemp_rate:
C = self.quadratic_func(item[0],rise_p_fit[0],rise_p_fit[1],rise_p_fit[2])
K = self.quadratic_func(item[0],loss_p_fit[0],loss_p_fit[1],loss_p_fit[2])
H = self.quadratic_func(item[0],capacity_p_fit[0],capacity_p_fit[1],capacity_p_fit[2])
D = self.quadratic_func(item[0],self_heatingTemp_p_fit[0],self_heatingTemp_p_fit[1],self_heatingTemp_p_fit[2])
Cp2 = self.specific_heat_func(E2, K, C,M2,H)
Qt = self.heat_generation_rate_func(M2, Cp2, H, D,K)
Cp2_list.append([item[0],Cp2])
Qt_list.append([item[0],Qt])
return Cp2_list, Qt_list
class ChannelStatusError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)

@ -0,0 +1,204 @@
import os
import time
import json
import ctypes
import threading
from ctypes import *
from datetime import datetime
from app.conf.Setting import settings
from app.lib.Utils import Utils
from app.lib.StaticData import data_lists, StatusDict, Flag
from app.lib.Log import logger
class VK70xUMCDAQ():
def __init__(self):
self.Record ='' # 试验id
self.pressure=None # 压力
self.temperature=None # 温度
self.theadPID = None # 采集线程
self.recordFlag = False # 是否开始记录及开启N采集模式
self.recordIndex = 0 # 当前记录数量
self.recordTemp1Data = [] # 温度值,压力值
# self.recordTemp2Data = [] # 压力值
self.origin_data = []
def save_data(self):
dir_path = os.path.join(settings.BASE_PATH, f"resource/{self.Record}/")
if not os.path.exists(dir_path):
os.mkdir(dir_path)
with open(os.path.join(dir_path, "temp.json"), 'w', encoding="utf-8") as f:
f.write(str(self.recordTemp1Data))
with open(os.path.join(dir_path, "origin_data.json"), "w", encoding="utf-8") as f1:
f1.write(str(self.origin_data))
logger.debug("数据保存完成")
def start(self):
self.theadPID = None
time.sleep(1)
if not self.theadPID:
self.theadPID = threading.Thread(target=self.startThread)
self.theadPID.start()
def startRecord(self):
self.recordTemp1Data = []
# self.recordTemp2Data = []
self.recordIndex = 0
self.recordFlag = True
def stopRecord(self):
self.recordFlag = False
def read(self):
return self.pressure, self.temperature
def startThread(self):
vk70xnhdll = windll.LoadLibrary('./VK70xUMC_DAQ2.dll')
ret = vk70xnhdll.USBDev_Open()#打开usb端口
connectedclientnum = c_long(0)
keepLoopRunning = c_long(0)
if self.recordFlag:
SAMPLINGFREQUENCY =settings.SAMPLINGFREQUENCY
else:
SAMPLINGFREQUENCY =settings.SAMPLINGFREQUENCY1
if ret < 0:
logger.error('USB 端口异常或已被占用!')
return
keepLoopRunning = 1
while keepLoopRunning:
time.sleep(0.1)
ret = vk70xnhdll.USBDev_Get_ConnectedClientNumbers(byref(connectedclientnum)) # 读取已连接采集卡数量
logger.debug(f'连接到当前服务器的DAQ设备数: {connectedclientnum.value}')
if connectedclientnum.value > 0:
keepLoopRunning = 0
time.sleep(0.1)
if connectedclientnum.value>0:
logger.debug('准备安装DAQ')
keepLoopRunning = 1
#VK70xUMC_InitializeAll方法参数
paraInitialize = (ctypes.c_int32 * 20)()
for i in range(20):
paraInitialize[i] = 0
paraInitialize[0] = 0x22 # 采样命令
paraInitialize[1] = SAMPLINGFREQUENCY # 采样率
# paraInitialize[1] = 20000 # 采样率
paraInitialize[12] = 1 # 通道1压力开启IEPE
paraInitialize[4] = 1 # 通道1压力-5V~5V
paraInitialize[5] = 5 # 通道2温度-100mV~100mV
ret = vk70xnhdll.VK70xUMC_InitializeAll(0, paraInitialize, 20) #初始化参数
if ret < 0:
keepLoopRunning = 0
logger.error(f'参数初始化失败! {ret}')
else:
logger.debug(f'参数初始化成功! {ret}')
time.sleep(0.1)
ret = vk70xnhdll.VK70xUMC_Set_AdditionalFeature(0, 23, 2, 0) #设置IO3为输入状态
if ret < 0:
keepLoopRunning = 0
logger.error(f'IO3输入状态设置失败! {ret}')
else:
logger.debug(f'IO3输入状态设置成功! {ret}')
time.sleep(0.1)
if self.recordFlag == False:
ret = vk70xnhdll.VK70xUMC_Set_AdditionalFeature(0, 21, 1, 0) #设置IO1为高电平
if ret < 0:
keepLoopRunning = 0
logger.error(f'设置IO1为高电平失败! {ret}')
else:
logger.debug(f'设置IO1为高电平成功! {ret}')
time.sleep(0.1)
ret = vk70xnhdll.VK70xUMC_StartSampling(0)#开始连续采样
else:
ret = vk70xnhdll.VK70xUMC_Set_AdditionalFeature(0, 21, 0, 0) # 设置IO1为低电平
if ret < 0:
keepLoopRunning = 0
logger.error(f'设置IO1为低电平失败! {ret}')
else:
logger.debug(f'设置IO1为低电平成功! {ret}')
ret = vk70xnhdll.VK70xUMC_StartSampling_NPoints(0,settings.GETPOINTS) # 固定采样总数N
if ret < 0:
keepLoopRunning = 0
logger.error(f'采集异常! {ret}')
else:
logger.debug(f'开始采集! {ret}')
num = SAMPLINGFREQUENCY * 4
data_buffer = (c_double * num)()
warning = 0
ending = 0
while keepLoopRunning and Flag["SHIYAN_Flag"]:
time.sleep(0.1)
collect_num = vk70xnhdll.VK70xUMC_GetFourChannel(0, data_buffer, SAMPLINGFREQUENCY) # 读取采样数据
datas = [data_buffer[i:i+4] for i in range(0, len(data_buffer), 4)]
IO4_buffer = (c_int * 1)(3)
ret = vk70xnhdll.VK70xUMC_Get_IO4(0, IO4_buffer) # 读取IO4高低电平
if ret < 0:
warning += 1
if warning >= 10:
keepLoopRunning = 0
logger.error(f'获取IO4电平异常! {ret}')
logger.debug(f"数据长度: {len(datas)}, 采样点数:{collect_num}, IO4电平: {IO4_buffer[0]}, recordFlag: {self.recordFlag}")
if IO4_buffer[0] == 0:
if self.recordFlag == False:#低电平,连续采集(切换模式)
logger.debug("==================== 切换采集模式 ================")
keepLoopRunning = 0
self.startRecord()
self.start()
break
if collect_num > 0:
for i in range(int(collect_num)):
burnTem = datas[i]
pressure = burnTem[0] # 压力
temperature = burnTem[1] # 温度
# 转换数据
data, k, b, index, temperature = Utils.binary_search(data_lists, 0, len(data_lists)-1, temperature * 1000)
self.temperature = temperature
self.pressure = pressure
if StatusDict['temperature']:
self.temperature = temperature + float(StatusDict['temperature'])
self.pressure = pressure / settings.K * 1000
if IO4_buffer[0] == 0 and self.recordFlag == True:
self.recordIndex += 1
self.origin_data.append(burnTem)
self.recordTemp1Data.append([self.recordIndex, self.temperature, self.pressure])
if self.recordFlag == True and collect_num == 0: # N采集采集数量0采集结束关闭
ending += 1
if ending >= 2:
logger.debug(f'采集数量: {self.recordIndex}')
self.save_data()
keepLoopRunning = 0
self.stopRecord()
time.sleep(0.1)
ret = vk70xnhdll.VK70xUMC_StopSampling(0) # 停止当前采集卡采样
time.sleep(0.1)
self.theadPID = None
logger.debug('关闭端口!')
if __name__ =='__main__':
tempCollect=VK70xUMCDAQ()
tempCollect.start()

@ -0,0 +1,46 @@
import getpass
import glob
import platform
from io import BytesIO
from os import path
import psutil
system_name = platform.system()
class DeviceNotFound(Exception):
pass
def fetch():
"""获取U盘中的Excel文件"""
if system_name == 'Windows':
usbs = [disk.device for disk in psutil.disk_partitions() if disk.opts == 'rw,removable']
if not usbs:
raise DeviceNotFound()
usb = usbs[0]
files = glob.glob(f'{usb}/*.xlsx') + glob.glob(f'{usb}/*.xls')
return [{"name": path.basename(f), "file": f} for f in files]
usbs = [item for item in psutil.disk_partitions() if item.mountpoint.startswith(f'/media/{getpass.getuser()}')]
if not usbs:
raise DeviceNotFound()
usb = usbs[0]
files = glob.glob(f'{usb.mountpoint}/*.xlsx') + glob.glob(f'{usb.mountpoint}/*.xls')
return [{"name": path.basename(f), "file": f} for f in files]
def put_in(filename: str, bin: BytesIO):
if system_name == 'Windows':
usbs = [disk.device for disk in psutil.disk_partitions() if disk.opts == 'rw,removable']
if not usbs:
raise DeviceNotFound()
path_file = path.join(usbs[0], filename)
else:
usbs = [item for item in psutil.disk_partitions() if item.mountpoint.startswith(f'/media/{getpass.getuser()}')]
if not usbs:
raise DeviceNotFound()
path_file = path.join(usbs[0].mountpoint, filename)
with open(path_file, 'wb') as f:
f.write(bin.getbuffer())

@ -0,0 +1,20 @@
from fastapi import WebSocket
class ConnectionManager:
def __init__(self):
self.active_connections: list[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_data(self, message: dict, websocket: WebSocket):
await websocket.send_json(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)

@ -0,0 +1,5 @@
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
__all__ = ["Base"]

@ -0,0 +1,382 @@
from app.models.Repository import OrmBase
from app.models.DateEntity import EntityUser, EntityRole, DeviceConfig, ExperimentSpecimen, ExperimentInfo, \
ExperimentReport, Channel
from app.lib.Utils import *
from sqlalchemy import and_, or_, desc, text
# 分页参数模型
class PageParam(object):
def __init__(self, curPage, pageRows, totalRecords=None, orderType=None, orderField=None):
self.orderType = orderType # 排序类型 asc=正序 desc=倒序
self.orderField = orderField # 排序字段
self.pageRows = pageRows # 单页行数
self.curPage = curPage # 当前页码
self.totalRecords = totalRecords # 总页数
# 用户逻辑类
class BllUser(OrmBase):
def __init__(self, entityType=EntityUser):
super().__init__(entityType)
# 用户登录
def login(self, username, password):
return self.findEntity(and_(EntityUser.user_name == username, EntityUser.user_pwd == Utils.MD5(password), EntityUser.is_enabled == 0))
def facelogin(self, id):
return self.findEntity(EntityUser.user_id == id)
# 获取用户列表
def getUserList(self):
return self.findList(EntityUser.is_enabled == 0)
# 获取用户详情信息
def getUserInfo(self, userId):
return self.findEntity(userId)
# 设备配置类
class BllDeviceConfig(OrmBase):
def __init__(self, entityType=DeviceConfig):
super().__init__(entityType)
# 获取设置详情信息
def getInfo(self):
return self.findEntity()
# 实验信息类
class BllExperimentInfo(OrmBase):
def __init__(self, entityType=ExperimentInfo):
super().__init__(entityType)
# 获取试验列表
def getExperimentList(self, search_word):
orm_query = self.findList(ExperimentInfo.is_enabled == 0)
if search_word.name:
orm_query = orm_query.filter(ExperimentInfo.specimen_name.like(f"%{search_word.name}%"))
if search_word.date:
orm_query = orm_query.filter(ExperimentInfo.created_time.contains(search_word.date))
if search_word.experiment_id:
orm_query = orm_query.filter(ExperimentInfo.id == search_word.experiment_id)
# if search_word.state:
# orm_query = orm_query.filter(ExperimentInfo.state == search_word.state)
return orm_query.order_by(desc(ExperimentInfo.created_time))
# 实验报表类
class BllExperimentReport(OrmBase):
def __init__(self, entityType=ExperimentReport):
super().__init__(entityType)
# 通道类
class BllChannel(OrmBase):
def __init__(self, entityType=Channel):
super().__init__(entityType)
# 材料类
class BllExperimentSpecimen(OrmBase):
def __init__(self, entityType=ExperimentSpecimen):
super().__init__(entityType)
# 角色逻辑类
class BllRole(OrmBase):
def __init__(self, entityType=EntityRole):
super().__init__(entityType)
# 获取角色列表
def getRoleList(self):
return self.findList(EntityRole.is_enabled == 0).all()
#
#
# class BllSpecimen(OrmBase):
# def __init__(self, entityType=EntitySpecimen):
# super().__init__(entityType)
#
# def insertSpecimen(self, **kwargs):
# entity = EntitySpecimen()
# entity.specimen_id = kwargs["specimen_id"]
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
#
# entity.specimen_name = kwargs["specimen_name"]
# entity.specimen_code = kwargs["specimen_code"]
# entity.specimen_type = kwargs["specimen_type"]
# entity.specimen_origin = kwargs["specimen_origin"]
# entity.specimen_desc = kwargs["specimen_desc"]
# entity.storage_location = kwargs["storage_location"]
#
# entity.remark = kwargs["remark"]
# entity.rfid = kwargs["rfid"]
# entity.created_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#
# entity.is_enabled = 0
# self.insert(entity)
#
# def updateSpecimen(self, **kwargs):
# entity = self.findEntity(EntitySpecimen.specimen_id == kwargs["specimen_id"])
# if "rfid" in kwargs:
# entity.rfid = kwargs.get("rfid")
# entity.rfid_state = 1 if kwargs.get("rfid") else 2
# if "inventory_status" in kwargs:
# entity.inventory_status = kwargs.get("inventory_status")
# if "disinfection_state" in kwargs:
# entity.disinfection_state = kwargs.get("disinfection_state")
#
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# self.update(entity)
# return entity
#
# def get_specimen_list(self, search_word):
# orm_query = self.findList(EntitySpecimen.is_enabled == 0)
# if search_word.specimen_name:
# orm_query = orm_query.filter(EntitySpecimen.specimen_name.like(f"%{search_word.specimen_name}%"))
# if hasattr(search_word, 'specimen_id') and search_word.specimen_id:
# orm_query = orm_query.filter(EntitySpecimen.specimen_id == search_word.specimen_id)
# if hasattr(search_word, 'record_id') and search_word.record_id:
# orm_query = orm_query.filter(EntitySpecimen.specimen_id == search_word.record_id)
# if hasattr(search_word, 'date') and search_word.date:
# orm_query = orm_query.filter(EntitySpecimen.created_time.contains(search_word.date))
# return orm_query.order_by(desc(EntitySpecimen.update_time))
#
# def delSpecimen(self, specimen_id):
# entity = self.findEntity(EntitySpecimen.specimen_id == specimen_id)
# entity.is_enabled = 1
# self.update(entity)
#
#
# class BllWaitDisinfSpecimen(OrmBase):
# def __init__(self, entityType=EntityWaitDisinfSpecimen):
# super().__init__(entityType)
#
# def insert_wait_disinf_specimen(self, **kwargs):
# entity = EntityWaitDisinfSpecimen()
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
# entity.record_id = kwargs["record_id"]
# entity.specimen_id = kwargs["specimen_id"]
# entity.specimen_name = kwargs["specimen_name"]
# entity.specimen_code = kwargs["specimen_code"]
# entity.specimen_desc = kwargs["specimen_desc"]
# entity.specimen_origin = kwargs["specimen_origin"]
# entity.specimen_type = kwargs["specimen_type"]
# entity.storage_location = kwargs["storage_location"]
# entity.remark = kwargs["remark"]
# entity.rfid = kwargs["rfid"]
# entity.disinf_type = kwargs["disinf_type"]
# entity.created_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.is_enabled = 0
# self.insert(entity)
#
# def get_wait_disinf_specimen_list(self, search_word):
# orm_query = self.findList(and_(EntityWaitDisinfSpecimen.is_enabled == 0,
# EntityWaitDisinfSpecimen.done == 0))
# if hasattr(search_word, 'user_id') and search_word.user_id:
# orm_query = orm_query.filter(EntityWaitDisinfSpecimen.user_id == search_word.user_id)
# return orm_query.order_by(desc(EntityWaitDisinfSpecimen.update_time))
#
#
#
# class BllExperimentConfig(OrmBase):
#
# def __init__(self, entityType=EntityExperimentConfig):
# super().__init__(entityType)
#
# def insertConfig(self, **kwargs):
# entity = EntityExperimentConfig()
# entity.config_id = kwargs["config_id"]
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
#
# entity.ozone_disinf = kwargs["ozone_disinf"]
# entity.uv_disinf = kwargs["uv_disinf"]
# entity.ozone_conc = kwargs["ozone_conc"]
# entity.disinf_time = kwargs["disinf_time"]
# entity.temperature = kwargs["temperature"]
# entity.humidity = kwargs["humidity"]
#
# entity.config_name = kwargs["config_name"]
# entity.config_desc = kwargs["config_desc"]
#
# entity.created_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# self.insert(entity)
#
# def get_config_list(self, search_word):
# orm_query = self.findList(EntityExperimentConfig.is_enabled == 0)
# if hasattr(search_word, 'user_id') and search_word.user_id:
# orm_query = orm_query.filter(EntityExperimentConfig.user_id == search_word.user_id)
# return orm_query.order_by(desc(EntityExperimentConfig.update_time))
#
#
# class BllExperiment(OrmBase):
# def __init__(self, entityType=EntityExperiment):
# super().__init__(entityType)
#
# def insertExperiment(self, **kwargs):
# specimen_ids = kwargs["specimens"]
# specimens = BllSpecimen().findList(and_(EntitySpecimen.specimen_id.in_(specimen_ids), EntitySpecimen.is_enabled == 0)).all()
#
# entity = EntityExperiment()
# entity.experiment_id = kwargs["experiment_id"]
# entity.name = kwargs["name"]
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
#
# entity.ozone_disinf = kwargs["ozone_disinf"]
# entity.uv_disinf = kwargs["uv_disinf"]
# entity.ozone_conc = kwargs["ozone_conc"]
# entity.disinf_time = kwargs["disinf_time"]
# entity.temperature = kwargs["temperature"]
# entity.humidity = kwargs["humidity"]
#
# entity.created_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.is_enabled = 0
#
# for specimen in specimens:
# from sqlalchemy.orm import object_session
# current_session = object_session(specimen)
# if current_session is not None:
# current_session.expunge(specimen)
# entity.specimens.append(specimen)
#
# self.insert(entity)
#
# def updateExperiment(self, **kwargs):
# entity = self.findEntity(kwargs["experiment_id"])
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
#
# self.update(entity)
# return entity
#
# # 获取试验列表
# def getExperimentList(self, search_word):
# orm_query = self.findList(EntityExperiment.is_enabled == 0)
# if search_word.name:
# orm_query = orm_query.filter(EntityExperiment.name.like(f"%{search_word.name}%"))
# if search_word.date:
# orm_query = orm_query.filter(EntityExperiment.created_time.contains(search_word.date))
# if search_word.experiment_id:
# orm_query = orm_query.filter(EntityExperiment.experiment_id == search_word.experiment_id)
# return orm_query.order_by(desc(EntityExperiment.update_time))
#
# # 删除单次试验
# def delExperiment(self, experiment_id):
# entity = self.findEntity(EntityExperiment.experiment_id == experiment_id)
# entity.is_enabled = 1
# self.update(entity)
#
# # 查询最后一次试验
# def getLastExperiment(self):
# orm_query = self.findList(EntityExperiment.is_enabled == 0)
# return orm_query.order_by(desc(EntityExperiment.created_time)).first()
#
# # 试验记录逻辑类
#
#
# class BllExperimentRecord(OrmBase):
#
# def __init__(self, entityType=EntityExperimentRecord):
# super().__init__(entityType)
#
# # 新增试验记录信息
#
# def insertExperimentRecord(self, **kwargs):
# entity = EntityExperimentRecord()
# entity.record_id = kwargs["record_id"]
# entity.experiment_id = kwargs["experiment_id"]
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
#
# entity.initial_press = kwargs["initial_press"]
# entity.final_press = kwargs["final_press"]
# entity.leakage_rate = kwargs["leakage_rate"]
# entity.dijici = kwargs["dijici"]
#
# entity.is_enabled = 0
# entity.created_time = kwargs['created_time'] if "created_time" in kwargs else datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# self.insert(entity)
#
# # 更新试验记录信息
# def updateExperimentRecord(self, **kwargs):
# entity = self.findEntity(EntityExperimentRecord.record_id == kwargs["record_id"])
# if entity:
# entity.update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# self.update(entity)
#
# # 获取单条试验记录信息
# def getExperimentRecord_by_record_id(self, record_id):
# data = self.findEntity(EntityExperimentRecord.record_id == record_id)
# return data
#
# # 获取单条试验记录信息
# def getExperimentRecord_by_experiment_id(self, experiment_id):
# data = self.findEntity(EntityExperimentRecord.experiment_id == experiment_id)
# return data
#
# # 删除单条记录
# def delExperimentRecord(self, record_id):
# entity = self.findEntity(EntityExperimentRecord.record_id == record_id)
# entity.is_enabled = 1
# self.update(entity)
#
#
# class BllUselog(OrmBase):
# def __init__(self, entityType=EntityUseLog):
# super().__init__(entityType)
#
# def insert_log(self, **kwargs):
# entity = EntityUseLog()
# entity.user_id = kwargs["user_id"]
# entity.user_name = kwargs["user_name"]
# entity.specimen_id = kwargs["specimen_id"]
# entity.specimen_code = kwargs["specimen_code"]
# entity.specimen_name = kwargs["specimen_name"]
# entity.operation_type = kwargs["operation_type"]
# entity.record_id = Utils.get_uuid()
# entity.created_time = Utils.get_time()
# entity.update_time = Utils.get_time()
# self.insert(entity)
#
#
# class BllTerminal(OrmBase):
# def __init__(self, entityType=EntityTerminal):
# super().__init__(entityType)
#
# def insert_terminal(self, **kwargs):
# entity = EntityTerminal()
# entity.name = kwargs["name"]
# entity.title = kwargs["title"]
# entity.login_count = kwargs["login_count"]
# entity.index_of_user = kwargs["index_of_user"]
# entity.id = Utils.get_uuid()
# entity.created_time = Utils.get_time()
# entity.update_time = Utils.get_time()
# self.insert(entity)
#
# def update_terminal(self, **kwargs):
# entity = self.findEntity(EntityTerminal.id == kwargs["id"])
# entity.name = kwargs["name"]
# entity.title = kwargs["title"]
# entity.login_count = kwargs["login_count"]
# entity.index_of_user = kwargs["index_of_user"]
# entity.update_time = Utils.get_time()
# self.update(entity)
#

@ -0,0 +1,304 @@
import datetime
from app.lib.Utils import Utils
from app.models.Base_model import Base
from sqlalchemy import Column, String, Integer, Float,Table, ForeignKey
from sqlalchemy.orm import relationship, backref
# 用户表
class EntityUser(Base):
__tablename__ = 'user'
user_id = Column(String(50), primary_key=True, comment="用户id")
user_name = Column(String(50), comment="用户昵称")
user_pwd = Column(String(50), comment="密码")
real_name = Column(String(50), comment="真实姓名")
user_sex = Column(Integer, comment="用户性别")
role_id = Column(String(50), comment="角色id")
user_phone = Column(String(50), comment="手机号")
remark = Column(String(500), comment="备用字段")
created_time = Column(String(50), default=Utils.get_time(), comment="用户创建时间")
update_time = Column(String(50), comment="用户更新时间")
is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
# 设备配置表
class DeviceConfig(Base):
__tablename__ = 'device_config'
id = Column(String(50), primary_key=True, comment="id")
box_temperature = Column(Float, comment="设备温度")
surface_temperature = Column(Float, comment="表面温度")
door_status = Column(Integer, comment="门状态")
light_status = Column(Integer, comment="灯光状态")
fan_status = Column(Integer, comment="风扇状态")
warning_light_status = Column(Integer, comment="报警灯状态")
lock_status = Column(Integer, comment="锁状态")
# 材料表
class ExperimentSpecimen(Base):
__tablename__ = "experiment_specimen"
id = Column(String(50), comment="id", primary_key=True)
specimen_name = Column(String(50), comment="样品名称")
battery_type = Column(String(50), comment="电池型号")
specimen_num = Column(String(50), comment="样品编号")
battery_specification = Column(String(255), comment="电池规格")
battery_manufacturers = Column(String(500), comment="电池厂家")
remark = Column(String(500), comment="备注")
created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
update_time = Column(String(50), comment="更新时间")
user_id = Column(String(50), comment="创建人")
user_name = Column(String(50), comment="用户昵称")
is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
md5 = Column(String(256), comment="md5值 样品名称+电池型号+样品编号+电池规格+电池厂家+备注")
# 实验信息表
class ExperimentInfo(Base):
__tablename__ = "experiment_info"
id = Column(String(50), comment="id", primary_key=True)
specimen_name = Column(String(50), comment="样品名称")
battery_type = Column(String(50), comment="电池型号")
specimen_num = Column(String(50), comment="样品编号")
battery_specification = Column(String(255), comment="电池规格")
battery_manufacturers = Column(String(500), comment="电池厂家")
remark = Column(String(500), comment="备注")
temperature = Column(Float, comment="设置温度")
expose_time = Column(Float, comment="暴露时间")
put_time = Column(Float, comment="放置时间")
short_circuit_param = Column(Float, comment="短路参数")
desc = Column(String(500), comment="描述")
created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
update_time = Column(String(50), comment="更新时间")
end_time = Column(String(50), comment="结束时间")
user_id = Column(String(50), comment="创建人")
user_name = Column(String(50), comment="用户昵称")
is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
state = Column(Integer, default=0, comment="状态 0未开始 1进行中 2已完成 3已取消")
specimen_id = Column(String(50), comment="材料表id")
channel_id = Column(String(256), comment="通道表id")
# 实验记录报表
class ExperimentReport(Base):
__tablename__ = "experiment_report"
id = Column(String(50), comment="id", primary_key=True)
experiment_id = Column(String(50), comment="实验表id")
channel_id = Column(String(50), comment="通道表id")
time = Column(Integer, default=0, comment="进行时间")
experiment_temperature = Column(Float, comment="实验温度")
surface_temperature = Column(Float, comment="表面温度")
battery_voltage = Column(Float, comment="电池电压")
battery_electricity = Column(Float, comment="电池电流")
recharge_capacity = Column(Float, comment="充电容量")
discharging_capacity = Column(Float, comment="放电容量")
internal_resistance = Column(Float, comment="内阻")
# 通道表
class Channel(Base):
__tablename__ = "channel"
id = Column(String(50), comment="id", primary_key=True)
name = Column(String(50), comment="通道名称")
status = Column(Integer, comment="状态 0未使用 1使用中")
last_start_time = Column(String(50), comment="最后开始时间")
# 角色表
class EntityRole(Base):
__tablename__ = 'role'
role_id = Column(String(50), primary_key=True, comment="角色id")
role_name = Column(String(50), comment="角色名称")
role_code = Column(String(50), comment="角色代号")
role_level = Column(Integer, comment="角色等级")
remark = Column(String(500), comment="备用字段")
created_time = Column(String(50), default=Utils.get_time(), comment="角色创建时间")
update_time = Column(String(50), comment="角色更新时间")
is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
specimen_experiment = Table('specimen_experiment', Base.metadata,
Column('specimen_id', Integer, ForeignKey('specimen.specimen_id')),
Column('experiment_id', Integer, ForeignKey('experiment.experiment_id'))
)
# # 证物表
# class EntitySpecimen(Base):
# __tablename__ = "specimen"
#
# specimen_id = Column(String(50), comment="物品id", primary_key=True)
# specimen_name = Column(String(50), comment="物品名称")
# specimen_code = Column(String(50), comment="物品编号")
# specimen_type = Column(String(50), comment="物品类型")
# specimen_origin = Column(String(255), comment="物品来源")
# specimen_desc = Column(String(500), comment="物品描述")
# storage_location = Column(String(255), comment="存放位置")
# remark = Column(String(500), comment="备注")
# rfid = Column(String(50), comment="条码")
#
# inventory_status = Column(Integer, comment="库存状态") # 1在库2出库
# rfid_state = Column(Integer, comment="条码状态") # 1已绑定2未绑定
# '''
# 0:未消毒:没有消毒过;
# 1:三天内已消毒在3天之内有消毒记录
# 2:一周内已消毒在7天之内有消毒记录
# 3:一月内已消毒在30天之内有消毒记录
# 4:三月内已消毒在90天之内有消毒记录
# 5:半年内已消毒在180天之内有消毒记录
# 6:一年内已消毒在365天之内有消毒记录
# 7:超过一年未消毒最近一次消毒已超过365天
# '''
# disinfection_state = Column(Integer, comment="消毒状态")
#
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
# user_id = Column(String(50), comment="创建人")
# user_name = Column(String(50), comment="用户昵称")
#
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
#
# last_disinf_time = Column(String(50), comment="最新一次消毒时间")
#
# experiments = relationship('EntityExperiment', secondary=specimen_experiment, back_populates='specimens')
#
#
# def to_dict(self):
# return dict(id=self.specimen_id, specimen_name = self.specimen_name, specimen_code=self.specimen_code,
# specimen_type=self.specimen_type, specimen_origin=self.specimen_origin,
# specimen_desc=self.specimen_desc, storage_location=self.storage_location,
# remark=self.remark, rfid=self.rfid, inventory_status=self.inventory_status,
# rfid_state=self.rfid_state, disinfection_state=self.disinfection_state,
# last_disinf_time = self.last_disinf_time,
# created_time=self.created_time, user_name=self.user_name)
#
# class EntityUseLog(Base):
# __tablename__ = "use_log"
# record_id = Column(String(50), comment="记录id",primary_key=True)
# specimen_id = Column(String(50), comment="物品id")
# specimen_name = Column(String(50), comment="物品名称")
# specimen_code = Column(String(50), comment="物品编号")
# operation_type = Column(String(50), comment="操作类型")
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
# user_id = Column(String(50), comment="创建人")
# user_name = Column(String(50), comment="用户昵称")
#
#
# class EntityTerminal(Base):
# __tablename__ = "terminals"
#
# id = Column(String(50), comment="id",primary_key=True)
# name = Column(String(50), comment="名称")
# title = Column(String(50), comment="标题")
# login_count = Column(Integer, comment="核验账号数")
# index_of_user = Column(Integer, comment="以第几人为使用者")
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
#
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
#
#
#
# class EntityWaitDisinfSpecimen(Base):
# __tablename__ = "wait_disinf_specimen"
# '''
# 待消毒物品记录 因一个证物可以在同一时间段被消毒多次
# '''
#
# record_id = Column(String(50), comment="记录id",primary_key=True)
# specimen_id = Column(String(50), comment="物品id")
#
# specimen_name = Column(String(50), comment="物品名称")
# specimen_code = Column(String(50), comment="物品编号")
# specimen_type = Column(String(50), comment="物品类型")
# specimen_origin = Column(String(255), comment="物品来源")
# specimen_desc = Column(String(500), comment="物品描述")
# storage_location = Column(String(255), comment="存放位置")
# remark = Column(String(500), comment="备注")
# rfid = Column(String(50), comment="条码")
#
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
# user_id = Column(String(50), comment="创建人")
# user_name = Column(String(50), comment="用户昵称")
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
# done = Column(Integer, default=0, comment="是否完成") # 0 未完成1 已完成
# disinf_type = Column(Integer, comment="消毒类型") # 1 入库消毒2 出库消毒3 其他
#
#
# # 试验配置表 每个员工可以自定多个配置,以方便后续消毒操作
# class EntityExperimentConfig(Base):
# __tablename__ = "experiment_config"
#
# config_id = Column(String(), comment="id",primary_key=True)
# ozone_disinf = Column(Integer, comment="臭氧消毒")
# uv_disinf = Column(Integer, comment="紫外线消毒")
# ozone_conc = Column(String(50), comment="臭氧浓度")
# disinf_time = Column(String(50), comment="消毒时间")
# temperature = Column(String(50), comment="温度")
# humidity = Column(String(50), comment="湿度")
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
# user_id = Column(String(50), comment="创建人")
# user_name = Column(String(50), comment="用户昵称")
#
# config_name = Column(String(50), comment="配置名称")
# config_desc = Column(String(500), comment="配置描述")
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
#
#
# # 试验表
# class EntityExperiment(Base):
# __tablename__ = "experiment"
#
# experiment_id = Column(String(50), comment="消毒流程id", primary_key=True)
# name = Column(String(50), comment="名称")
#
# ozone_disinf = Column(Integer, comment="臭氧消毒")
# uv_disinf = Column(Integer, comment="紫外线消毒")
# ozone_conc = Column(String(50), comment="臭氧浓度")
# disinf_time = Column(String(50), comment="消毒时间")
# temperature = Column(String(50), comment="浓度")
# humidity = Column(String(50), comment="湿度")
#
# remark = Column(String(500), comment="备注信息")
# created_time = Column(String(50), default=Utils.get_time(), comment="创建时间")
# update_time = Column(String(50), comment="更新时间")
#
# user_id = Column(String(50), comment="创建人")
# user_name = Column(String(50), comment="用户昵称")
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")
# state = Column(Integer, default=0, comment="状态")
#
# specimens = relationship('EntitySpecimen', secondary=specimen_experiment, back_populates='experiments')
#
# def to_dict(self):
# return dict(id=self.experiment_id, ozone_disinf=self.ozone_disinf, uv_disinf=self.uv_disinf,
# ozone_conc=self.ozone_conc, disinf_time=self.disinf_time, temperature=self.temperature,
# humidity=self.humidity, remark=self.remark, created_time=self.created_time, user_name=self.user_name)
#
#
# # 试验记录信息表
# class EntityExperimentRecord(Base):
# __tablename__ = "experiment_record"
#
# record_id = Column(String(50), comment="记录id", primary_key=True)
# update_time = Column(String(50), comment="试验记录更新时间")
# is_enabled = Column(Integer, default=0, comment="是否禁用或删除")

@ -0,0 +1,216 @@
import traceback
import paginate_sqlalchemy
from sqlalchemy import create_engine, desc
from app.conf.Setting import settings
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import func
from sqlalchemy.sql.elements import BinaryExpression, BooleanClauseList
from app.lib.Log import logger
class OrmBase(object):
# 初始化数据库连接
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI,connect_args={'check_same_thread': False}, pool_size=100)
# 创建session工厂
DBSession = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=True)
# 创建session对象
session = DBSession()
# 事务标识
transFlag = False
def __init__(self, entityType):
super().__init__()
# 对象类型
self.entityType = entityType
# 开始事务
def beginTrans(self):
self.session = self.DBSession()
self.transFlag = True
# 提交事务
def commitTrans(self):
try:
self.transFlag = False
self.session.commit()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 新增数据
def insert(self, entity):
try:
if (not self.transFlag):
self.session = self.DBSession()
self.session.add(entity)
if (not self.transFlag):
self.session.commit()
return True
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 新增多行数据
def insert_many(self, entity_list):
try:
if (not self.transFlag):
self.session = self.DBSession()
self.session.bulk_save_objects(entity_list)
if (not self.transFlag):
self.session.commit()
return True
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 更新数据
def update(self, entity):
try:
if (not self.transFlag):
self.session = self.DBSession()
self.session.merge(entity)
if (not self.transFlag):
self.session.commit()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 删除操作
def delete(self, where):
try:
if (not self.transFlag):
self.session = self.DBSession()
self.session.query(self.entityType).filter(where).delete()
if (not self.transFlag):
self.session.commit()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 查询单个实体
def findEntity(self, *where):
try:
self.session = self.DBSession()
if (type(*where) is BinaryExpression or type(*where) is BooleanClauseList):
return self.session.query(self.entityType).filter(*where).first()
else:
return self.session.query(self.entityType).get(where)
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
def findLastEntity(self, order_by_field, *where):
try:
self.session = self.DBSession()
if (type(*where) is BinaryExpression or type(*where) is BooleanClauseList):
return self.session.query(self.entityType).filter(*where).order_by(desc(order_by_field)).first()
else:
return self.session.query(self.entityType).order_by(desc(order_by_field)).get(where)
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 查询实体列表
def findList(self, *where):
try:
self.session = self.DBSession()
return self.session.query(self.entityType).filter(*where)
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 查询所有实体列表
def findAllList(self):
try:
self.session = self.DBSession()
return self.session.query(self.entityType).all()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 查询分页
def queryPage(self, orm_query, pageParam):
try:
page = paginate_sqlalchemy.SqlalchemyOrmPage(
orm_query, page=pageParam.curPage, items_per_page=pageParam.pageRows, db_session=self.DBSession)
pageParam.totalRecords = page.item_count
return page.items
except Exception as e:
logger.error(traceback.format_exc())
raise e
# 查询数量
def findCount(self, *where):
try:
self.session = self.DBSession()
return self.session.query(func.count('*')).select_from(self.entityType).filter(*where).scalar()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 查询最大数
def findMax(self, prop, *where):
try:
self.session = self.DBSession()
return self.session.query(func.max(prop)).select_from(self.entityType).filter(*where).scalar()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 执行Sql语句
def execute(self, sql, *agrs):
try:
self.session = self.DBSession()
return self.session.execute(sql, *agrs)
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()
# 执行Sql语句
def executeNoParam(self, sql):
try:
self.session = self.DBSession()
self.session.execute(sql)
if (not self.transFlag):
self.session.commit()
except Exception as e:
logger.error(traceback.format_exc())
self.session.rollback()
raise e
finally:
self.session.close()

@ -0,0 +1 @@
from .DateEntity import EntityUser,EntityRole

@ -0,0 +1,409 @@
import urllib.parse
import psutil
import os
import time
import random
import getpass
import platform
from io import BytesIO
from os import path
from app.conf.Setting import settings
import openpyxl
from openpyxl import Workbook
from openpyxl.styles import Alignment
from openpyxl.drawing.image import Image
from openpyxl.styles import Font
from datetime import datetime
import matplotlib.pyplot as plt
from app.lib.Utils import Utils
from openpyxl.styles import PatternFill, Border, Side
system_name = platform.system()
class ExcelExport():
def __init__(self, file_name, data, download_type):
self.file_name = file_name
self.data = data
self.download_type = download_type
self.book = Workbook()
self.side = Side(style='thin', color='000000') # 颜色可以是RGB值或者颜色名称
self.border = Border(left=self.side, right=self.side, top=self.side, bottom=self.side)
def set_title(self, sheet):
"""设置标题"""
for i in range(1, 13):
sheet.column_dimensions[openpyxl.utils.get_column_letter(i)].width = 15
sheet.merge_cells(f'A1:L1')
# sheet.merge_cells(f'A1:{chr(64 + self.culumn)}1')
cell = sheet.cell(row=1, column=1)
cell.value = self.file_name
cell.alignment = Alignment(horizontal='center', vertical='center')
sheet.row_dimensions[1].height = 40
# 设置背景色
fill = PatternFill(start_color="D9E1F4", end_color="D9E1F4", fill_type="solid")
sheet['A1'].fill = fill
# sheet['A1'].border = self.border
def set_column_title(self, sheet, experiment_type):
sheet.merge_cells(f'A2:A8')
cell = sheet.cell(row=2, column=1)
cell.value = '基本信息'
cell.alignment = Alignment(horizontal='center', vertical='center')
if experiment_type == '1':
sheet.merge_cells(f'A9:A12')
else:
sheet.merge_cells(f'A9:A13')
cell = sheet.cell(row=9, column=1)
cell.value = '报告信息'
cell.alignment = Alignment(horizontal='center', vertical='center')
fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid")
sheet['A2'].fill = fill
sheet['A9'].fill = fill
# sheet['A2'].border = self.border
# sheet['A9'].border = self.border
def set_columns(self, sheet, title_list):
"""设置列名"""
for i in range(len(title_list)):
sheet.merge_cells(f'B{i + 2}:E{i + 2}')
sheet.merge_cells(f'F{i + 2}:I{i + 2}')
cell = sheet.cell(row=i + 2, column=2)
cell.value = title_list[i]
cell.alignment = Alignment(horizontal='center', vertical='center')
def set_temp(self, sheet, data_item):
"""设置40,60,80,100"""
temp = ['校准物质40℃升温速率', '校准物质60℃升温速率', '校准物质80℃升温速率', '校准物质100℃升温速率']
temp1 = ['校准物质40℃降温速率', '校准物质60℃降温速率', '校准物质80℃降温速率', '校准物质100℃降温速率']
for i in range(len(temp)):
sheet.merge_cells(f'B{i + 9}:C{i + 9}')
sheet.merge_cells(f'D{i + 9}:E{i + 9}')
sheet.merge_cells(f'F{i + 9}:G{i + 9}')
sheet.merge_cells(f'H{i + 9}:I{i + 9}')
cell = sheet.cell(row=i + 9, column=2)
cell.value = temp[i]
cell.alignment = Alignment(horizontal='center', vertical='center')
cell = sheet.cell(row=i + 9, column=4)
cell.value = data_item["temp_data"][i]
cell.alignment = Alignment(horizontal='center', vertical='center')
cell1 = sheet.cell(row=i + 9, column=6)
cell1.value = temp1[i]
cell1.alignment = Alignment(horizontal='center', vertical='center')
cell = sheet.cell(row=i + 9, column=8)
cell.value = data_item["temp_data1"][i]
cell.alignment = Alignment(horizontal='center', vertical='center')
def insert_data(self, sheet, data_item, key_list):
"""插入数据"""
for i in range(len(key_list)):
cell = sheet.cell(row=i + 2, column=6)
cell.value = data_item[key_list[i]]
cell.alignment = Alignment(horizontal='center', vertical='center')
# def insert_chart(self, sheet,chart_result,ro):
# """插入图表"""
#
#
# plt.rcParams['xtick.direction'] = 'in' # 将x周的刻度线方向设置向内
# plt.rcParams['ytick.direction'] = 'in' # 将y轴的刻度方向设置向内
# ax1_color = '#1f77b4'
# ax2_color = '#d62728'
# ax3_color = '#573627'
#
# data = chart_result
#
# x_data = [int(float(i[-1])) for i in data] # 时间
# y_data = [round(float(i[2]), 2) for i in data] # 压力
# y_data2 = [round(float(i[3]), 2) for i in data] # 温度
# # y_data3 = [round(float(i[-5] or 0) + 250, 2) for i in data] # 热表面温度 + 250
# # 创建主轴的图表
# fig, ax1 = plt.subplots()
# ax1.plot(x_data, y_data, 'b-', label='["压力","时间"]', linewidth=1, color=ax1_color)
# ax2 = ax1.twinx()
# ax1.plot(x_data, y_data2, 'b-', label='["温度","时间"]', linewidth=1, color=ax2_color)
# # ax1.plot(x_data, y_data3, 'b-', label='["高热表面温度250","时间"]', linewidth=1, color=ax3_color)
# plt.title('试验数据')
#
# ax1.set_xlabel('时间')
# ax1.set_ylabel('压力')
# ax1.legend(loc='upper left')
# ax2.legend(loc='upper right')
#
#
# # 图表标题
# # 设置中文字体
# plt.rcParams['font.sans-serif'] = ['SimHei']
# # 设置负号字体
# plt.rcParams['axes.unicode_minus'] = False
# # 保存 Matplotlib 图表为图像文件
# chart_file_path = path.join(settings.BASE_PATH, f'resource/{int(time.time() * 1000)}.png')
# print(chart_file_path)
# plt.savefig(chart_file_path, dpi=400)
# # 将图像插入到 Excel 工作表中
# img = Image(chart_file_path)
# img.width = 960
# img.height = 400
# sheet.add_image(img, 'B' + str(ro+2))
def insert_chart(self, sheet,chart_result,ro,type):
"""插入图表"""
plt.rcParams['xtick.direction'] = 'in' # 将x周的刻度线方向设置向内
plt.rcParams['ytick.direction'] = 'in' # 将y轴的刻度方向设置向内
# ax1_color = '#1f77b4'
# ax2_color = '#d62728'
# ax3_color = '#573627'
data = chart_result
x_data = [int(float(i.time)) for i in data] # 时间
if type == 1:
y_data = [round(float(i.surface_temperature), 2) for i in data] # 温度1 表面温度
ax_color = '#1f77b4'
label='["表面温度","时间"]'
ylabel = '表面温度'
elif type == 2:
y_data = [round(float(i.battery_voltage), 2) for i in data] # 温度2 电池电压
ax_color = '#d62728'
label='["电池电压","时间"]'
ylabel = '电池电压'
else:
y_data = [round(float(i.battery_electricity), 2) for i in data] # 温度3 电池电流
ax_color = '#573627'
label='["电池电流","时间"]'
ylabel = '电池电流'
# y_data = [round(float(i.surface_temperature), 2) for i in data] # 温度1 表面温度
# y_data2 = [round(float(i.battery_voltage), 2) for i in data] # 温度2 电池电压
# y_data3 = [round(float(i.battery_electricity), 2) for i in data] # 温度3 电池电流
# 创建主轴的图表
fig, ax1 = plt.subplots()
ax1.plot(x_data, y_data, 'b-', label=label, linewidth=1, color=ax_color)
# ax1.plot(x_data, y_data2, 'b-', label='["电池电压","时间"]', linewidth=1, color=ax2_color)
# ax1.plot(x_data, y_data3, 'b-', label='["电池电流","时间"]', linewidth=1, color=ax3_color)
plt.title('试验数据')
ax1.set_xlabel('时间')
ax1.set_ylabel(ylabel)
ax1.legend(loc='upper left')
# 图表标题
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 设置负号字体
plt.rcParams['axes.unicode_minus'] = False
# 保存 Matplotlib 图表为图像文件
chart_file_path = path.join(settings.BASE_PATH, f'resource/{int(time.time() * 1000)}.png')
print(chart_file_path)
plt.savefig(chart_file_path, dpi=400)
# 将图像插入到 Excel 工作表中
img = Image(chart_file_path)
img.width = 960
img.height = 400
sheet.add_image(img, 'E' + str(ro+2))
ro+=20
# def insert_chart(self, sheet,chart_result,ro):
#
#
#
#
# # 设置全局字体和刻度线方向
# plt.rcParams['xtick.direction'] = 'in' # x轴刻度线方向
# plt.rcParams['ytick.direction'] = 'in' # y轴刻度线方向
# plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
# plt.rcParams['axes.unicode_minus'] = False # 负号显示
#
# # 假设这是你获取的实验数据
# data = chart_result
# x_data = [int(float(i.time)) for i in data] # 时间
# y_data = [round(float(i.surface_temperature), 2) for i in data] # 表面温度
# y_data2 = [round(float(i.battery_voltage), 2) for i in data] # 电池电压
# y_data3 = [round(float(i.battery_electricity), 2) for i in data] # 电池电流
#
# # 创建图表并设置主轴
# fig, ax1 = plt.subplots()
#
# # 绘制第一个数据系列:表面温度
# ax1.set_xlabel('时间')
# ax1.set_ylabel('表面温度 (°C)', color='#1f77b4')
# ax1.plot(x_data, y_data, label='表面温度', color='#1f77b4', linewidth=1)
# ax1.tick_params(axis='y', labelcolor='#1f77b4')
#
# # 设置第一个 y 轴的范围(根据数据调整)
# ax1.set_ylim([min(y_data) - 1, max(y_data) + 1]) # 适当调整范围,避免线条挤压
#
# # 创建第二个 y 轴:电池电压
# ax2 = ax1.twinx() # 创建第二个 y 轴
# ax2.set_ylabel('电池电压 (V)', color='#d62728')
# ax2.plot(x_data, y_data2, label='电池电压', color='#d62728', linewidth=1)
# ax2.tick_params(axis='y', labelcolor='#d62728')
#
# # 设置第二个 y 轴的范围(根据数据调整)
# ax2.set_ylim([min(y_data2) - 0.5, max(y_data2) + 0.5]) # 适当调整范围
#
# # 将第二个 y 轴移到左侧
# ax2.spines['left'].set_position(('outward', 60)) # 将第二个 y 轴移到外面
#
# # 创建第三个 y 轴:电池电流
# ax3 = ax1.twinx() # 创建第三个 y 轴
# ax3.spines['left'].set_position(('outward', 320)) # 将第三个 y 轴移到外面
# ax3.set_ylabel('电池电流 (A)', color='#573627')
# ax3.plot(x_data, y_data3, label='电池电流', color='#573627', linewidth=1)
# ax3.tick_params(axis='y', labelcolor='#573627')
#
# # 设置第三个 y 轴的范围(根据数据调整)
# ax3.set_ylim([min(y_data3) - 0.1, max(y_data3) + 0.1]) # 适当调整范围
#
# # 图表标题
# plt.title('试验数据')
#
# # 图例
# fig.tight_layout() # 自动调整子图间距
# ax1.legend(loc='upper left')
#
# # 保存图表为图片
# chart_file_path = path.join(settings.BASE_PATH, f'resource/{int(time.time() * 1000)}.png')
# print(chart_file_path)
# plt.savefig(chart_file_path, dpi=400)
#
# # 将图像插入到 Excel 工作表中
# img = Image(chart_file_path)
# img.width = 960
# img.height = 400
# sheet.add_image(img, 'D' + str(ro + 2))
def experiment_table(self):
"""试验报表"""
for item in self.data:
base_data_title = item['base_data_title']
base_data_list = item['base_data_list']
sheet_title = item['sheet_title']
title_list = item['title_list']
specimen_data = item['specimen_data']
sheet = self.book.create_sheet(str(sheet_title)) # 创建工作表用试验时间作工作表名称
self.set_title(sheet) # 设置标题
for i in range(len(title_list)):
sheet.merge_cells(f'A{i + 1}:L{i + 1}')
cell = sheet.cell(row=i+1, column=1)
cell.value = title_list[i]
cell.alignment = Alignment(horizontal='center', vertical='center')
if i == 0:
cell.font = Font(size=22, bold=True)
ro = 2
for key, value in specimen_data.items():
cell = sheet.cell(row=ro, column=1)
cell.value = key
# sheet.merge_cells(f'B{ro + 1}:E{ro + 1}')
cell_b = sheet.cell(row=ro, column=2)
cell_b.value = value
ro += 1
ro +=1
for index,value in enumerate(base_data_title):
if value:
cell = sheet.cell(row=ro, column=index+1)
cell.value = value
cell.alignment = Alignment(horizontal='center', vertical='center')
ro += 1
for i in base_data_list:
for index, value in enumerate(base_data_title):
cell = sheet.cell(row=ro, column=index + 1)
cell.value = i[base_data_title[index]]
cell.alignment = Alignment(horizontal='center', vertical='center')
ro += 1
# 在数据行下方增加一行,并填充新的列标题('实验时间', '表面温度', '电池电压', '电池电流'
new_row_title = ['实验时间', '表面温度', '电池电压', '电池电流']
# 填充新的一行数据标题,紧接数据区域下方
for index, value in enumerate(new_row_title):
cell = sheet.cell(row=ro+4, column=index + 1)
cell.value = value
cell.alignment = Alignment(horizontal='center', vertical='center') # 可选,居中对齐标题
fill_ro = ro+5
# 填充额外数据('实验时间', '表面温度', '电池电压', '电池电流'
for i in base_data_list[0]['other_data']:
cell_css= Alignment(horizontal='center', vertical='center') # 可选,居中对齐数据
time_cell = sheet.cell(row=fill_ro, column= 1)
time_cell.value = i.time # 时间
time_cell.alignment = cell_css
hxwd_cell = sheet.cell(row=fill_ro, column= 2)
hxwd_cell.value = i.surface_temperature # 表面温度
hxwd_cell.alignment = cell_css
ypwd_cell = sheet.cell(row=fill_ro, column= 3)
ypwd_cell.value = i.battery_voltage # 电池电压
ypwd_cell.alignment = cell_css
dcdl_cell = sheet.cell(row=fill_ro, column= 4)
dcdl_cell.value = i.battery_electricity # 电池电流
dcdl_cell.alignment = cell_css
fill_ro += 1 # 每填充一行数据后,增加行号
for i in base_data_list:
result_row_data = i['other_data']
if result_row_data:
char_data =[]
for row in result_row_data:
# if row[0] in [4,'4']:
char_data.append(row)
# 循环三次、表面温度、电池电压、电池电流
type = 1
for i in range(3):
self.insert_chart(sheet, char_data, ro,type)
type += 1
ro += 20
# 获取所有工作表的列表
sheets = self.book.sheetnames
# 删除第一个工作表
first_sheet_name = sheets[0]
self.book.remove(self.book[first_sheet_name])
# 导出
return self.export()
def export(self):
"""
导出
"""
binary = BytesIO()
self.book.save(binary)
self.book.close()
binary.seek(0)
filename = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{self.file_name}-报表.xlsx"
if self.download_type == 'usb':
try:
self.put_in(filename, binary)
return 200, "导出成功"
except Exception as e:
print(e)
return 404, "U盘未找到"
encoded_filename = urllib.parse.quote(filename)
return binary, encoded_filename
def put_in(self, filename: str, bin: BytesIO):
if system_name == 'Windows':
usbs = [disk.device for disk in psutil.disk_partitions() if disk.opts == 'rw,removable']
if not usbs:
raise Exception()
# with open(f"D:\\{filename}", 'wb') as f:
# f.write(bin.getbuffer())
# return
path_file = path.join(usbs[0], filename)
else:
usbs = [item for item in psutil.disk_partitions() if item.mountpoint.startswith(f'/media/{getpass.getuser()}')]
if not usbs:
raise Exception()
path_file = path.join(usbs[0].mountpoint, filename)
with open(path_file, 'wb') as f:
f.write(bin.getbuffer())

@ -0,0 +1,123 @@
from pydantic import BaseModel
#用户注册,编辑
class SignUpdateModel(BaseModel):
username: str
name: str
role_id: str
password: str
sex: int
#用户登录
class SignInModel(BaseModel):
username: str
password: str
#用户分页列表
class UserModel(BaseModel):
page_no: int = 1
page_size: int = 5
#下位机发送指令
class SendMesModel(BaseModel):
name: str
status:int = 0
#新增试验
class InsertModel(BaseModel):
items: dict
class ContinueModel(BaseModel):
id: str
step: str
class UpdateModel(BaseModel):
id: str
items: dict
#停止试验
class StopModel(BaseModel):
id: str
test_result: int = 0
class SpecimenSearchModel(BaseModel):
page_no: int = 1
page_size: int = 5
specimen_name: str = None
specimen_code: str = None
specimen_id: str = None
rfid: str = None
class WaitSpecimenSearchModel(BaseModel):
page_no: int = 1
page_size: int = 5
record_id: str = None
specimen_name: str = None
specimen_code: str = None
specimen_id: str = None
rfid: str = None
#报表查询
class SearchWordModel(BaseModel):
page_no: int = 1
page_size: int = 5
name: str = None
experiment_id: str = None
record_id: str = None
date: str = None
class CombustionLimitModel(BaseModel):
specimen_id: str
ct_value: str
combustion_type: str
#报表导出
class ExportTableModel(BaseModel):
specimen_id: str
download_type: str
# 获取实验数据
class DataModel(BaseModel):
id: str
class SpecimenModel(BaseModel):
items: dict
class SpecimenConfigModel(BaseModel):
items: dict
class InStorageModel(BaseModel):
specimen_ids: list
storage_type: int
class DeviceConfigModel(BaseModel):
light_status: int # 0-关闭 1-开启
fan_status: int # 0-关闭 1-开启
warning_light_status: int = 0# 0-关闭 1-开启
lock_status: int # 0-关闭 1-开启
class ExperimentParamModel(BaseModel):
temperature: float # 设置温度
expose_time: float # 暴露时间
put_time: float # 放置时间
short_circuit_param: float = 0.0 # 短路参数
specimen_name: str # 样品名称
battery_type: str # 电池型号
specimen_num: str # 样品编号
battery_specification: str # 电池规格
battery_manufacturers: str # 电池厂家
channel_ids: str # 通道ids 逗号分隔
experiment_specimen_id: str = '' # 材料id
channel_experiment_dict : dict = {} # 通道实验参数

@ -0,0 +1,50 @@
import os
from app.api.ShortCircuit import app_start
from app.conf.Setting import settings
import uvicorn
from fastapi import Request
from app.api import create_app
from app.lib.Utils import Utils
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from app.models import DateEntity
from app.models.Repository import OrmBase
from fastapi.middleware.cors import CORSMiddleware
app = create_app()
DateEntity.Base.metadata.create_all(bind=OrmBase.engine)#创建表结构
app.add_middleware(
CORSMiddleware,
# allow_origins=["*"], # 允许所有源,也可以指定具体源
allow_origins=["http://127.0.0.1:8000"], # 前端的源地址
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有头
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=200,
content=jsonable_encoder(Utils.false_return(desc='参数错误')),
)
@app.middleware("http")
async def before_request(request, call_next):
response = await call_next(request)
return response
# @app.on_event("startup")
# async def startup():
# # pass
# await app_start()
if __name__ == '__main__':
uvicorn.run(app="main:app",host=settings.SERVER_HOST,port=settings.SERVER_POST,reload=settings.RELOAD)

@ -0,0 +1,32 @@
aiomysql==0.2.0
annotated-types==0.6.0
anyio==4.3.0
click==8.1.7
colorama==0.4.6
exceptiongroup==1.2.0
fastapi==0.110.1
greenlet==3.0.3
h11==0.14.0
idna==3.6
logzero==1.7.0
modbus-tk==1.1.3
paginate==0.5.6
paginate-sqlalchemy==0.3.1
psutil==5.9.8
pydantic==1.9.1
pydantic_core==2.16.3
PyMySQL==1.1.0
pyserial==3.5
sniffio==1.3.1
SQLAlchemy==2.0.29
starlette==0.37.2
typing_extensions==4.11.0
uvicorn==0.29.0
websockets==12.0
openpyxl==3.1.2
matplotlib==3.7.5
scipy==1.10.1
opencv-python~=4.10.0.84
numpy~=1.24.4
pillow~=10.4.0
APScheduler~=3.10.4

@ -0,0 +1,7 @@
@echo off
start /min cmd /k "conda activate py10 && D:&&cd DG16-D\&& python main.py"
start "" "C:\Program Files\Google\Chrome\Application\chrome" --start-fullscreen http://127.0.0.1:8088"
D:&&cd dist\ && python -m http.server 8088"
exit

@ -0,0 +1,4 @@
@echo off
::start python D:\DG21-E\main.py
start C:\Users\Administrator\AppData\Local\Google\Chrome\Application\chrome --start-fullscreen http://127.0.0.1
exit

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save