994 lines
41 KiB
Python
994 lines
41 KiB
Python
import argparse
|
||
import asyncio
|
||
import signal
|
||
import time
|
||
import keyboard
|
||
import requests
|
||
|
||
import sqlite3, datetime
|
||
import eel
|
||
import threading, os, sys, platform
|
||
import serial
|
||
import logging
|
||
# from flask_socketio import SocketIO, emit # 改为sockets 来实现websocket
|
||
from flask import Flask, request, jsonify, g, Response, send_from_directory, json
|
||
from flask_cors import CORS, cross_origin
|
||
import websockets
|
||
|
||
# sudo systemctl restart nginx #centos 下重启nginx
|
||
# gunicorn -b 0.0.0.0:5050 -w 3 -error-log ./dtmgt.log dtmgtApp:app
|
||
# gunicorn -b 0.0.0.0:5050 -w 3 --error-log ./dtmgt.log --preload dtmgtApp:app
|
||
# 在linux 下不使用gunicorn启动此程序,nohup python(39) dtmgtApp.py --modbusServer=xxxxx --modbusServerPort=xxxxx
|
||
# 本地运行 python dtmgtApp.py --startModusServer
|
||
# pyinstaller --add-data "src;dst" xxxx.py
|
||
# 打包成1个可执行文件 exe
|
||
# pyinstaller --onefile --add-data "web;web" dtmgtApp.py
|
||
|
||
from pymodbus.client import ModbusTcpClient
|
||
|
||
# import plc_comm as dtm_machine
|
||
import dtMachineService as machineService
|
||
from mhi_plc import ModbusPlcServer
|
||
|
||
app = Flask(__name__, static_folder='web')
|
||
app.config['SECRET_KEY'] = 'secret!'
|
||
|
||
# socketio = SocketIO()
|
||
# socketio.init_app(app, async_mode='threading1', cors_allowed_origins="*")
|
||
|
||
initialized = False
|
||
dbServer_aloned = False
|
||
dtm_machines = {}
|
||
dtMachineService = None
|
||
|
||
webview_thread = None
|
||
modbus_server_obj = None
|
||
http_server = None
|
||
webSocket_server = None
|
||
flask_server_thread = None
|
||
keyboard_input_thread = None
|
||
main_exit_flag = False
|
||
flask_stop_event = None
|
||
socketio_thread = None
|
||
# 关闭所有的日志记录
|
||
app.logger.disabled = True
|
||
|
||
# 或者将日志等级调整为 ERROR 以仅记录错误
|
||
# app.logger.setLevel(logging.ERROR)
|
||
|
||
# 设置 Werkzeug 日志记录器(Flask 默认的开发服务器)等级为 ERROR
|
||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
||
|
||
CORS(app) # 这会让所有的资源都支持跨域访问
|
||
CORS(app, resources=r"/*")
|
||
|
||
# 连接SQLite数据库
|
||
try:
|
||
dbConn = sqlite3.connect('db\dtmgtDb.db')
|
||
dbCursor = dbConn.cursor()
|
||
|
||
# 创建表
|
||
dbCursor.execute('''CREATE TABLE IF NOT EXISTS TEST
|
||
(ID INT PRIMARY KEY NOT NULL,
|
||
NAME TEXT NOT NULL,
|
||
RESULT TEXT NOT NULL);''')
|
||
|
||
except sqlite3.Error as e:
|
||
print(f"Database error: {e}")
|
||
# 或者你可以选择抛出异常,让程序停止运行
|
||
# raise e
|
||
# socket IO
|
||
name_space = "/ws"
|
||
|
||
"""
|
||
@socketio.on('connect', namespace=name_space) # 处理前端定时或者主动来查询试验机器数据和状态的请求
|
||
@cross_origin()
|
||
def ws_connect(auth ={}):
|
||
print('socket client connected', auth)
|
||
return None
|
||
|
||
@socketio.on('disconnect', namespace=name_space) # 处理前端定时或者主动来查询试验机器数据和状态的请求
|
||
@cross_origin()
|
||
def ws_disconnect():
|
||
print('socket client disconnected')
|
||
"""
|
||
|
||
|
||
async def websocket_server(ws, path):
|
||
async for message in ws:
|
||
# print("ws", message)
|
||
try:
|
||
msg_json = json.loads(message)
|
||
if msg_json:
|
||
# print("ws",msg_json)
|
||
if "command" in msg_json and msg_json["command"] == "get_machines_data" and \
|
||
"data" in msg_json:
|
||
result = handle_get_machines_value(msg_json['data'])
|
||
result['command'] = 'get_machines_data'
|
||
# 将字典转换为 JSON 字符串
|
||
await ws.send(json.dumps(result))
|
||
except Exception as e:
|
||
pass
|
||
# print(f'Unexpected error: {e}')
|
||
# await websocket.send(json.dumps({"message": "Received your message: " + data['message']}))
|
||
|
||
|
||
# socket IO
|
||
# @socketio.on('get_machines_data', namespace=name_space) # 处理前端定时或者主动来查询试验机器数据和状态的请求
|
||
def handle_get_machines_value(param):
|
||
# print('socket io received message: ' + param)
|
||
try:
|
||
param = json.loads(param)
|
||
except:
|
||
pass
|
||
if 'machines' in param:
|
||
if dtMachineService:
|
||
result = dtMachineService.get_machines_value(param['machines'])
|
||
# emit('get_machines_data', {"status": "success", "data": result})
|
||
return {"status": "success", "data": result}
|
||
else:
|
||
return {"status": "error", "msg": "machine service is not available"}
|
||
# emit('get_machines_data', {"status": "error", "msg": "machine service is not available"})
|
||
else:
|
||
# emit('get_machines_data', {"status": "error", "msg": "machines in param is invalid"})
|
||
return {"status": "error", "msg": "machines in param is invalid"}
|
||
|
||
|
||
@app.before_request
|
||
def before_request():
|
||
global dtMachineService, initialized
|
||
db_path = os.path.join('db', 'dtmgtDb.db')
|
||
g.db = sqlite3.connect(db_path)
|
||
if not initialized:
|
||
print("Initializing dtMachineService")
|
||
dtMachineService = machineService.DtmMachineService('127.0.0.1', 5020, access_table_data, app ,station_data_changed)
|
||
dtMachineService.dtm_virt_machine_client_connect()
|
||
initialized = True
|
||
|
||
# print("open database", g.db)
|
||
|
||
|
||
"""
|
||
@app.route('/<path:path>')
|
||
@cross_origin()
|
||
def send_file(path):
|
||
print("access ",path)
|
||
if path != "" and path.endswith(".html"):
|
||
return send_from_directory(app.static_folder, path)
|
||
else:
|
||
return send_from_directory(app.static_folder, path)
|
||
"""
|
||
|
||
|
||
@app.route('/login', methods=['POST'])
|
||
@cross_origin()
|
||
def login_validate():
|
||
json_return = {"status": "error"}
|
||
params = request.json
|
||
db_path = os.path.join('db', 'dtmgtDb.db')
|
||
print("/login", params) # cyx
|
||
if params.get('username') is None or params.get('password') is None:
|
||
return jsonify({"status": "error", "error": "username or password is missing"}), 400
|
||
if g is None:
|
||
db = sqlite3.connect(db_path)
|
||
elif hasattr(g, 'db') == False:
|
||
db = sqlite3.connect(db_path)
|
||
else:
|
||
db = g.db
|
||
sql_str = f"SELECT * FROM user WHERE id > 0 and username =? and password = ?"
|
||
sql_params = [params.get('username'), params.get('password')]
|
||
try:
|
||
cursor = db.cursor()
|
||
cursor.execute(sql_str, sql_params)
|
||
rows = cursor.fetchall()
|
||
column_names = [description[0] for description in cursor.description]
|
||
result = []
|
||
for row in rows:
|
||
dict_row = {col: row[idx] if row[idx] is not None else None for idx, col in enumerate(column_names)}
|
||
result.append(dict_row)
|
||
# print("login result",result)#cyx
|
||
if len(result) == 1:
|
||
json_return = {"status": "success", "username": result[0].get('username'),
|
||
"userid": result[0].get('userid'),
|
||
"role": result[0].get('role'), "auth": result[0].get('auth')}
|
||
except sqlite3.Error as e:
|
||
print("get user info error", e) # cyx
|
||
return jsonify(json_return)
|
||
|
||
|
||
@app.route('/modbusServerStarted', methods=['POST'])
|
||
@cross_origin()
|
||
def modbus_server_started():
|
||
json_return = {"status": "success"}
|
||
params = request.json
|
||
print("/modbusServerStarted", params) # cyx
|
||
dtMachineService.dtm_virt_machine_client_connect()
|
||
return jsonify(json_return)
|
||
|
||
|
||
# 设置跌落机的试验参数
|
||
@app.route('/machine_control', methods=['POST'])
|
||
@cross_origin()
|
||
def machine_control():
|
||
global dtm_machines, dtMachineService
|
||
params = request.json
|
||
json_return = {"status": "error"}
|
||
actionParam = {}
|
||
# print("dtmgtApp /machine_control", params) # cyx
|
||
"""
|
||
let control_reqs={
|
||
start:{machine:'HYSZ-DTM-002',com:'com2',station:'01'},
|
||
stop:{machine:'HYSZ-DTM-002',com:'com2',station:'01'},
|
||
setParams: {
|
||
machine: 'HYSZ-DTM-002', com: 'com2', station: '01', dropHeight: 67, dropCycles: 89,
|
||
},
|
||
getParams:{machine: 'HYSZ-DTM-002', com: 'com2', station: '01'}
|
||
}
|
||
"""
|
||
"""
|
||
dtm_machine1.read_station_dropheight('01')
|
||
dtm_machine1.set_station_dropheight('02',531)
|
||
dtm_machine1.start_station('01')
|
||
"""
|
||
# 检测键是否存在
|
||
if 'setParams' in params: # 设置工作站(台)测试执行参数
|
||
value_return = {"status": "error", "msg": "setParams error"}
|
||
setParams = params['setParams']
|
||
actionParam = setParams
|
||
print("machine_control setParams", setParams) # cyx
|
||
# 使用 set 检测多个键是否存在
|
||
for setParam in setParams:
|
||
required_keys = {'machine', 'com', 'station'}
|
||
if required_keys.issubset(setParam.keys()):
|
||
if 'dropHeight' in setParam:
|
||
try:
|
||
dtMachineSN = setParam['machine']
|
||
result = dtMachineService.set_station_dropheight(dtMachineSN, setParam['station'],
|
||
int(setParam['dropHeight']))
|
||
except Exception as error:
|
||
value_return = {'status': "error", "message": "set station dropHeght ", "error": error}
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "set station dropHeight success"}
|
||
pass
|
||
if 'dropCycles' in setParam:
|
||
try:
|
||
dtMachineSN = setParam['machine']
|
||
result = dtMachineService.set_station_cycles(dtMachineSN, setParam['station'],
|
||
setParam['dropCycles'])
|
||
print("machine_control setParams set cycles return ", result) # cyx
|
||
except Exception as error:
|
||
value_return = {'status': "error", "message": "set station dropCycles ", "error": error}
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "set station dropCycles success"}
|
||
if 'cyclesFinished' in setParam:
|
||
try:
|
||
dtMachineSN = setParam['machine']
|
||
result = dtMachineService.set_station_finished(dtMachineSN, setParam['station'],
|
||
setParam['cyclesFinished'])
|
||
except Exception as error:
|
||
value_return = {'status': "error", "message": "set station cyclesFinished ", "error": error}
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "set station cyclesFinished success"}
|
||
pass
|
||
return value_return
|
||
|
||
if 'start' in params or 'stop' in params or 'resume' in params or 'pause' in params or 'cancel' in params:
|
||
# 启动 停止 继续 取消 暂停 工作站(台)
|
||
value_return = {'status': "error", 'message': "station start stop resume error"}
|
||
param = None
|
||
if 'start' in params:
|
||
param = params['start']
|
||
if 'stop' in params:
|
||
param = params['stop']
|
||
if 'cancel' in params:
|
||
param = params['cancel']
|
||
if 'resume' in params:
|
||
param = params['resume']
|
||
if 'pause' in params:
|
||
param = params['pause']
|
||
actionParam = param
|
||
# 使用 set 检测多个键是否存在
|
||
required_keys = {'machine', 'station'}
|
||
if required_keys.issubset(param.keys()):
|
||
# 将前端发送的 start resume stop cancel pause 等命令统一按照dtStationAttachDut 处理,以便
|
||
# 统一处理工位 机台 试验样品 试验要求的状态
|
||
if 'start' in params:
|
||
return dtMachineService.dtStationAttachDut(param['machine'], param['station'], 'start', None, True)
|
||
|
||
if 'resume' in params:
|
||
return dtMachineService.dtStationAttachDut(param['machine'], param['station'], 'resume', None, True)
|
||
|
||
if 'stop' in params:
|
||
return dtMachineService.dtStationDetachDut(param['machine'], param['station'], 'stop')
|
||
|
||
if 'cancel' in params:
|
||
return dtMachineService.dtStationDetachDut(param['machine'], param['station'], 'cancel')
|
||
|
||
if 'pause' in params:
|
||
return dtMachineService.dtStationAttachDut(param['machine'], param['station'], 'pause', None, True)
|
||
else:
|
||
# print("键 'machine', 'com', 'station', 'params' 不完全存在于 JSON 中")
|
||
pass
|
||
return jsonify(value_return)
|
||
|
||
if 'getParams' in params:
|
||
value_return = {'status': "error", 'message': "get station params error"}
|
||
param = params['getParams']
|
||
actionParam = param
|
||
# 使用 set 检测多个键是否存在q
|
||
required_keys = {'machine', 'com', 'station'}
|
||
if required_keys.issubset(param.keys()):
|
||
if 'all' not in param and 'machine' in param:
|
||
try:
|
||
dtMachineSN = param['machine']
|
||
result = dtMachineService.read_station_cyclesFinished(dtMachineSN, param['station'])
|
||
if 'status' in result and result['status'] == 'success' and 'value' in result:
|
||
value_return["status"] = "success"
|
||
value_return["action"] = "getParams"
|
||
value_return["finished"] = result['value']
|
||
value_return["message"] = "get station params success"
|
||
except Exception as error:
|
||
value_return["error"] = error
|
||
pass
|
||
return jsonify(value_return)
|
||
|
||
if 'getStationValue' in params: # 获取机器工作站(台)的状态与数据
|
||
value_return = {'status': "error", 'message': "get station params error"}
|
||
param = params['getStationValue']
|
||
# 使用 set 检测多个键是否存在q
|
||
required_keys = {'machine', 'station'}
|
||
if 'station' in param and 'machine' in param:
|
||
if dtMachineService:
|
||
result = dtMachineService.get_station_value(param['machine'], param['station'])
|
||
return jsonify(result)
|
||
else:
|
||
return jsonify({"status": "error", "msg": "machine service is not available"})
|
||
# print("键 'machine', 'com', 'station', 'params' 不完全存在于 JSON 中")
|
||
pass
|
||
|
||
if 'getMachinesValue' in params: # 获取机器的状态与数据
|
||
value_return = {'status': "error", 'message': "get station params error"}
|
||
param = params['getMachinesValue']
|
||
# print("/machine_control getMachinesValue", dtMachineService)
|
||
if 'machines' in param:
|
||
if dtMachineService:
|
||
result = dtMachineService.get_machines_value(param['machines'])
|
||
return jsonify(result)
|
||
else:
|
||
return jsonify({"status": "error", "msg": "machine service is not available"})
|
||
# print("键 'machine', 'com', 'station', 'params' 不完全存在于 JSON 中")
|
||
pass
|
||
|
||
if 'attachDut' in params: # 将测试样品与机器的工作站(台)关联
|
||
value_return = {'status': "error", 'message': "station attach dut error"}
|
||
param = params['attachDut']
|
||
if 'machine' in param and 'station' in param and 'dut' in param and 'action' in param:
|
||
station_sn = param['station']
|
||
machine_sn = param['machine']
|
||
dut_sn = param['dut']
|
||
action = param['action']
|
||
updateTableFlag = param['updateTableFlag']
|
||
userid = param.get('userid', None) # 安排试验的用户id
|
||
result = dtMachineService.dtStationAttachDut(machine_sn, station_sn, action, dut_sn, updateTableFlag,
|
||
userid)
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "station attach dut success"}
|
||
if 'station' in result:
|
||
value_return['station'] = result['station']
|
||
return jsonify(value_return)
|
||
|
||
if 'detachDut' in params: # 测试样品与机器的工作站(台)断开关联
|
||
value_return = {'status': "error", 'message': "station detach dut error"}
|
||
param = params['detachDut']
|
||
if 'machine' in param and 'station' in param and 'action' in param:
|
||
station_sn = param['station']
|
||
machine_sn = param['machine']
|
||
action = param['action']
|
||
result = dtMachineService.dtStationDetachDut(machine_sn, station_sn, action)
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "station detach dut success"}
|
||
if 'station' in result:
|
||
value_return['station'] = result['station']
|
||
return jsonify(value_return)
|
||
|
||
if 'assignTestReq' in params: # 设置测试要求
|
||
value_return = {'status': "error", 'message': "station assign testReq error"}
|
||
param = params['assignTestReq']
|
||
if 'machine' in param and 'station' in param and 'dut' in param and 'testReqIndex' in param:
|
||
station_sn = param['station']
|
||
machine_sn = param['machine']
|
||
dut_sn = param['dut']
|
||
testReqIndex = param['testReqIndex'] # 测试样件的测试项目index
|
||
result = dtMachineService.dtStationAssignTestReq(machine_sn, station_sn, dut_sn, testReqIndex)
|
||
if 'status' in result and result['status'] == 'success':
|
||
value_return = {'status': "success", 'message': "station assign testReq success"}
|
||
if 'station' in result:
|
||
value_return['station'] = result['station']
|
||
return value_return
|
||
if 'registerTest' in params: # PLC 寄存器测试
|
||
value_return = {'status': "error", 'message': "register read or write test error"}
|
||
param = params['registerTest']
|
||
# 使用 set 检测多个键是否存在q
|
||
required_keys = {'machine', 'station'}
|
||
if 'machine' in param and 'action' in param and 'address' in param:
|
||
if dtMachineService:
|
||
result = dtMachineService.register_test(param['machine'], param['action'], param['address'],
|
||
param.get('value', None))
|
||
return jsonify(result)
|
||
else:
|
||
return jsonify({"status": "error", "msg": "machine service is not available"})
|
||
# print("键 'machine', 'com', 'station', 'params' 不完全存在于 JSON 中")
|
||
pass
|
||
return json_return
|
||
|
||
|
||
# 设置跌落机的试验参数
|
||
@app.route('/get_machine_config', methods=['GET'])
|
||
@cross_origin()
|
||
def get_machine_config():
|
||
global dtm_machines, dtMachineService
|
||
json_return = {"status": "error"}
|
||
print("get machine_config")
|
||
if dtMachineService:
|
||
json_return = {"status": "success", "data": dtMachineService.get_machine_com_config()}
|
||
return jsonify(json_return)
|
||
|
||
|
||
"""
|
||
CREATE TABLE DUTList (
|
||
SN TEXT (20) UNIQUE,
|
||
name TEXT (20),
|
||
project TEXT (20),
|
||
phase TEXT (16),
|
||
stationAssigned TEXT (18),
|
||
itemOnGoing TEXT (10),
|
||
itemsFinished NUMERIC (5),
|
||
status TEXT (10),
|
||
testReq TEXT (10),
|
||
inspector TEXT (20),
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT
|
||
);
|
||
"""
|
||
import sqlite3
|
||
from flask import jsonify, request
|
||
|
||
|
||
@app.route('/dbTableAccess', methods=['GET', 'POST'])
|
||
@cross_origin()
|
||
def access_table_data():
|
||
global date_span_str
|
||
table = request.args.get('table')
|
||
pageSize = request.args.get('pageSize', default=None, type=int)
|
||
currentPage = request.args.get('currentPage', default=None, type=int)
|
||
# print(f"/dbTableAccess pageSize={pageSize} currentPage={currentPage}")
|
||
db_path = os.path.join('db', 'dtmgtDb.db')
|
||
if table is None:
|
||
return jsonify({"error": "Table parameter is missing"}), 400
|
||
if g is None:
|
||
db = sqlite3.connect(db_path)
|
||
elif hasattr(g, 'db') == False:
|
||
db = sqlite3.connect(db_path)
|
||
else:
|
||
db = g.db
|
||
if request.method == 'GET':
|
||
sql_rows_str = f"SELECT * FROM {table} WHERE id > 0"
|
||
sql_condition_str = ""
|
||
sql_params = []
|
||
parameters_to_check = []
|
||
if table == 'dutList':
|
||
parameters_to_check = ['name', 'SN', 'description', 'status', 'stationAssigned']
|
||
if table == 'testReq':
|
||
parameters_to_check = ['SN', 'dropItem', 'dropHeight', 'dropCycles', 'description', 'status']
|
||
if table == 'project':
|
||
parameters_to_check = ['name', 'key', 'manager', 'type', 'description']
|
||
if table == 'projectPhase':
|
||
parameters_to_check = ['name', 'PN', 'description']
|
||
if table == 'dtMachine':
|
||
parameters_to_check = ['label', 'SN', 'status', 'type', 'color', 'description', 'plc_address']
|
||
if table == 'testReqResult':
|
||
parameters_to_check = ['SN', 'id', 'dropItem', 'dropHeight', 'dropCycles', 'description', 'status',
|
||
'userid']
|
||
|
||
def get_parameter(param):
|
||
param_sql = ""
|
||
param_value = request.args.getlist(param)
|
||
if param == "SN" and table == "testReqResult":
|
||
param = "testReq.SN"
|
||
if param == "id" and table == "testReqResult":
|
||
param = "testReq.id"
|
||
if param_value and param != 'stationAssigned':
|
||
param_sql = f" AND {param} IN ({','.join(['?'] * len(param_value))})"
|
||
else:
|
||
pass
|
||
if param_value and param == 'stationAssigned':
|
||
param_sql = f" AND json_extract({param}, '$.dtMachine') IN ({','.join(['?'] * len(param_value))})"
|
||
return param_sql, param_value
|
||
|
||
for param in parameters_to_check:
|
||
param_sql, param_value = get_parameter(param)
|
||
sql_condition_str += param_sql
|
||
sql_params.extend(param_value)
|
||
# print(f"dbTableAccess {table}", sql_condition_str,request.args.getlist('SN')) # cyx
|
||
|
||
try:
|
||
total_count = 0
|
||
if table == 'dutList' or table == 'DUTList':
|
||
sql_condition_str = sql_condition_str + ' ORDER BY date(createdate) DESC'
|
||
if table == 'testReqResult':
|
||
date_span_str = ""
|
||
if request.args.get('date1') and request.args.get('date2'):
|
||
try:
|
||
# 将这些字符串转化为日期对象
|
||
# 前端传入类似 date1: '2023/11/10 10:10:00', date2: '2024/6/11 10:10:00'
|
||
start_date_obj = datetime.datetime.strptime(request.args.get('date1'), "%Y/%m/%d %H:%M:%S")
|
||
end_date_obj = datetime.datetime.strptime(request.args.get('date2'), "%Y/%m/%d %H:%M:%S")
|
||
|
||
# 然后,将这些日期对象转化为字符串,以符合数据库中日期字段的格式
|
||
formatted_start_date = start_date_obj.strftime('%Y-%m-%d %H:%M:%S')
|
||
formatted_end_date = end_date_obj.strftime('%Y-%m-%d %H:%M:%S')
|
||
date_span_str = f" AND testReq.endTime BETWEEN '{formatted_start_date}' AND '{formatted_end_date}'"
|
||
except Exception as error:
|
||
print("error=", error)
|
||
finally:
|
||
sql_condition_str = sql_condition_str + date_span_str
|
||
|
||
if pageSize and currentPage:
|
||
cursor = db.cursor()
|
||
if table == 'testReqResult':
|
||
cursor.execute(f"SELECT COUNT(*) FROM testReq WHERE id >0 " + sql_condition_str, sql_params)
|
||
else:
|
||
cursor.execute(f"SELECT COUNT(*) FROM {table} WHERE id >0 " + sql_condition_str, sql_params)
|
||
total_count = cursor.fetchone()[0]
|
||
|
||
if table == 'testReqResult':
|
||
sql_str = f'SELECT testReq.* , dutList.project, dutList.phase , dutList.stationAssigned, ' \
|
||
f'dutList.name ,dutList.SN as dutSN ' \
|
||
f'FROM testReq LEFT JOIN dutList ON testReq.SN = dutList.SN ' \
|
||
f'WHERE testReq.id > 0 ' + sql_condition_str
|
||
else:
|
||
sql_str = sql_rows_str + sql_condition_str
|
||
|
||
if pageSize and currentPage:
|
||
sql_str = sql_str + f' LIMIT {pageSize} OFFSET {pageSize * (currentPage - 1)}'
|
||
print(f"dbTableAccess {table} sql_str", sql_str, sql_params) # cyx
|
||
cursor = db.cursor()
|
||
cursor.execute(sql_str, sql_params)
|
||
else:
|
||
sql_str = sql_rows_str + sql_condition_str
|
||
if pageSize and currentPage:
|
||
sql_str = sql_str + f' LIMIT {pageSize} OFFSET {pageSize * (currentPage - 1)}'
|
||
print("dbTableAccess sql_str", sql_str, sql_params) # cyx
|
||
cursor = db.cursor()
|
||
cursor.execute(sql_str, sql_params)
|
||
except sqlite3.Error as e:
|
||
print(f"dbTableAccess {table} error={e}") # cyx
|
||
return jsonify({"error": f"Database error: {e}"}), 500
|
||
|
||
rows = cursor.fetchall()
|
||
column_names = [description[0] for description in cursor.description]
|
||
result = []
|
||
for row in rows:
|
||
dict_row = {col: row[idx] if row[idx] is not None else None for idx, col in enumerate(column_names)}
|
||
if 'stationAssigned' in dict_row:
|
||
station_assigned_json = json.loads(dict_row['stationAssigned'])
|
||
if station_assigned_json:
|
||
dict_row['dtMachine'] = station_assigned_json['dtMachine']
|
||
dict_row['station'] = station_assigned_json['station']
|
||
|
||
result.append(dict_row)
|
||
return jsonify({'totalCount': total_count, 'length': len(rows), 'data': result})
|
||
|
||
elif request.method == 'POST':
|
||
data = request.get_json()
|
||
action = data['action']
|
||
records = data['records']
|
||
if action is None:
|
||
return jsonify({"status": "error", "message": "action is not specified"})
|
||
elif action == "insert":
|
||
try:
|
||
cursor = db.cursor()
|
||
current_date = datetime.datetime.now().strftime('%Y-%m-%d')
|
||
for record in records:
|
||
if 'id' in record:
|
||
del record['id']
|
||
columns = ', '.join(record.keys())
|
||
placeholders = ', '.join('?' for _ in record)
|
||
sql = f'INSERT INTO {table} ({columns}) VALUES ({placeholders})'
|
||
cursor.execute(sql, tuple(record.values()))
|
||
db.commit()
|
||
return jsonify(
|
||
{"status": "success", "action": "insert", "message": f"insert records into {table} success",
|
||
"recordsCnt": len(records)})
|
||
except sqlite3.Error as error:
|
||
print('create duts error', error)
|
||
return jsonify({"status": "error", "action": "insert", "message": f"insert records into {table} error"})
|
||
|
||
elif action == "update":
|
||
try:
|
||
cursor = db.cursor()
|
||
update_key = 'PN'
|
||
if table == 'dutList':
|
||
update_key = 'SN'
|
||
if table == 'testReq':
|
||
update_key = 'id'
|
||
if table == 'project':
|
||
update_key = 'key'
|
||
if table == 'projectPhase':
|
||
update_key = 'PN'
|
||
if table == 'dtMachine':
|
||
update_key = 'SN'
|
||
|
||
for record in records:
|
||
pn_value = record.get(update_key, None)
|
||
if pn_value is not None:
|
||
cursor.execute(
|
||
f'UPDATE {table} SET {", ".join(["{} = ?".format(field) for field in record])} WHERE {update_key} = ?',
|
||
tuple(record.values()) + (record[update_key],))
|
||
db.commit()
|
||
return jsonify(
|
||
{"status": "success", "action": "update", "message": f"update records into {table} success",
|
||
"recordsCnt": len(records)})
|
||
except sqlite3.Error as error:
|
||
print('update duts error', error)
|
||
return jsonify(
|
||
{"status": "error", "action": "update", "message": f"update records into {table} error{error}"})
|
||
|
||
elif action == "delete":
|
||
try:
|
||
cursor = db.cursor()
|
||
cursor.executemany(f"DELETE FROM {table} WHERE id = ?", [(record['id'],) for record in records])
|
||
db.commit()
|
||
return jsonify({"status": "success", "action": "delete", "message": f"delete {table} records success",
|
||
"recordsCnt": len(records)})
|
||
except sqlite3.Error as error:
|
||
print('delete', error)
|
||
return jsonify(
|
||
{"status": "error", "action": "delete", "message": f"delete {table} records error{error}"})
|
||
|
||
|
||
def get_parameter(param_name):
|
||
param = request.args.get(param_name)
|
||
if param is not None:
|
||
return f" AND {param_name} = ?", param
|
||
return "", None
|
||
|
||
|
||
def post_data_to_lmis(station, data):
|
||
print("post data to lmis--", data)
|
||
if data is None:
|
||
return
|
||
headers = {'content-type': 'application/json'}
|
||
retry_count = 0
|
||
url = "http://lims.sunwoda.com/gateway/api/rel/upload/commonUploadData"
|
||
while retry_count < 5:
|
||
try:
|
||
response = requests.post(url, data=json.dumps(data), headers=headers)
|
||
# 检查响应状态码
|
||
"""
|
||
上传失败异常处理
|
||
若上传失败,如网络异常、接口调用不同、未知错误等原因,最终导致数据上传失败,则使用同一个 requestId 进行重传,最多重试5次,每次间隔1分钟。
|
||
若重试过程中,收到 1501 状态码,则表示数据已上传成功,停止重传,结束请求。
|
||
"""
|
||
if response.status_code == 200:
|
||
res_json = response.json()
|
||
if res_json.get('statusCode') == 1501:
|
||
print("数据已上传成功")
|
||
break
|
||
else:
|
||
raise Exception("接口返回状态码错误")
|
||
else:
|
||
raise Exception("接口响应状态码错误")
|
||
except Exception as e:
|
||
print(f"上传失败,原因:{e},开始第{retry_count + 1}次重试")
|
||
retry_count += 1
|
||
time.sleep(60) # 间隔1分钟
|
||
else:
|
||
print("重试上传5次后仍失败,结束请求")
|
||
|
||
def station_data_changed(station, data):
|
||
if data is None:
|
||
return
|
||
upload_thread = threading.Thread(target=post_data_to_lmis, args=(station,data,))
|
||
upload_thread.start()
|
||
|
||
# 添加一个全局错误处理器
|
||
@app.errorhandler(500)
|
||
def internal_error(error):
|
||
return jsonify({'error': 'Internal server error'}), 500
|
||
|
||
|
||
@app.route('/shutdown', methods=['POST'])
|
||
def shutdown_flask_Server():
|
||
print("flask http server Shutting down gracefully...")
|
||
"""
|
||
def stop(self):
|
||
Stop a running SocketIO web server.
|
||
This method must be called from a HTTP or SocketIO handler function
|
||
"""
|
||
shutdown_func = request.environ.get('werkzeug.server.shutdown')
|
||
if shutdown_func is None:
|
||
print("shutdown_func is None")
|
||
else:
|
||
try:
|
||
shutdown_func()
|
||
except Exception as e:
|
||
print('Error shutting down server:', str(e))
|
||
else:
|
||
print('flask Server shutting down...')
|
||
return 'flask Server shutting down...'
|
||
|
||
|
||
def run_flask_server(flask_stop_event=None):
|
||
global http_server
|
||
print("begin init flask server")
|
||
app.run(host='0.0.0.0', port=5050)
|
||
|
||
|
||
def run_websockets():
|
||
global webSocket_server
|
||
loop = asyncio.new_event_loop()
|
||
asyncio.set_event_loop(loop)
|
||
webSocket_server = websockets.serve(websocket_server, 'localhost', 5080)
|
||
loop.run_until_complete(webSocket_server)
|
||
try:
|
||
loop.run_forever()
|
||
except KeyboardInterrupt:
|
||
print("Caught keyboard interrupt. stop web sockets server")
|
||
webSocket_server.close()
|
||
loop.run_until_complete(webSocket_server.wait_closed())
|
||
|
||
|
||
def stop_flask_server():
|
||
requests.post('http://127.0.0.1:5050/shutdown') # 将端口号改为你的 Flask 服务器的端口号
|
||
|
||
|
||
def run_webview():
|
||
eel.init('web') # 告诉 Eel 查找 'web' 文件夹
|
||
eel.start('dtm_entry.html', size=(3000, 2000), position=(0, 0), suppress_error=True)
|
||
|
||
|
||
# 定义 Flask-SocketIO 运行函数
|
||
def run_flask_socketio():
|
||
# socketio.run(app, host='0.0.0.0', port=5080)
|
||
pass
|
||
|
||
|
||
class ModbusServer:
|
||
def __init__(self, machines_config):
|
||
self.machines_config = machines_config
|
||
self.modbus_loop = asyncio.new_event_loop()
|
||
self.thread = threading.Thread(target=self.run_in_thread, daemon=True)
|
||
self.modbus_server = None
|
||
|
||
def run_in_thread(self):
|
||
asyncio.set_event_loop(self.modbus_loop)
|
||
self.modbus_loop.run_until_complete(self.start_server())
|
||
self.modbus_loop.run_forever()
|
||
|
||
async def start_server(self):
|
||
self.modbus_server = ModbusPlcServer('127.0.0.1', 5020, self.machines_config)
|
||
try:
|
||
print("Attempting to start the modbus server...")
|
||
await self.modbus_server.run_server()
|
||
except Exception as error:
|
||
print("Failed to start the modbus server:", error)
|
||
|
||
# 如果需要,可以增加一个方法来检查服务器的状态
|
||
def is_running(self):
|
||
return self.modbus_loop.is_running()
|
||
|
||
def start(self):
|
||
self.thread.start()
|
||
|
||
def stop(self):
|
||
if self.modbus_loop.is_running():
|
||
# 通过事件循环异步调用 stop_server 方法
|
||
asyncio.run_coroutine_threadsafe(self.modbus_server.stop_server(), self.modbus_loop)
|
||
# 确保事件循环也会停止
|
||
self.modbus_loop.call_soon_threadsafe(self.modbus_loop.stop)
|
||
|
||
def exit_program(sig=None, frame=None):
|
||
global main_exit_flag, http_server, flask_stop_event, modbus_server_obj, webview_thread, socketio_thread, flask_server_thread
|
||
print("收到SIGINT,正在退出...")
|
||
main_exit_flag = True
|
||
modbus_loop_stop_event = threading.Event()
|
||
stop_flask_server()
|
||
|
||
try:
|
||
print("shutdown modbus server ---2") # cyx
|
||
# 停止 Modbus 服务器线程
|
||
if modbus_server_obj:
|
||
modbus_server_obj.stop()
|
||
print("shutdown ---4") # cyx
|
||
except Exception as error:
|
||
print("failed to stop", error)
|
||
|
||
try:
|
||
|
||
http_server.stop()
|
||
# 停止 Webview 线程
|
||
if webview_thread:
|
||
webview_thread.join()
|
||
print("webview_thread stop")
|
||
|
||
except Exception as error:
|
||
print("没有安全退出", error)
|
||
sys.exit(0) # 使用sys.exit来终止程序
|
||
finally:
|
||
main_exit_flag = True
|
||
sys.exit(0) # 使用sys.exit来终止程序
|
||
|
||
|
||
def modbus_client_test():
|
||
# 创建 Modbus 客户端实例
|
||
client = ModbusTcpClient('127.0.0.1', port=5020)
|
||
# 连接到 Modbus 服务器
|
||
connection = client.connect()
|
||
if connection:
|
||
print("Modbus client connected successfully")
|
||
# 在这里执行你的 Modbus 请求
|
||
# 例如,读取线圈状态
|
||
response = client.read_coils(13, 5)
|
||
if response.isError():
|
||
print("Modbus request failed:", response)
|
||
else:
|
||
print("Coil states:", response.bits)
|
||
|
||
response = client.read_holding_registers(185, 2, 1)
|
||
if response.isError():
|
||
print("Modbus request failed:", response)
|
||
else:
|
||
print("Holding Registers is :", response.registers)
|
||
|
||
response = client.write_registers(78, [81, 88, 89, 100])
|
||
if response.isError():
|
||
print("Modbus request failed:", response)
|
||
else:
|
||
print("write Registers is :", response.isError())
|
||
else:
|
||
print("Modbus client connection failed")
|
||
|
||
# 关闭 Modbus 客户端连接
|
||
client.close()
|
||
|
||
|
||
def main(args):
|
||
global initialized, http_server, modbus_server_obj, webview_thread, socketio_thread, flask_server_thread, main_exit_flag, flask_stop_event
|
||
global dtMachineService
|
||
comm_config = {
|
||
"baudrate": 9600,
|
||
"stopbits": serial.STOPBITS_ONE,
|
||
"parity": serial.PARITY_EVEN,
|
||
"bytesize": serial.SEVENBITS
|
||
}
|
||
drop_register = {
|
||
'01': {"height": 'D0160', "cycles": "D0200", "cyclesFinished": "D0202", "start": 'M0016', "stop": "M0008"},
|
||
'02': {"height": 'D0162', "cycles": "D0204", "cyclesFinished": "D0206", "start": 'M0017', "stop": "M0018"}
|
||
|
||
}
|
||
signal.signal(signal.SIGINT, exit_program) # 设置SIGINT(Ctrl + C)信号处理器
|
||
|
||
print("init dtMachineService")
|
||
dtMachineService = machineService.DtmMachineService(args.modbusServer, args.modbusServerPort, access_table_data,
|
||
app, station_data_changed)
|
||
initialized = True
|
||
|
||
if args.startModbusServer:
|
||
# 创建并启动Modbus Server的线程
|
||
machines_config = dtMachineService.get_machine_com_config()
|
||
print("init modbus server") # cyx
|
||
modbus_server_obj = ModbusServer(machines_config)
|
||
modbus_server_obj.start()
|
||
print("main create modbus over--4")
|
||
|
||
initialized = True
|
||
print("modbus client connect to server") # cyx
|
||
dtMachineService.dtm_virt_machine_client_connect()
|
||
# modbus_client_test()
|
||
|
||
# 创建并启动Flask的线程
|
||
print("begin init flask server")
|
||
flask_stop_event = threading.Event()
|
||
flask_server_thread = threading.Thread(target=run_flask_server, args=(flask_stop_event,))
|
||
flask_server_thread.daemon = True
|
||
flask_server_thread.start()
|
||
print("init flask server over")
|
||
|
||
# 创建线程并启动
|
||
socketio_thread = threading.Thread(target=run_websockets)
|
||
socketio_thread.daemon = True
|
||
socketio_thread.start()
|
||
"""
|
||
webview_thread = threading.Thread(target=run_webview)
|
||
webview_thread.daemon = True
|
||
webview_thread.start()
|
||
"""
|
||
|
||
while not main_exit_flag:
|
||
if flask_server_thread:
|
||
flask_server_thread.join(1)
|
||
elif socketio_thread:
|
||
socketio_thread.join(1)
|
||
else:
|
||
pass
|
||
|
||
|
||
class startArgs:
|
||
def __init__(self, startModbusServer=False, modbusServer="127.0.0.1", modbusServerPort=5020):
|
||
self.startModbusServer = startModbusServer
|
||
self.modbusServer = modbusServer
|
||
self.modbusServerPort = modbusServerPort
|
||
|
||
ERVER_ADDRESS = [1]
|
||
START_ADDRESS = 41088+180
|
||
START_ADDRESS_2 = 41088+179
|
||
QUANTITY = 4
|
||
QUANTITY_2 = 4
|
||
COM_PORT = "com1"
|
||
BAUD_RATE = 19200
|
||
from pymodbus.client import ModbusSerialClient as ModbusClient
|
||
def test_modbus_plc():
|
||
# 创建Modbus RTU客户端
|
||
with ModbusClient(method="rtu", port=COM_PORT, baudrate=BAUD_RATE, parity="E" ,timeout=1) as client:
|
||
if client.connect():
|
||
print("Modbus RTU Client Connected")
|
||
else:
|
||
print("Failed to connect to Modbus RTU Client")
|
||
# 无限循环读取数据
|
||
while True:
|
||
# 遍历服务器地址
|
||
for server_address in ERVER_ADDRESS:
|
||
try:
|
||
result = client.write_register(41268,23,1)
|
||
print('write result',result.isError())
|
||
# 读取第一组保持寄存器
|
||
result = client.read_holding_registers(START_ADDRESS, QUANTITY, unit=1)
|
||
# 读取第二组保持寄存器
|
||
result_2 = client.read_holding_registers(START_ADDRESS_2, QUANTITY_2, unit=1)
|
||
|
||
# 处理读取结果
|
||
if not result.isError() and not result_2.isError():
|
||
# 控制台输出读取到的寄存器值
|
||
print(", ".join(map(str, result.registers)))
|
||
print(", ".join(map(str, result_2.registers)))
|
||
else:
|
||
# 创建解析器
|
||
# 输出读取错误信息
|
||
print(f"Error reading from server address {server_address}: {result}")
|
||
print(f"Error reading from server address {server_address}: {result_2}")
|
||
except Exception as e:
|
||
# 输出并捕获异常信息
|
||
print(f"Error: {e}")
|
||
|
||
# 暂停执行,等待下一轮读取
|
||
time.sleep(1)
|
||
|
||
pass
|
||
|
||
def test_modbus_plc1():
|
||
# 创建Modbus RTU客户端
|
||
with ModbusClient(method="rtu", port=COM_PORT, baudrate=BAUD_RATE, parity="E" ,timeout=1) as client:
|
||
if client.connect():
|
||
print("Modbus RTU Client Connected")
|
||
else:
|
||
print("Failed to connect to Modbus RTU Client")
|
||
result = client.write_coil(117, 0, 1)
|
||
print('write result', result.isError())
|
||
result = client.write_coil(118, 1, 1)
|
||
print('write result', result.isError())
|
||
|
||
if __name__ == "__main__":
|
||
test_modbus_plc1()
|
||
|
||
parser = argparse.ArgumentParser(description="My modbus and plc Description")
|
||
# 添加参数 同时启动Modbus Server
|
||
parser.add_argument('--startModbusServer', default=True, help="with start modbus server", action='store_true')
|
||
# 添加参数 ModbusServerHost 地址
|
||
parser.add_argument('--modbusServer', default="127.0.0.1", help="modbus Server URL")
|
||
# 添加参数 ModbusServer Port
|
||
parser.add_argument('--modbusServerPort', default=5020, help="Server URL")
|
||
# 解析参数
|
||
args = parser.parse_args()
|
||
# main(args)
|
||
|