parent
a70e15932a
commit
8b48f3177a
@ -0,0 +1,27 @@
|
||||
# 1804
|
||||
PROJECT_NAME = "9000"
|
||||
|
||||
# 柜体型号 9000 3000 1804 1806
|
||||
CLIENT_NUMBER = '9000'
|
||||
|
||||
TERMINAL_ID = "c97fef32-8dec-48eb-ab3f-80caa4593871"
|
||||
# TERMINAL_ID = ""
|
||||
|
||||
# 危化品
|
||||
DEFAULT_ARCHIVE_ID = "66a3877e-4c2d-4af8-859b-89921b7be38d"
|
||||
|
||||
# 虹软
|
||||
ARCSOFT_APP_ID = '3zedF4M48gsn1XeXaxWVR6DBEJJxDQYxgVk2B3w8KLgA'
|
||||
ARCSOFT_SDK_KEY = '4HKQrzqK95PrNLSB6vTFaR5GnR47REpoCikzYEd5T3Q2'
|
||||
# mysql
|
||||
# DB_URL = 'mysql://root:Yanei!23@192.168.2.101:3344/rms_ge_prod'
|
||||
# DB_URL = 'mysql://root:123456@127.0.0.1:3306/rms_ge_prod_maomingjianyansuo'
|
||||
# DB_URL = 'mysql://root:123456@127.0.0.1:3306/shceduler'
|
||||
|
||||
# DB_URL = "sqlite://db.sqlite3"
|
||||
|
||||
DB_URL = "mysql://root:123456@127.0.0.1:3306/agv"
|
||||
|
||||
# Jwt
|
||||
JWT_SECRET_KEY = "09d25e194faa6ca2556c818166b7a9573b93f7099f4f0f4caa7cf63b88e8d3e7"
|
||||
JWT_ALGORITHM = "HS256"
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,291 @@
|
||||
|
||||
import httpx
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from pytz import timezone
|
||||
import time
|
||||
from conf import setting
|
||||
from helper.logger import logger
|
||||
from datetime import datetime
|
||||
import json
|
||||
# from models.assignment import Assignment
|
||||
# from models.warehouse import Vacancy
|
||||
import os
|
||||
import codecs
|
||||
from plc.cmd import readStatus
|
||||
|
||||
|
||||
scheduler = AsyncIOScheduler(timezone=timezone('Asia/Shanghai'))
|
||||
|
||||
|
||||
# async def genCMD(assignment, cmdStdList, cmdNonStdList):
|
||||
|
||||
# cmdStdl = []
|
||||
# cmdNonStdl = []
|
||||
|
||||
# for cmdStd in cmdStdList:
|
||||
|
||||
# if 1 == cmdStd["action"]:
|
||||
# #1: 归还
|
||||
# cmdStdl.append({"Return": cmdStd})
|
||||
# elif 3 == cmdStd["action"]:
|
||||
# #3: 预约领用
|
||||
# #find new Vacancy
|
||||
# qs1 = await Vacancy.filter(is_shelf = True, is_valid = True, is_active = True)
|
||||
# if len(qs1) > 0:
|
||||
# print("find new Vacancy: ", qs1)
|
||||
# vacancy = qs1[0]
|
||||
# #generate destination with respond to task
|
||||
# cmdStdl.append({vacancy.number: cmdStd})
|
||||
# #update vacancy
|
||||
# vacancy.is_valid = False
|
||||
# vacancy.orderids = []
|
||||
# await vacancy.save()
|
||||
# else:
|
||||
# raise Exception("Shelf vacancy is not enough!")
|
||||
|
||||
# elif 5 == cmdStd["action"]:
|
||||
# #5: 现场领用
|
||||
# qs1 = await Vacancy.filter(is_connect = True, is_valid = True, is_active = True)
|
||||
# if len(qs1) > 0:
|
||||
# print("find new Vacancy: ", qs1)
|
||||
# vacancy = qs1[0]
|
||||
# #generate destination with respond to task
|
||||
# cmdStdl.append({vacancy.number: cmdStd})
|
||||
# #update vacancy
|
||||
# vacancy.is_valid = False
|
||||
# vacancy.orderids = []
|
||||
# await vacancy.save()
|
||||
# else:
|
||||
# raise Exception("Shelf vacancy is not enough!")
|
||||
|
||||
# for cmdNonStd in cmdNonStdList:
|
||||
|
||||
# if 1 == cmdNonStd["action"]:
|
||||
# #1: 归还
|
||||
# # cmdNonStdl.append({"Return": cmdNonStd})
|
||||
|
||||
# #where to return after usage
|
||||
# # to shelf or stay in connection !!!
|
||||
|
||||
|
||||
# #check if next assignment will use this tray
|
||||
# assignmentNext = await Assignment.filter(
|
||||
# is_canceled = False,
|
||||
# is_processing = False,
|
||||
# is_failed = False,
|
||||
# is_done = False,
|
||||
# is_valid = True,
|
||||
# is_active = True
|
||||
# ).first()
|
||||
|
||||
# if assignmentNext:
|
||||
# subtasksNext = await assignmentNext.subtasks.all()
|
||||
|
||||
# for subtaskNext in subtasksNext:
|
||||
# if 9 == subtaskNext.typetask:
|
||||
# qs4 = await Vacancy.filter(is_connect = True, is_valid = False, is_active = True, traynum=[subtaskNext.traynum])
|
||||
# if len(qs4) > 0:
|
||||
# print("connection area tray num {}, is used in next assignment {}, don't need to move ".format(subtaskNext.traynum, assignmentNext.id))
|
||||
# cmdStdl.insert(0, {"Skip": cmdNonStd})
|
||||
# return
|
||||
|
||||
# #check if the tray in Schelve
|
||||
# qs5 = await Vacancy.filter(is_shelf = True, is_valid = False, is_active = True, traynum=[cmdNonStd["traynum"]])
|
||||
# if len(qs5) > 0:
|
||||
# vacancy = qs5[0]
|
||||
# if vacancy.orderids and len(vacancy) > 0:
|
||||
# # update destination to Schelve vacancy
|
||||
# cmdNonStd["coordinates"] = vacancy.number
|
||||
# # insert into beginning of the list
|
||||
# cmdStdl.insert(0, {"Return": cmdNonStd})
|
||||
# return
|
||||
|
||||
# # normal return process
|
||||
# cmdStdl.append({"Return": cmdNonStd})
|
||||
# elif 3 == cmdNonStd["action"]:
|
||||
# #3: 预约领用
|
||||
# qs1 = await Vacancy.filter(is_shelf = True, is_valid = True, is_active = True)
|
||||
# if len(qs1) > 0:
|
||||
# vacancy = qs1[0]
|
||||
|
||||
# #check if tray already at Vacancy
|
||||
# qs3 = await Vacancy.filter(is_valid = False, is_active = True, traynum=[cmdNonStd["traynum"]])
|
||||
# if len(qs3) > 0:
|
||||
# vacancy3 = qs3[0]
|
||||
# #if exist then change destination to corresponding vacancy
|
||||
# cmdNonStd["coordinates"] = vacancy3.number
|
||||
# #update vacancy
|
||||
# orderids = vacancy3.orderids
|
||||
# if not orderids:
|
||||
# orderids = [ cmdNonStd["orderid"] ]
|
||||
# else:
|
||||
# orderids.append(cmdNonStd["orderid"])
|
||||
|
||||
# vacancy3.is_valid = False
|
||||
# vacancy3.orderids = orderids
|
||||
# await vacancy3.save()
|
||||
|
||||
# #generate destination with respond to task
|
||||
# # cmdNonStdl.append({vacancy.number: cmdNonStd})
|
||||
# cmdStdl.append({vacancy.number: cmdNonStd})
|
||||
# #update vacancy
|
||||
# orderids = vacancy.orderids
|
||||
# if not orderids:
|
||||
# orderids = [ cmdNonStd["orderid"] ]
|
||||
# else:
|
||||
# orderids.append(cmdNonStd["orderid"])
|
||||
|
||||
# vacancy.is_valid = False
|
||||
# vacancy.traynum = cmdNonStd["traynum"]
|
||||
# vacancy.orderids = orderids
|
||||
# await vacancy.save()
|
||||
# else:
|
||||
# raise Exception("Shelf vacancy is not enough!")
|
||||
|
||||
# elif 5 == cmdNonStd["action"]:
|
||||
# #5: 现场领用
|
||||
# qs1 = await Vacancy.filter(is_connect = True, is_valid = True, is_active = True)
|
||||
# if len(qs1) > 0:
|
||||
# vacancy = qs1[0]
|
||||
|
||||
# #check if tray already at Vacancy
|
||||
# qs3 = await Vacancy.filter(is_valid = False, is_active = True, traynum=[cmdNonStd["traynum"]])
|
||||
# if len(qs3) > 0:
|
||||
# vacancy3 = qs3[0]
|
||||
# #if exist then change destination to corresponding vacancy
|
||||
# cmdNonStd["coordinates"] = vacancy3.number
|
||||
# #update vacancy
|
||||
# orderids = vacancy3.orderids
|
||||
|
||||
# if orderids and len(orderids) > 0:
|
||||
# lstSet = set(orderids)
|
||||
# lstSet.discard( cmdNonStd["orderid"] )
|
||||
# # Converting set back to list
|
||||
# orderids=list(lstSet)
|
||||
# else:
|
||||
# orderids = []
|
||||
|
||||
# if len(orderids) > 0:
|
||||
# vacancy3.is_valid = False
|
||||
# else:
|
||||
# vacancy3.is_valid = True
|
||||
|
||||
# vacancy3.orderids = orderids
|
||||
# await vacancy3.save()
|
||||
|
||||
# #generate destination with respond to task
|
||||
# # cmdNonStdl.append({vacancy.number: cmdNonStd})
|
||||
# # #update vacancy
|
||||
# # orderids = vacancy.orderids
|
||||
# # if not orderids:
|
||||
# # orderids = [ cmdNonStd["orderid"] ]
|
||||
# # else:
|
||||
# # orderids.append(cmdNonStd["orderid"])
|
||||
|
||||
# # vacancy.is_valid = False
|
||||
# # vacancy.traynum = cmdNonStd["traynum"]
|
||||
# # # vacancy.orderids = orderids
|
||||
# # await vacancy.save()
|
||||
|
||||
# cmdStdl.append({vacancy.number: cmdNonStd})
|
||||
|
||||
# else:
|
||||
# raise Exception("Connect vacancy is not enough!")
|
||||
|
||||
|
||||
# assignment.cmd = cmdStdl + cmdNonStdl
|
||||
# await assignment.save()
|
||||
|
||||
# async def agv_asign_job():
|
||||
# print("Working ", "{}".format(datetime.now()))
|
||||
|
||||
# # assignments = await Assignment.filter(is_canceled = False, is_valid = True, is_active = True)
|
||||
# assignments = await Assignment.filter(
|
||||
# is_canceled = False,
|
||||
# is_processing = False,
|
||||
# is_failed = False,
|
||||
# is_done = False,
|
||||
# is_valid = True,
|
||||
# is_active = True
|
||||
# )
|
||||
|
||||
# path = "agvtask/testdata/test-{}.json".format(datetime.now().strftime("%Y-%m-%d_%H_%M_%S"))
|
||||
|
||||
# assignmentsL = []
|
||||
|
||||
# for assignment in assignments:
|
||||
# owner = await assignment.owner
|
||||
# subtasks = await assignment.subtasks.all()
|
||||
# assignmentJ = assignment.as_json()
|
||||
|
||||
# subtasksL = []
|
||||
# cmdStdList = []
|
||||
# cmdNonStdList = []
|
||||
|
||||
# for subtask in subtasks:
|
||||
# if 1 == subtask.typetask \
|
||||
# or 1 == subtask.typetask \
|
||||
# or 3 == subtask.typetask \
|
||||
# or 5 == subtask.typetask \
|
||||
# or 7 == subtask.typetask:
|
||||
# # for standard
|
||||
# cmdStdList.append({
|
||||
# "orderid": subtask.orderid,
|
||||
# "name": subtask.name,
|
||||
# "typetask": subtask.typetask,
|
||||
# "action": subtask.action,
|
||||
# "quantity": subtask.quantity,
|
||||
# "coordinates": subtask.coordinates,
|
||||
# "traynum": subtask.traynum,
|
||||
# })
|
||||
# else:
|
||||
# # for non standard
|
||||
# cmdNonStdList.append({
|
||||
# "orderid": subtask.orderid,
|
||||
# "name": subtask.name,
|
||||
# "typetask": subtask.typetask,
|
||||
# "action": subtask.action,
|
||||
# "quantity": subtask.quantity,
|
||||
# "coordinates": subtask.coordinates,
|
||||
# "traynum": subtask.traynum,
|
||||
# })
|
||||
|
||||
# subtasksL.append(subtask.as_json())
|
||||
|
||||
# assignment.is_processing = True
|
||||
# await assignment.save()
|
||||
|
||||
# await genCMD(assignment, cmdStdList, cmdNonStdList)
|
||||
|
||||
# assignmentJ["owner"] = owner.as_json()
|
||||
# assignmentJ["subtasks"] = subtasksL
|
||||
# assignmentJ["cmd"] = assignment.cmd
|
||||
|
||||
# assignmentsL.append(assignmentJ)
|
||||
|
||||
# # print("assignment: ", assignmentJ)
|
||||
# with open(path, "w", encoding="utf-8") as fp:
|
||||
# json.dump(assignmentsL, fp, ensure_ascii=False)
|
||||
|
||||
|
||||
|
||||
def getAgvStatus():
|
||||
status = readStatus()
|
||||
|
||||
print("getAgvStatus: ", status)
|
||||
|
||||
|
||||
async def start_scheduler():
|
||||
""" """
|
||||
second = 10
|
||||
logger.info(f"准备启动AGV小车任务派发定时任务, 间隔时间: {second}")
|
||||
|
||||
scheduler.add_job(getAgvStatus, 'interval', seconds=int(second))
|
||||
|
||||
# scheduler.add_job(agv_asign_job, 'interval', seconds=int(second))
|
||||
|
||||
# minute = 1
|
||||
# logger.info(f"准备启动AGV小车任务派发定时任务, 间隔时间: {minute}")
|
||||
# scheduler.add_job(agv_asign_job, 'interval', minutes=int(minute))
|
||||
# scheduler.add_job(agv_asign_job, 'cron', hour="8,20")
|
||||
scheduler.start()
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,123 @@
|
||||
INF=100000
|
||||
|
||||
# shelfMatrixLabel = ["SH1-L1", "SH1-L2", "SH1-L3", "SH1-L4", "SH1-L5", "SH2-L1", "SH2-L2", "SH2-L3", "SH2-L4", "SH2-L5", "SH3-L1", "SH3-L2", "SH3-L3", "SH3-L4", "SH3-L5"]
|
||||
# shelfMatrix = []
|
||||
|
||||
|
||||
# dmatrixLabel = ['B7', 'C1', 'C4', 'D0', 'E2', 'E4', 'E9', 'E8', 'F2', 'F4', 'G6', 'H1', 'I0', 'I6', 'I9', 'J0', 'J1', 'J6', 'K8', 'L2', 'L3', 'L7', 'L8', 'M7', 'M9', 'N1', 'N2', 'N3', 'N4', 'N6', 'O1', 'O2', 'O3', 'Q3', 'Q6', 'Q7', 'R3', 'S8', 'T4', 'U0', 'U5', 'U2', 'V1', 'V3', 'V7', 'X6', 'Y5', 'Z4']
|
||||
|
||||
# dmatrixLabel = ['A0', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2', 'G1', 'G2', 'H1', 'H2', 'I1', 'I2', 'J1', 'J2', 'K1', 'K2', 'L1', 'L2', 'M1', 'M2', 'N1', 'N2', 'P1', 'P2', 'Q1', 'Q2', 'R1', 'R2', 'S1', 'S2', 'T1', 'T2', 'U1', 'U2', 'V1', 'V2', 'W1', 'W2', 'X1', 'X2', 'Y1', 'Y2']
|
||||
|
||||
dmatrixLabel = ['A0', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2', 'E1', 'E2', 'F1', 'F2', 'G1', 'G2', 'H1', 'H2', 'I1', 'I2', 'J1', 'J2', 'K1', 'K2', 'L1', 'L2', 'M1', 'M2', 'N1', 'N2', 'P1', 'P2', 'Q1', 'Q2', 'R1', "SH1-L1", "SH1-L2", "SH1-L3", "SH1-L4", "SH1-L5", "SH2-L1", "SH2-L2", "SH2-L3", "SH2-L4", "SH2-L5", "SH3-L1", "SH3-L2", "SH3-L3", "SH3-L4", "SH3-L5"]
|
||||
|
||||
|
||||
dmatrix = [
|
||||
[ 0, 4727, 1205, 6363, 3657, 3130, 2414, 563, 463, 5654, 1713, 1604, 2368, 2201, 1290, 1004, 3833, 2258, 3419, 2267, 2957, 720, 1700, 5279, 2578, 6076, 3465, 2654, 3625, 3115, 1574, 3951, 1748, 2142, 6755, 2383, 3306, 1029, 3530, 825, 2188, 4820, 3489, 1947, 6835, 1542, 2379, 3744],
|
||||
[ 4727, 0, 3588, 2012, 1842, 6977, 6501, 5187, 5028, 2327, 4148, 4723, 3635, 3125, 4907, 3930, 7463, 6338, 7243, 5105, 4043, 4022, 3677, 2863, 3106, 1850, 7173, 6630, 1204, 6814, 6001, 3447, 5253, 2656, 3123, 6274, 7183, 5622, 3085, 4564, 2756, 1591, 7027, 6186, 3472, 5461, 4390, 2088],
|
||||
[ 1205, 3588, 0, 5163, 2458, 3678, 3071, 1742, 1444, 4462, 1184, 1520, 1498, 1103, 1501, 951, 4298, 2903, 3967, 2169, 2209, 652, 828, 4136, 1518, 4873, 3954, 3254, 2446, 3581, 2441, 2960, 1966, 950, 5564, 2916, 3878, 2035, 2482, 1027, 1395, 3617, 3891, 2686, 5661, 2023, 1867, 2560],
|
||||
[ 6363, 2012, 5163, 0, 2799, 8064, 7727, 6878, 6581, 1402, 5366, 5946, 4679, 4378, 6225, 5709, 8417, 7578, 8296, 6135, 4802, 5707, 4982, 2322, 4178, 320, 8186, 7800, 2778, 7859, 7408, 3763, 6461, 4223, 1427, 7451, 8263, 7131, 3669, 6011, 4638, 1681, 7987, 7502, 1877, 6758, 5360, 2844],
|
||||
[ 3657, 1842, 2458, 2799, 0, 5330, 4946, 4200, 3824, 2012, 2573, 3157, 1924, 1580, 3427, 3179, 5749, 4793, 5577, 3409, 2223, 3066, 2185, 1860, 1401, 2491, 5486, 5035, 894, 5141, 4611, 1669, 3677, 1590, 3113, 4682, 5533, 4352, 1252, 3227, 2426, 1169, 5313, 4706, 3241, 3962, 2651, 304],
|
||||
[ 3130, 6977, 3678, 8064, 5330, 0, 743, 3209, 2670, 6929, 2831, 2266, 3407, 3854, 2178, 4076, 727, 881, 293, 1930, 3310, 3672, 3315, 6199, 3932, 7745, 365, 482, 5774, 261, 1659, 4513, 1746, 4431, 7910, 769, 207, 2225, 4435, 2681, 5053, 6384, 550, 1224, 7805, 1670, 2704, 5230],
|
||||
[ 2414, 6501, 3071, 7727, 4946, 743, 0, 2468, 1952, 6673, 2380, 1795, 3051, 3405, 1604, 3382, 1469, 168, 1020, 1681, 3110, 2993, 2827, 6009, 3552, 7412, 1104, 267, 5300, 821, 916, 4348, 1270, 3890, 7698, 332, 900, 1484, 4185, 2049, 4415, 6051, 1219, 482, 7635, 1054, 2432, 4884],
|
||||
[ 563, 5187, 1742, 6878, 4200, 3209, 2468, 0, 718, 6203, 2241, 2051, 2920, 2762, 1687, 1304, 3932, 2331, 3487, 2669, 3487, 1175, 2260, 5840, 3141, 6596, 3563, 2728, 4120, 3240, 1559, 4507, 2082, 2658, 7304, 2512, 3364, 985, 4091, 1319, 2544, 5358, 3632, 1987, 7391, 1785, 2879, 4296],
|
||||
[ 463, 5028, 1444, 6581, 3824, 2670, 1952, 718, 0, 5789, 1602, 1343, 2330, 2291, 970, 1451, 3376, 1796, 2959, 1951, 2835, 1112, 1725, 5346, 2628, 6285, 3007, 2193, 3889, 2661, 1122, 3920, 1372, 2391, 6883, 1927, 2845, 611, 3543, 676, 2590, 4993, 3039, 1486, 6934, 1112, 2196, 3876],
|
||||
[ 5654, 2327, 4462, 1402, 2012, 6929, 6673, 6203, 5789, 0, 4392, 4947, 3648, 3501, 5274, 5183, 7216, 6535, 7140, 5022, 3621, 5077, 4090, 922, 3207, 1131, 7014, 6714, 2437, 6707, 6477, 2476, 5432, 3599, 1102, 6376, 7121, 6284, 2497, 5160, 4318, 937, 6795, 6507, 1268, 5773, 4249, 1914],
|
||||
[ 1713, 4148, 1184, 5366, 2573, 2831, 2380, 2241, 1602, 4392, 0, 586, 766, 1029, 883, 2040, 3353, 2224, 3100, 1049, 1246, 1625, 503, 3841, 1196, 5054, 3042, 2488, 2945, 2676, 2087, 2331, 1114, 1650, 5459, 2132, 3037, 1958, 1997, 931, 2513, 3701, 2923, 2137, 5459, 1394, 711, 2534],
|
||||
[ 1604, 4723, 1520, 5946, 3157, 2266, 1795, 2051, 1343, 4947, 586, 0, 1299, 1612, 406, 2208, 2824, 1639, 2542, 694, 1586, 1767, 1050, 4357, 1770, 5633, 2498, 1907, 3520, 2128, 1558, 2778, 531, 2171, 6003, 1552, 2472, 1538, 2506, 791, 2912, 4277, 2403, 1564, 5983, 827, 892, 3109],
|
||||
[ 2368, 3635, 1498, 4679, 1924, 3407, 3051, 2920, 2330, 3648, 766, 1299, 0, 646, 1642, 2446, 3840, 2905, 3655, 1488, 730, 2096, 697, 3076, 533, 4363, 3567, 3122, 2453, 3219, 2842, 1592, 1791, 1480, 4706, 2772, 3610, 2721, 1232, 1656, 2550, 3001, 3403, 2860, 4697, 2126, 756, 1836],
|
||||
[ 2201, 3125, 1103, 4378, 1580, 3854, 3405, 2762, 2291, 3501, 1029, 1612, 646, 0, 1853, 2026, 4349, 3247, 4119, 1997, 1341, 1753, 606, 3078, 419, 4070, 4052, 3517, 1923, 3690, 3032, 1866, 2142, 838, 4593, 3161, 4060, 2788, 1380, 1663, 1932, 2736, 3915, 3138, 4647, 2395, 1351, 1592],
|
||||
[ 1290, 4907, 1501, 6225, 3427, 2178, 1604, 1687, 970, 5274, 883, 406, 1642, 1853, 0, 2029, 2803, 1438, 2466, 986, 1987, 1593, 1253, 4716, 2072, 5915, 2454, 1764, 3710, 2082, 1204, 3164, 497, 2287, 6342, 1419, 2379, 1134, 2867, 554, 2885, 4569, 2405, 1289, 6338, 555, 1297, 3406],
|
||||
[ 1004, 3930, 951, 5709, 3179, 4076, 3382, 1304, 1451, 5183, 2040, 2208, 2446, 2026, 2029, 0, 4759, 3220, 4368, 2900, 3151, 442, 1765, 4960, 2444, 5443, 4396, 3610, 2932, 4034, 2572, 3891, 2525, 1590, 6278, 3313, 4261, 2033, 3398, 1476, 1241, 4287, 4390, 2928, 6419, 2428, 2749, 3337],
|
||||
[ 3833, 7463, 4298, 8417, 5749, 727, 1469, 3932, 3376, 7216, 3353, 2824, 3840, 4349, 2803, 4759, 0, 1601, 477, 2359, 3617, 4345, 3851, 6433, 4372, 8098, 370, 1206, 6267, 726, 2384, 4754, 2335, 4991, 8148, 1452, 609, 2949, 4752, 3331, 5687, 6746, 437, 1948, 8005, 2334, 3098, 5618],
|
||||
[ 2258, 6338, 2903, 7578, 4793, 881, 168, 2331, 1796, 6535, 2224, 1639, 2905, 3247, 1438, 3220, 1601, 0, 1165, 1563, 2988, 2829, 2666, 5882, 3401, 7263, 1233, 399, 5138, 923, 794, 4227, 1117, 3724, 7565, 286, 1049, 1348, 4051, 1881, 4248, 5903, 1322, 355, 7508, 887, 2302, 4736],
|
||||
[ 3419, 7243, 3967, 8296, 5577, 293, 1020, 3487, 2959, 7140, 3100, 2542, 3655, 4119, 2466, 4368, 477, 1165, 0, 2170, 3520, 3965, 3588, 6393, 4183, 7977, 202, 767, 6041, 438, 1932, 4706, 2027, 4711, 8107, 1061, 132, 2503, 4652, 2972, 5344, 6617, 486, 1501, 7989, 1962, 2939, 5469],
|
||||
[ 2267, 5105, 2169, 6135, 3409, 1930, 1681, 2669, 1951, 5022, 1049, 694, 1488, 1997, 986, 2900, 2359, 1563, 2170, 0, 1430, 2460, 1547, 4333, 2019, 5817, 2079, 1694, 3910, 1733, 1813, 2668, 654, 2694, 6029, 1366, 2130, 1991, 2525, 1474, 3542, 4455, 1923, 1641, 5957, 1071, 777, 3302],
|
||||
[ 2957, 4043, 2209, 4802, 2223, 3310, 3110, 3487, 2835, 3621, 1246, 1586, 730, 1341, 1987, 3151, 3617, 2988, 3520, 1430, 0, 2779, 1387, 2905, 1062, 4482, 3398, 3119, 2922, 3087, 3115, 1240, 1953, 2175, 4607, 2796, 3501, 3119, 1136, 2173, 3268, 3136, 3189, 3029, 4527, 2355, 711, 2042],
|
||||
[ 720, 4022, 652, 5707, 3066, 3672, 2993, 1175, 1112, 5077, 1625, 1767, 2096, 1753, 1593, 442, 4345, 2829, 3965, 2460, 2779, 0, 1401, 4781, 2166, 5427, 3984, 3212, 2946, 3620, 2224, 3603, 2089, 1496, 6178, 2906, 3861, 1719, 3132, 1040, 1479, 4211, 3969, 2553, 6290, 2012, 2336, 3189],
|
||||
[ 1700, 3677, 828, 4982, 2185, 3315, 2827, 2260, 1725, 4090, 503, 1050, 697, 606, 1253, 1765, 3851, 2666, 3588, 1547, 1387, 1401, 0, 3621, 903, 4675, 3537, 2954, 2475, 3169, 2427, 2254, 1578, 1148, 5177, 2598, 3521, 2194, 1833, 1074, 2054, 3340, 3423, 2541, 5213, 1801, 1077, 2190],
|
||||
[ 5279, 2863, 4136, 2322, 1860, 6199, 6009, 5840, 5346, 922, 3841, 4357, 3076, 3078, 4716, 4960, 6433, 5882, 6393, 4333, 2905, 4781, 3621, 0, 2718, 2042, 6254, 6024, 2569, 5966, 5913, 1687, 4807, 3384, 1716, 5699, 6384, 5787, 1852, 4687, 4285, 1272, 6022, 5892, 1629, 5178, 3581, 1639],
|
||||
[ 2578, 3106, 1518, 4178, 1401, 3932, 3552, 3141, 2628, 3207, 1196, 1770, 533, 419, 2072, 2444, 4372, 3401, 4183, 2019, 1062, 2166, 903, 2718, 0, 3864, 4097, 3635, 1932, 3748, 3274, 1448, 2284, 1164, 4286, 3283, 4136, 3086, 967, 1973, 2285, 2507, 3935, 3331, 4312, 2589, 1284, 1340],
|
||||
[ 6076, 1850, 4873, 320, 2491, 7745, 7412, 6596, 6285, 1131, 5054, 5633, 4363, 4070, 5915, 5443, 8098, 7263, 7977, 5817, 4482, 5427, 4675, 2042, 3864, 0, 7866, 7483, 2515, 7539, 7101, 3449, 6146, 3938, 1375, 7134, 7944, 6831, 3349, 5709, 4397, 1363, 7667, 7190, 1798, 6446, 5041, 2528],
|
||||
[ 3465, 7173, 3954, 8186, 5486, 365, 1104, 3563, 3007, 7014, 3042, 2498, 3567, 4052, 2454, 4396, 370, 1233, 202, 2079, 3398, 3984, 3537, 6254, 4097, 7866, 0, 839, 5973, 374, 2019, 4569, 1996, 4669, 7970, 1085, 305, 2581, 4532, 2976, 5339, 6509, 287, 1581, 7844, 1974, 2838, 5369],
|
||||
[ 2654, 6630, 3254, 7800, 5035, 482, 267, 2728, 2193, 6714, 2488, 1907, 3122, 3517, 1764, 3610, 1206, 399, 767, 1694, 3119, 3212, 2954, 6024, 3635, 7483, 839, 0, 5427, 558, 1181, 4349, 1377, 4044, 7723, 356, 653, 1744, 4218, 2241, 4614, 6121, 955, 743, 7644, 1231, 2465, 4957],
|
||||
[ 3625, 1204, 2446, 2778, 894, 5774, 5300, 4120, 3889, 2437, 2945, 3520, 2453, 1923, 3710, 2932, 6267, 5138, 6041, 3910, 2922, 2946, 2475, 2569, 1932, 2515, 5973, 5427, 0, 5612, 4824, 2550, 4050, 1498, 3476, 5071, 5980, 4470, 2096, 3388, 1911, 1501, 5831, 4994, 3704, 4264, 3209, 1196],
|
||||
[ 3115, 6814, 3581, 7859, 5141, 261, 821, 3240, 2661, 6707, 2676, 2128, 3219, 3690, 2082, 4034, 726, 923, 438, 1733, 3087, 3620, 3169, 5966, 3748, 7539, 374, 558, 5612, 0, 1716, 4280, 1624, 4298, 7679, 735, 420, 2263, 4216, 2606, 4967, 6179, 400, 1277, 7567, 1609, 2501, 5032],
|
||||
[ 1574, 6001, 2441, 7408, 4611, 1659, 916, 1559, 1122, 6477, 2087, 1558, 2842, 3032, 1204, 2572, 2384, 794, 1932, 1813, 3115, 2224, 2427, 5913, 3274, 7101, 2019, 1181, 4824, 1716, 0, 4330, 1180, 3346, 7545, 1023, 1808, 578, 4062, 1438, 3693, 5763, 2115, 440, 7537, 763, 2404, 4603],
|
||||
[ 3951, 3447, 2960, 3763, 1669, 4513, 4348, 4507, 3920, 2476, 2331, 2778, 1592, 1866, 3164, 3891, 4754, 4227, 4706, 2668, 1240, 3603, 2254, 1687, 1448, 3449, 4569, 4349, 2550, 4280, 4330, 0, 3184, 2510, 3402, 4031, 4698, 4281, 533, 3245, 3612, 2187, 4339, 4265, 3296, 3576, 1941, 1381],
|
||||
[ 1748, 5253, 1966, 6461, 3677, 1746, 1270, 2082, 1372, 5432, 1114, 531, 1791, 2142, 497, 2525, 2335, 1117, 2027, 654, 1953, 2089, 1578, 4807, 2284, 6146, 1996, 1377, 4050, 1624, 1180, 3184, 0, 2685, 6475, 1022, 1952, 1341, 2963, 1050, 3358, 4787, 1926, 1086, 6436, 422, 1244, 3619],
|
||||
[ 2142, 2656, 950, 4223, 1590, 4431, 3890, 2658, 2391, 3599, 1650, 2171, 1480, 838, 2287, 1590, 4991, 3724, 4711, 2694, 2175, 1496, 1148, 3384, 1164, 3938, 4669, 4044, 1498, 4298, 3346, 2510, 2685, 0, 4697, 3693, 4636, 2975, 1981, 1909, 1124, 2718, 4565, 3548, 4830, 2839, 2140, 1751],
|
||||
[ 6755, 3123, 5564, 1427, 3113, 7910, 7698, 7304, 6883, 1102, 5459, 6003, 4706, 4593, 6342, 6278, 8148, 7565, 8107, 6029, 4607, 6178, 5177, 1716, 4286, 1375, 7970, 7723, 3476, 7679, 7545, 3402, 6475, 4697, 0, 7393, 8097, 7370, 3515, 6249, 5379, 2001, 7738, 7556, 461, 6829, 5267, 3013],
|
||||
[ 2383, 6274, 2916, 7451, 4682, 769, 332, 2512, 1927, 6376, 2132, 1552, 2772, 3161, 1419, 3313, 1452, 286, 1061, 1366, 2796, 2906, 2598, 5699, 3283, 7134, 1085, 356, 5071, 735, 1023, 4031, 1022, 3693, 7393, 0, 965, 1542, 3883, 1913, 4286, 5772, 1121, 600, 7322, 902, 2128, 4608],
|
||||
[ 3306, 7183, 3878, 8263, 5533, 207, 900, 3364, 2845, 7121, 3037, 2472, 3610, 4060, 2379, 4261, 609, 1049, 132, 2130, 3501, 3861, 3521, 6384, 4136, 7944, 305, 653, 5980, 420, 1808, 4698, 1952, 4636, 8097, 965, 0, 2380, 4629, 2877, 5250, 6583, 570, 1380, 7986, 1866, 2904, 5432],
|
||||
[ 1029, 5622, 2035, 7131, 4352, 2225, 1484, 985, 611, 6284, 1958, 1538, 2721, 2788, 1134, 2033, 2949, 1348, 2503, 1991, 3119, 1719, 2194, 5787, 3086, 6831, 2581, 1744, 4470, 2263, 578, 4281, 1341, 2975, 7370, 1542, 2380, 0, 3952, 1127, 3197, 5518, 2658, 1002, 7395, 951, 2429, 4380],
|
||||
[ 3530, 3085, 2482, 3669, 1252, 4435, 4185, 4091, 3543, 2497, 1997, 2506, 1232, 1380, 2867, 3398, 4752, 4051, 4652, 2525, 1136, 3132, 1833, 1852, 967, 3349, 4532, 4218, 2096, 4216, 4062, 533, 2963, 1981, 3515, 3883, 4629, 3952, 0, 2873, 3080, 2012, 4324, 4046, 3478, 3328, 1755, 1000],
|
||||
[ 825, 4564, 1027, 6011, 3227, 2681, 2049, 1319, 676, 5160, 931, 791, 1656, 1663, 554, 1476, 3331, 1881, 2972, 1474, 2173, 1040, 1074, 4687, 1973, 5709, 2976, 2241, 3388, 2606, 1438, 3245, 1050, 1909, 6249, 1913, 2877, 1127, 2873, 0, 2374, 4392, 2943, 1659, 6285, 1012, 1563, 3254],
|
||||
[ 2188, 2756, 1395, 4638, 2426, 5053, 4415, 2544, 2590, 4318, 2513, 2912, 2550, 1932, 2885, 1241, 5687, 4248, 5344, 3542, 3268, 1479, 2054, 4285, 2285, 4397, 5339, 4614, 1911, 4967, 3693, 3612, 3358, 1124, 5379, 4286, 5250, 3197, 3080, 2374, 0, 3386, 5284, 3997, 5585, 3386, 3125, 2664],
|
||||
[ 4820, 1591, 3617, 1681, 1169, 6384, 6051, 5358, 4993, 937, 3701, 4277, 3001, 2736, 4569, 4287, 6746, 5903, 6617, 4455, 3136, 4211, 3340, 1272, 2507, 1363, 6509, 6121, 1501, 6179, 5763, 2187, 4787, 2718, 2001, 5772, 6583, 5518, 2012, 4392, 3386, 0, 6314, 5837, 2205, 5095, 3680, 1169],
|
||||
[ 3489, 7027, 3891, 7987, 5313, 550, 1219, 3632, 3039, 6795, 2923, 2403, 3403, 3915, 2405, 4390, 437, 1322, 486, 1923, 3189, 3969, 3423, 6022, 3935, 7667, 287, 955, 5831, 400, 2115, 4339, 1926, 4565, 7738, 1121, 570, 2658, 4324, 2943, 5284, 6314, 0, 1676, 7603, 1964, 2662, 5184],
|
||||
[ 1947, 6186, 2686, 7502, 4706, 1224, 482, 1987, 1486, 6507, 2137, 1564, 2860, 3138, 1289, 2928, 1948, 355, 1501, 1641, 3029, 2553, 2541, 5892, 3331, 7190, 1581, 743, 4994, 1277, 440, 4265, 1086, 3548, 7556, 600, 1380, 1002, 4046, 1659, 3997, 5837, 1676, 0, 7521, 744, 2325, 4670],
|
||||
[ 6835, 3472, 5661, 1877, 3241, 7805, 7635, 7391, 6934, 1268, 5459, 5983, 4697, 4647, 6338, 6419, 8005, 7508, 7989, 5957, 4527, 6290, 5213, 1629, 4312, 1798, 7844, 7644, 3704, 7567, 7537, 3296, 6436, 4830, 461, 7322, 7986, 7395, 3478, 6285, 5585, 2205, 7603, 7521, 0, 6805, 5208, 3102],
|
||||
[ 1542, 5461, 2023, 6758, 3962, 1670, 1054, 1785, 1112, 5773, 1394, 827, 2126, 2395, 555, 2428, 2334, 887, 1962, 1071, 2355, 2012, 1801, 5178, 2589, 6446, 1974, 1231, 4264, 1609, 763, 3576, 422, 2839, 6829, 902, 1866, 951, 3328, 1012, 3386, 5095, 1964, 744, 6805, 0, 1644, 3928],
|
||||
[ 2379, 4390, 1867, 5360, 2651, 2704, 2432, 2879, 2196, 4249, 711, 892, 756, 1351, 1297, 2749, 3098, 2302, 2939, 777, 711, 2336, 1077, 3581, 1284, 5041, 2838, 2465, 3209, 2501, 2404, 1941, 1244, 2140, 5267, 2128, 2904, 2429, 1755, 1563, 3125, 3680, 2662, 2325, 5208, 1644, 0, 2532],
|
||||
[ 3744, 2088, 2560, 2844, 304, 5230, 4884, 4296, 3876, 1914, 2534, 3109, 1836, 1592, 3406, 3337, 5618, 4736, 5469, 3302, 2042, 3189, 2190, 1639, 1340, 2528, 5369, 4957, 1196, 5032, 4603, 1381, 3619, 1751, 3013, 4608, 5432, 4380, 1000, 3254, 2664, 1169, 5184, 4670, 3102, 3928, 2532, 0],
|
||||
]
|
||||
|
||||
|
||||
# dmatrix = [
|
||||
# [0, 4, INF, 10],
|
||||
# [4, 0, 50, 20],
|
||||
# [INF, 50, 0, 3],
|
||||
# [10, 20, 3, 0],
|
||||
# ]
|
||||
|
||||
|
||||
# # # p01_d.txt
|
||||
# dmatrix = [
|
||||
# [0, 29, 82, 46, 68, 52, 72, 42, 51, 55, 29, 74, 23, 72, 46],
|
||||
# [29, 0, 55, 46, 42, 43, 43, 23, 23, 31, 41, 51, 11, 52, 21],
|
||||
# [82, 55, 0, 68, 46, 55, 23, 43, 41, 29, 79, 21, 64, 31, 51],
|
||||
# [46, 46, 68, 0, 82, 15, 72, 31, 62, 42, 21, 51, 51, 43, 64],
|
||||
# [68, 42, 46, 82, 0, 74, 23, 52, 21, 46, 82, 58, 46, 65, 23],
|
||||
# [52, 43, 55, 15, 74, 0, 61, 23, 55, 31, 33, 37, 51, 29, 59],
|
||||
# [72, 43, 23, 72, 23, 61, 0, 42, 23, 31, 77, 37, 51, 46, 33],
|
||||
# [42, 23, 43, 31, 52, 23, 42, 0, 33, 15, 37, 33, 33, 31, 37],
|
||||
# [51, 23, 41, 62, 21, 55, 23, 33, 0, 29, 62, 46, 29, 51, 11],
|
||||
# [55, 31, 29, 42, 46, 31, 31, 15, 29, 0, 51, 21, 41, 23, 37],
|
||||
# [29, 41, 79, 21, 82, 33, 77, 37, 62, 51, 0, 65, 42, 59, 61],
|
||||
# [74, 51, 21, 51, 58, 37, 37, 33, 46, 21, 65, 0, 61, 11, 55],
|
||||
# [23, 11, 64, 51, 46, 51, 51, 33, 29, 41, 42, 61, 0, 62, 23],
|
||||
# [72, 52, 31, 43, 65, 29, 46, 31, 51, 23, 59, 11, 62, 0, 59],
|
||||
# [46, 21, 51, 64, 23, 59, 33, 37, 11, 37, 61, 55, 23, 59, 0],
|
||||
# ]
|
||||
|
||||
|
||||
# # #five_d.txt
|
||||
# dmatrix = [
|
||||
# [0.0, 3.0, 4.0, 2.0, 7.0],
|
||||
# [3.0, 0.0, 4.0, 6.0, 3.0],
|
||||
# [4.0, 4.0, 0.0, 5.0, 8.0],
|
||||
# [2.0, 6.0, 5.0, 0.0, 6.0],
|
||||
# [7.0, 3.0, 8.0, 6.0, 0.0],
|
||||
# ]
|
||||
|
||||
|
||||
# # #gr17_d.txt
|
||||
# dmatrix = [
|
||||
# [ 0, 633, 257, 91, 412, 150, 80, 134, 259, 505, 353, 324, 70, 211, 268, 246, 121],
|
||||
# [ 633, 0, 390, 661, 227, 488, 572, 530, 555, 289, 282, 638, 567, 466, 420, 745, 518],
|
||||
# [ 257, 390, 0, 228, 169, 112, 196, 154, 372, 262, 110, 437, 191, 74, 53, 472, 142],
|
||||
# [ 91, 661, 228, 0, 383, 120, 77, 105, 175, 476, 324, 240, 27, 182, 239, 237, 84],
|
||||
# [ 412, 227, 169, 383, 0, 267, 351, 309, 338, 196, 61, 421, 346, 243, 199, 528, 297],
|
||||
# [ 150, 488, 112, 120, 267, 0, 63, 34, 264, 360, 208, 329, 83, 105, 123, 364, 35],
|
||||
# [ 80, 572, 196, 77, 351, 63, 0, 29, 232, 444, 292, 297, 47, 150, 207, 332, 29],
|
||||
# [ 134, 530, 154, 105, 309, 34, 29, 0, 249, 402, 250, 314, 68, 108, 165, 349, 36],
|
||||
# [ 259, 555, 372, 175, 338, 264, 232, 249, 0, 495, 352, 95, 189, 326, 383, 202, 236],
|
||||
# [ 505, 289, 262, 476, 196, 360, 444, 402, 495, 0, 154, 578, 439, 336, 240, 685, 390],
|
||||
# [ 353, 282, 110, 324, 61, 208, 292, 250, 352, 154, 0, 435, 287, 184, 140, 542, 238],
|
||||
# [ 324, 638, 437, 240, 421, 329, 297, 314, 95, 578, 435, 0, 254, 391, 448, 157, 301],
|
||||
# [ 70, 567, 191, 27, 346, 83, 47, 68, 189, 439, 287, 254, 0, 145, 202, 289, 55],
|
||||
# [ 211, 466, 74, 182, 243, 105, 150, 108, 326, 336, 184, 391, 145, 0, 57, 426, 96],
|
||||
# [ 268, 420, 53, 239, 199, 123, 207, 165, 383, 240, 140, 448, 202, 57, 0, 483, 153],
|
||||
# [ 246, 745, 472, 237, 528, 364, 332, 349, 202, 685, 542, 157, 289, 426, 483, 0, 336],
|
||||
# [ 121, 518, 142, 84, 297, 35, 29, 36, 236, 390, 238, 301, 55, 96, 153, 336, 0],
|
||||
# ]
|
@ -0,0 +1,84 @@
|
||||
from dataset import dmatrix, dmatrixLabel
|
||||
import json
|
||||
|
||||
def floyd(graph):
|
||||
length = len(graph)
|
||||
path = {}
|
||||
|
||||
for i in range(length):
|
||||
path.setdefault(i, {})
|
||||
for j in range(length):
|
||||
if i == j:
|
||||
continue
|
||||
|
||||
path[i].setdefault(j, [i,j])
|
||||
new_node = None
|
||||
|
||||
for k in range(length):
|
||||
if k == j:
|
||||
continue
|
||||
|
||||
new_len = graph[i][k] + graph[k][j]
|
||||
if graph[i][j] > new_len:
|
||||
graph[i][j] = new_len
|
||||
new_node = k
|
||||
if new_node:
|
||||
path[i][j].insert(-1, new_node)
|
||||
|
||||
return graph, path
|
||||
|
||||
def floyd_dict(graph):
|
||||
length = len(graph)
|
||||
path = {}
|
||||
|
||||
for src in graph:
|
||||
path.setdefault(src, {})
|
||||
for dst in graph[src]:
|
||||
if src == dst:
|
||||
continue
|
||||
path[src].setdefault(dst, [src,dst])
|
||||
new_node = None
|
||||
|
||||
for mid in graph:
|
||||
if mid == dst:
|
||||
continue
|
||||
|
||||
new_len = graph[src][mid] + graph[mid][dst]
|
||||
if graph[src][dst] > new_len:
|
||||
graph[src][dst] = new_len
|
||||
new_node = mid
|
||||
if new_node:
|
||||
path[src][dst].insert(-1, new_node)
|
||||
|
||||
return graph, path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ini = float('inf')
|
||||
# graph_list = [ [0, 2, 1, 4, 5, 1],
|
||||
# [1, 0, 4, 2, 3, 4],
|
||||
# [2, 1, 0, 1, 2, 4],
|
||||
# [3, 5, 2, 0, 3, 3],
|
||||
# [2, 4, 3, 4, 0, 1],
|
||||
# [3, 4, 7, 3, 1, 0]]
|
||||
|
||||
# graph_dict = { "s1":{"s1": 0, "s2": 2, "s10": 1, "s12": 4},
|
||||
# "s2":{"s1": 1, "s2": 0, "s10": 4, "s12": 2},
|
||||
# "s10":{"s1": 2, "s2": 1, "s10": 0, "s12":1},
|
||||
# "s12":{"s1": 3, "s2": 5, "s10": 2, "s12":0},
|
||||
# }
|
||||
|
||||
# #new_graph, path= floyd_dict(graph_dict)
|
||||
# new_graph, path = floyd(graph_list)
|
||||
# print(new_graph, '\n\n\n', path)
|
||||
|
||||
|
||||
new_graph, path = floyd(dmatrix)
|
||||
print("Weights: ", new_graph)
|
||||
print("Paths: ", path)
|
||||
|
||||
with open("Weights.json", "w") as fp:
|
||||
json.dump(new_graph, fp)
|
||||
|
||||
with open("Paths.json", "w") as fp:
|
||||
json.dump(path, fp)
|
@ -0,0 +1,2 @@
|
||||
from .database import register_mysql
|
||||
from .setting import setting
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,25 @@
|
||||
from fastapi import FastAPI
|
||||
from tortoise.contrib.fastapi import register_tortoise
|
||||
|
||||
from .setting import setting
|
||||
|
||||
|
||||
def register_mysql(app: FastAPI):
|
||||
register_tortoise(
|
||||
app,
|
||||
config={
|
||||
"connections": {
|
||||
"default": setting.DB_URL
|
||||
},
|
||||
"apps": {
|
||||
"models": {
|
||||
"models": ["aerich.models", "models"],
|
||||
"default_connection": "default"
|
||||
}
|
||||
},
|
||||
'use_tz': False,
|
||||
'timezone': setting.TIMEZONE
|
||||
},
|
||||
generate_schemas=False,
|
||||
add_exception_handlers=False,
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
# import aioredis
|
||||
#
|
||||
# from . import setting
|
||||
#
|
||||
# redis = aioredis.from_url(
|
||||
# setting.REDIS_URL,
|
||||
# encoding='utf-8',
|
||||
# decode_responses=True,
|
||||
# max_connections=5
|
||||
# )
|
Binary file not shown.
@ -0,0 +1,143 @@
|
||||
import sys
|
||||
from helper.SerialPort import *
|
||||
from collections import Counter
|
||||
import time
|
||||
import threading
|
||||
|
||||
from conf import setting
|
||||
|
||||
# 天平收发数据文件
|
||||
|
||||
|
||||
class Balance:
|
||||
lockSend=threading.Lock()
|
||||
def __init__(self):
|
||||
self.ser = SerialPort(setting.BALANCE_SERIAL, 9600)
|
||||
# self.ser = SerialPort('com5', 9600)
|
||||
self.balanceData = 0
|
||||
|
||||
# # 启动服务
|
||||
# def start(self):
|
||||
# self.balanceData = 0
|
||||
# self.taskKillFlag = False
|
||||
# p = threading.Thread(target=self.readDataThread)
|
||||
# p.start()
|
||||
#
|
||||
# # 停止服务
|
||||
# def stop(self):
|
||||
# self.taskKillFlag = True
|
||||
# self.balanceData = 0
|
||||
|
||||
def readDataThread(self):
|
||||
while not (self.taskKillFlag):
|
||||
try:
|
||||
dataStr = self.readRealTimeVal()
|
||||
if (dataStr):
|
||||
if (dataStr[0] == '+'):
|
||||
self.balanceData = float(dataStr[1:])
|
||||
else:
|
||||
self.balanceData = float(dataStr[1:]) * -1
|
||||
else:
|
||||
self.balanceData = -999999
|
||||
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
def getData(self):
|
||||
try:
|
||||
dataStr = self.readRealTimeVal()
|
||||
if (dataStr):
|
||||
if (dataStr[0] == '+'):
|
||||
self.balanceData = float(dataStr[1:])
|
||||
else:
|
||||
self.balanceData = float(dataStr[1:]) * -1
|
||||
else:
|
||||
self.balanceData = -999999
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
self.balanceData = -999999
|
||||
return self.balanceData
|
||||
|
||||
def readRealTimeVal(self):
|
||||
try:
|
||||
data = None
|
||||
content = self.write('001?\r\n')
|
||||
#print('11111')
|
||||
#print(content)
|
||||
if content:
|
||||
list_data = content.split('\r\n')
|
||||
# print(list_data)
|
||||
for i in list_data:
|
||||
if "WT:" in i:
|
||||
data = i
|
||||
break
|
||||
if data:
|
||||
data = data.replace(' ', '')
|
||||
data = data.replace('WT:', '')
|
||||
data = data.replace('g', '')
|
||||
if '-' not in data:
|
||||
data = '+' + data
|
||||
#print(data)
|
||||
return data
|
||||
|
||||
else:
|
||||
return data
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
# 去皮
|
||||
def tare(self):
|
||||
self.write('T\r\n')
|
||||
|
||||
# 归零
|
||||
def zero(self):
|
||||
self.write('Z')
|
||||
|
||||
def write(self, data):
|
||||
try:
|
||||
self.lockSend.acquire()
|
||||
self.ser.Write(data.encode())
|
||||
#print(data)
|
||||
content = bytes.decode(self.ser.Read())
|
||||
# print(content)
|
||||
return content
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
return None
|
||||
finally:
|
||||
self.lockSend.release()
|
||||
|
||||
def read(self):
|
||||
# 过滤天平串口返回
|
||||
content = self.write('001?\r\n')
|
||||
print(content)
|
||||
if content:
|
||||
list_data = content.split('\r\n')
|
||||
# print(list_data)
|
||||
data = None
|
||||
for i in list_data:
|
||||
if "WT:" in i:
|
||||
data = i
|
||||
break
|
||||
if data:
|
||||
data = data.replace(' ', '')
|
||||
data = data.replace('WT:', '')
|
||||
data = data.replace('g', '')
|
||||
if '-' not in data:
|
||||
data = '+' + data
|
||||
print(data)
|
||||
return data
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
balance = Balance()
|
||||
# if __name__ == '__main__':
|
||||
# # balance.write('#')
|
||||
# balance.tare()
|
||||
# while 1:
|
||||
# data = balance.getData()
|
||||
# print(data)
|
||||
# time.sleep(1)
|
||||
# # print(a.readRealTimeVal())
|
@ -0,0 +1,32 @@
|
||||
import socket
|
||||
import binascii
|
||||
|
||||
def convert_to_decimal(hex_str):
|
||||
return int(hex_str, 16)
|
||||
|
||||
def main():
|
||||
TCP_IP = '192.168.0.7'
|
||||
TCP_PORT = 26
|
||||
MESSAGE = bytes.fromhex('01 03 00 00 00 02 C4 0B')
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect((TCP_IP, TCP_PORT))
|
||||
s.send(MESSAGE)
|
||||
data = s.recv(1024)
|
||||
|
||||
hex_data = binascii.hexlify(data).decode('utf-8')
|
||||
print("Received Hex Data:", hex_data)
|
||||
|
||||
# 获取校验位和所需的数据部分
|
||||
# checksum = hex_data[-4:] # 校验位是最后两个字节
|
||||
required_data = hex_data[10:-4] # 数据部分是从索引12开始到校验位之前
|
||||
|
||||
# decimal_checksum = convert_to_decimal(checksum)
|
||||
decimal_required_data = (int)(convert_to_decimal(required_data)/1000)
|
||||
|
||||
# print("Decimal Checksum:", decimal_checksum)
|
||||
print("Decimal Required Data:", decimal_required_data)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -0,0 +1,41 @@
|
||||
import hashlib
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from urllib import parse
|
||||
|
||||
from fastapi import Request, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import JSONResponse
|
||||
from pytz import timezone
|
||||
|
||||
from conf import setting
|
||||
|
||||
|
||||
async def login_required(request: Request):
|
||||
from models import User
|
||||
|
||||
users = request.headers.get('X-TOKEN', '').split(',')
|
||||
# 用户所选柜体
|
||||
client_id = request.headers.get("CLIENT-ID", '')
|
||||
# 大类id
|
||||
archive_id = request.headers.get("ARCHIVE-ID")
|
||||
|
||||
if len(users) == 0:
|
||||
raise HTTPException(status_code=401)
|
||||
current_user_id = users[0]
|
||||
current_user = await User.get_or_none(id=current_user_id)
|
||||
if not current_user:
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
request.state.current_user = current_user
|
||||
request.state.users = users
|
||||
request.state.members = parse.unquote(request.headers.get('X-TOKEN-NAME', ''))
|
||||
request.state.archive_id = archive_id
|
||||
request.state.client_id = client_id
|
||||
|
||||
|
||||
def respond_to(code: int = 200, desc: str = '成功', data=None):
|
||||
if data is None:
|
||||
return JSONResponse(content={"code": code, "desc": desc})
|
||||
else:
|
||||
return JSONResponse(content={"code": code, "desc": desc, "data": jsonable_encoder(data)})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
from models.barcode_builders import BarcodeBuilders
|
||||
|
||||
|
||||
async def get_barcode_index():
|
||||
data = await BarcodeBuilders.get(id=1).values("id", "index")
|
||||
return data.index
|
||||
|
||||
async def update_barcode_index(value):
|
||||
await BarcodeBuilders.filter(id=1).update(index=value)
|
@ -0,0 +1,11 @@
|
||||
class FaceNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
class FaceMoreFoundException(Exception):
|
||||
pass
|
||||
|
||||
class ArcSoftCallException(Exception):
|
||||
pass
|
||||
|
||||
class NonLivingException(Exception):
|
||||
pass
|
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/26 11:05
|
||||
# @Author : tx
|
||||
# @File : __init__.py
|
||||
# @Description :
|
||||
|
||||
from .report_inventory import ReportInventory
|
||||
from .report_drug_logs import ReportDrugUseLogs
|
||||
from .report_use import ReportUse
|
||||
from .report_slack import ReportSlack
|
||||
from .report_put_in import ReportPutIn
|
||||
from .report_remain import ReportRemain
|
||||
from .report_dosage import ReportDosage
|
||||
from .report_put_in1 import ReportPutIn1
|
||||
from .report_reagent_expiration import ReportReagentExpiration
|
||||
from .report_environmental import ReportEnvironmental
|
||||
from .report_take_out import ReportTakeOut
|
||||
from .report_put_in_detail import ReportPutInDetail
|
||||
from .report_take_out_detail import ReportTakeOutDetail
|
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/28 16:06
|
||||
# @Author : tx
|
||||
# @File : report_dosage.py
|
||||
# @Description : 药剂用量
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_dosage_drug
|
||||
|
||||
|
||||
class ReportDosage(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "使用信息汇总"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_dosage_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["cas_code", "total_use_weight", "use_num"]
|
||||
finds_list = ["CAS码", "用量", "使用次数"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/26 11:02
|
||||
# @Author : tx
|
||||
# @File : report_drug_logs.py
|
||||
# @Description :
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from tortoise.queryset import QuerySet
|
||||
|
||||
from conf import setting
|
||||
# from models.drug_use_log import DrugUseLog, DrugUseStateEnum
|
||||
from helper.export.base import ReportExport
|
||||
from helper.drug import milligram_to_gram
|
||||
from helper.report import report_drug_logs
|
||||
|
||||
|
||||
class ReportDrugUseLogs(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "流转日志"
|
||||
self.stateDict = {0: "入库", 1: "归还", 2: "领用", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
"""
|
||||
解析数据
|
||||
:return:
|
||||
"""
|
||||
keyword.page_no = 0
|
||||
result = await report_drug_logs(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["state", "position", "users", "weight", "use_weight", "created_at"]
|
||||
finds_list = ["流转类型", "位置", "用户", "余量", "用量", "创建时间"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/10/25 16:31
|
||||
# @Author : tx
|
||||
# @File : report_environmental.py
|
||||
# @Description : 环境记录报表导出
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_cabinet_environmental
|
||||
|
||||
|
||||
class ReportEnvironmental(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "环境温度记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
keyword = keyword.dict()
|
||||
result = await report_cabinet_environmental(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["cabinet_label", "temperature", "created_at"]
|
||||
finds_list = ["柜体", "温度℃", "记录时间"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/26 11:03
|
||||
# @Author : tx
|
||||
# @File : report_inventory.py
|
||||
# @Description :
|
||||
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_inventory_drug
|
||||
|
||||
|
||||
class ReportInventory(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "库存信息记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_inventory_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["rfid", "cas_code", "batch_no", "manufacturer", "net_weight", "remain_gross_weight", "position", "expired_at", "storage_at", "last_user"]
|
||||
finds_list = ["RFID", "CAS码", "批号", "生产厂家", "净重", "余量", "位置", "有效日期", "入库时间", "最后使用人"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/27 19:13
|
||||
# @Author : tx
|
||||
# @File : report_put_in.py
|
||||
# @Description : 入库统计报表导出
|
||||
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_put_in_drug
|
||||
|
||||
class ReportPutIn(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "入库信息汇总"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_put_in_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key") if result.get("attribute_key") else []
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["cas_code", "count"]
|
||||
finds_list = ["CAS码", "入库量"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/27 19:13
|
||||
# @Author : tx
|
||||
# @File : report_put_in.py
|
||||
# @Description : 入库统计报表导出
|
||||
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_put_in_drug
|
||||
|
||||
class ReportPutIn1(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "入库验收记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_put_in_drug(request, keyword)
|
||||
print("result", result)
|
||||
self.drug_attribute_key_list = result.get("attribute_key") if result.get("attribute_key") else []
|
||||
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["expired_at","manufacturer","distribution","total_count","info","ysr","storage_at","jg"]
|
||||
finds_list = ["有效期","生产厂商","经销单位","数量","验收记录","验收人","日期", "结果"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Created on : 2023/12/24 10:03
|
||||
@Author: bzy
|
||||
@Des:
|
||||
"""
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_put_in_detail_drug
|
||||
|
||||
|
||||
class ReportPutInDetail(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "入库信息记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_put_in_detail_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["rfid", "cas_code", "batch_no", "manufacturer", "net_weight", "remain_gross_weight", "position", "expired_at", "storage_at", "storage_user", "state"]
|
||||
finds_list = ["RFID", "CAS码", "批号", "生产厂家", "净重", "余量", "位置", "有效日期", "入库时间", "入库人", "当前状态"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/10/17 17:04
|
||||
# @Author : tx
|
||||
# @File : report_reagent_expiration.py
|
||||
# @Description : 试剂过期导出
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_expire_drug
|
||||
|
||||
|
||||
class ReportReagentExpiration(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "试剂过期统计"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_expire_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["expired_at", "last_user", "remain_gross_weight", "state", "position"]
|
||||
finds_list = ["过期日期", "最后使用人", "余量", "状态", "位置"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/27 13:13
|
||||
# @Author : tx
|
||||
# @File : report_use.py
|
||||
# @Description : 库存量统计报表导出
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_remain_drug
|
||||
|
||||
|
||||
class ReportRemain(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "库存信息汇总"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_remain_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["cas_code", "count"]
|
||||
finds_list = ["CAS码", "在库数量"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/27 13:58
|
||||
# @Author : tx
|
||||
# @File : report_slack.py
|
||||
# @Description : 呆滞物料报表导出
|
||||
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_slack_drug
|
||||
|
||||
|
||||
class ReportSlack(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "呆滞物料"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
keyword = keyword.dict()
|
||||
print("keyword", keyword)
|
||||
result = await report_slack_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key") if result.get("attribute_key") else []
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["expired_at", "last_use_at", "last_user", "remain_gross_weight", "now_slack_days"]
|
||||
finds_list = ["过期日期", "最后使用时间", "最后使用人", "余量", "呆滞天数"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Created on : 2023/12/23 14:07
|
||||
@Author: bzy
|
||||
@Des:
|
||||
"""
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_take_out_drug
|
||||
|
||||
|
||||
class ReportTakeOut(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "出库信息汇总"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_take_out_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key") if result.get("attribute_key") else []
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["cas_code", "count"]
|
||||
finds_list = ["CAS码", "出库数量"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Created on : 2023/12/24 10:08
|
||||
@Author: bzy
|
||||
@Des:
|
||||
"""
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_take_out_detail_drug
|
||||
|
||||
|
||||
class ReportTakeOutDetail(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "出库信息记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_take_out_detail_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["rfid", "cas_code", "batch_no", "manufacturer", "net_weight", "remain_gross_weight", "receive_user", "receive_position", "receive_at", "expired_at"]
|
||||
finds_list = ["RFID", "CAS码", "批号", "生产厂家", "净重", "余量", "领用人", "领用位置", "领用时间", "有效日期"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/9/27 13:13
|
||||
# @Author : tx
|
||||
# @File : report_use.py
|
||||
# @Description :
|
||||
|
||||
from helper.export.base import ReportExport
|
||||
from helper.report import report_use_drug
|
||||
|
||||
|
||||
class ReportUse(ReportExport):
|
||||
def __init__(self, request, keyword):
|
||||
super().__init__()
|
||||
self.title = "使用明细记录"
|
||||
self.stateDict = {0: "待入库", 1: "在库", 2: "出库", 3: "空瓶", 4: "报废"}
|
||||
self.request = request
|
||||
self.keyword = keyword
|
||||
|
||||
async def parse_data(self, request, keyword):
|
||||
keyword.page_no = 0
|
||||
result = await report_use_drug(request, keyword)
|
||||
self.drug_attribute_key_list = result.get("attribute_key")
|
||||
return result.get("data")
|
||||
|
||||
def parse_key_finds_list(self):
|
||||
"""
|
||||
解析键列名
|
||||
:return:
|
||||
"""
|
||||
key_list = ["rfid", "cas_code", "batch_no", "manufacturer", "net_weight", "use_weight", "receive_users", "receive_position", "receive_at"]
|
||||
finds_list = ["RFID", "CAS码", "批号", "生产厂家", "净重", "用量", "操作人员", "位置", "操作时间"]
|
||||
self.key_list = self.drug_attribute_key_list + key_list
|
||||
self.finds_list = self.drug_attribute_key_list + finds_list
|
||||
|
||||
async def main(self):
|
||||
"""
|
||||
主函数
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
self.data_list = await self.parse_data(self.request, self.keyword)
|
||||
self.parse_key_finds_list()
|
@ -0,0 +1,98 @@
|
||||
import cv2
|
||||
from arcsoft.engine import *
|
||||
from .exception import FaceNotFoundException, FaceMoreFoundException, ArcSoftCallException, NonLivingException
|
||||
from conf import setting
|
||||
|
||||
class FaceCameraEngine:
|
||||
def __init__(self):
|
||||
self.app_id = bytes(setting.ARCSOFT_APP_ID, 'utf-8')
|
||||
self.sdk_key = bytes(setting.ARCSOFT_SDK_KEY, 'utf-8')
|
||||
|
||||
def __enter__(self):
|
||||
face_engine = ArcFace()
|
||||
face_engine.ASFInitEngine(ASF_DETECT_MODE_IMAGE, ASF_OP_0_ONLY, 30, 5, ASF_FACE_DETECT | ASF_FACERECOGNITION | ASF_LIVENESS)
|
||||
self.face_engine = face_engine
|
||||
return self
|
||||
|
||||
# 虹软库激活
|
||||
def activation(self) -> bool:
|
||||
ack = ASFOnlineActivation(self.app_id, self.sdk_key)
|
||||
return (MOK == ack or MERR_ASF_ALREADY_ACTIVATED == ack)
|
||||
|
||||
# 查找相似特征人员
|
||||
def match(self, frame, features: dict) -> str:
|
||||
frame = self.do_resize(frame)
|
||||
|
||||
ack, detect_faces = self.face_engine.ASFDetectFaces(frame)
|
||||
if MOK != ack:
|
||||
raise ArcSoftCallException()
|
||||
|
||||
if detect_faces.faceNum == 0:
|
||||
raise FaceNotFoundException()
|
||||
|
||||
if detect_faces.faceNum > 1:
|
||||
raise FaceMoreFoundException()
|
||||
|
||||
ack = self.face_engine.ASFProcess(frame, detect_faces, ASF_LIVENESS)
|
||||
if MOK != ack:
|
||||
raise ArcSoftCallException()
|
||||
|
||||
ack, liveness = self.face_engine.ASFGetLivenessScore()
|
||||
if MOK != ack:
|
||||
raise ArcSoftCallException()
|
||||
|
||||
# TODO: 是否活体(还需进一步测试)
|
||||
if liveness.isLive[0] != 1:
|
||||
raise NonLivingException()
|
||||
|
||||
# 特征值
|
||||
single_face_info = ASF_SingleFaceInfo()
|
||||
single_face_info.faceRect = detect_faces.faceRect[0]
|
||||
single_face_info.faceOrient = detect_faces.faceOrient[0]
|
||||
ack, feature = self.face_engine.ASFFaceFeatureExtract(frame, single_face_info)
|
||||
if MOK != ack:
|
||||
raise ArcSoftCallException()
|
||||
|
||||
for (key, value) in features.items():
|
||||
ack, score = self.face_engine.ASFFaceFeatureCompare(feature, value)
|
||||
if MOK != ack:
|
||||
continue
|
||||
if score >= setting.FACE_RECOGNITION_THRESHOLD:
|
||||
return key
|
||||
|
||||
# 捕获图像人脸特征值
|
||||
def capture(self, frame) -> List[bytes]:
|
||||
frame = self.do_resize(frame)
|
||||
|
||||
ack, detect_faces = self.face_engine.ASFDetectFaces(frame)
|
||||
if MOK != ack:
|
||||
raise ArcSoftCallException()
|
||||
|
||||
data = []
|
||||
|
||||
for i in range(0, detect_faces.faceNum):
|
||||
single_face_info = ASF_SingleFaceInfo()
|
||||
single_face_info.faceRect = detect_faces.faceRect[i]
|
||||
single_face_info.faceOrient = detect_faces.faceOrient[i]
|
||||
ack, feature = self.face_engine.ASFFaceFeatureExtract(frame, single_face_info)
|
||||
if MOK == ack:
|
||||
data.append(feature.get_feature_bytes())
|
||||
|
||||
return data if len(data) > 0 else None
|
||||
|
||||
# 二进制转特征值
|
||||
@staticmethod
|
||||
def b2f(bin: bytes) -> ASF_FaceFeature:
|
||||
face_feature = ASF_FaceFeature()
|
||||
face_feature.featureSize = bin.__len__()
|
||||
face_feature.feature = lib_func.malloc(face_feature.featureSize)
|
||||
lib_func.memcpy(face_feature.feature, bin, face_feature.featureSize)
|
||||
return face_feature
|
||||
|
||||
def __exit__(self, type, value, trace):
|
||||
self.face_engine.ASFUninitEngine()
|
||||
|
||||
# 处理图片宽域
|
||||
def do_resize(self, frame):
|
||||
sp = frame.shape
|
||||
return cv2.resize(frame, (sp[1] // 4 * 4, sp[0]))
|
@ -0,0 +1,7 @@
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
def genRandomStr(instance, N = 4):
|
||||
res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N))
|
||||
return res
|
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: tx
|
||||
@file: log.py
|
||||
@time: 2023/6/6 17:03
|
||||
@desc:
|
||||
"""
|
||||
import json
|
||||
|
||||
from fastapi import Request
|
||||
from functools import wraps
|
||||
import inspect
|
||||
|
||||
from helper.logger import logger
|
||||
from models.log import Log
|
||||
|
||||
def _parse_func_doc(info):
|
||||
delimiter = ":param" if ":param" in info else ":return"
|
||||
result = info.split(delimiter)[0].strip()
|
||||
return result
|
||||
|
||||
def logger_wrapper(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
request = kwargs.get("request")
|
||||
if not request or not hasattr(request.state, "current_user"):
|
||||
interface_user_name, interface_user_id = "", None
|
||||
else:
|
||||
current_user = request.state.current_user
|
||||
interface_user_name, interface_user_id = current_user.name, current_user.id
|
||||
|
||||
interface_desc = _parse_func_doc(func.__doc__)
|
||||
|
||||
response = await func(*args, **kwargs)
|
||||
response_data = json.loads(response.body)
|
||||
|
||||
response_msg = response_data.get("desc")
|
||||
record = {
|
||||
"user_id": interface_user_id,
|
||||
"users": interface_user_name,
|
||||
"kind": interface_desc,
|
||||
"comment": f"访问:{interface_desc}, 返回信息:{response_msg}"
|
||||
}
|
||||
await record_log(**record)
|
||||
|
||||
return response
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
async def record_log(**kwargs):
|
||||
await Log.create(**kwargs)
|
||||
|
||||
logger.info(f"{kwargs.get('users')} {kwargs.get('comment')}")
|
||||
|
@ -0,0 +1,366 @@
|
||||
""" 日志指定目录扫描以及切割, 有效文件名格式如下:
|
||||
包含一个.的日志文件: [^.]+.log
|
||||
其他忽略
|
||||
"""
|
||||
|
||||
import os
|
||||
import fnmatch
|
||||
import shutil
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
import warnings
|
||||
import requests
|
||||
|
||||
from logging import FileHandler, StreamHandler
|
||||
from conf import setting
|
||||
|
||||
from .common import Singleton, str2int
|
||||
|
||||
|
||||
MAXSIZE = 200 * 1024 * 1024
|
||||
|
||||
|
||||
def check_and_rotate_logfile(dirname, logger, maxsize=10 * 1024 * 1024, maxtime=30 * 24 * 3600):
|
||||
"""每周定时切换日志
|
||||
NOTE: 如果日志文件大小小于10MB, 则不进行切换操作
|
||||
"""
|
||||
if not os.path.exists(dirname):
|
||||
logger.error(f'日志检查: 日志目录({dirname})不存在, 请检查')
|
||||
return
|
||||
|
||||
# 1. 切割日志
|
||||
now = datetime.datetime.now()
|
||||
date_str = '{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}'.format(now.year, now.month, now.day, now.hour, now.minute, now.second)
|
||||
for filename in os.listdir(dirname):
|
||||
# a. 拷贝
|
||||
filepath = dirname + os.sep + filename
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
if filename.count('.') > 1:
|
||||
continue
|
||||
if not fnmatch.fnmatchcase(filename, '*.log'):
|
||||
continue
|
||||
filesize = os.path.getsize(filepath)
|
||||
if filesize <= maxsize: # 10MB以下文件不切割
|
||||
continue
|
||||
new_filepath = dirname + os.sep + filename.rsplit('.log', 1)[0] + '.' + date_str + '.log'
|
||||
shutil.copyfile(filepath, new_filepath)
|
||||
# b. 清空
|
||||
with open(filepath, 'r+', encoding='utf8') as fd:
|
||||
fd.seek(0)
|
||||
fd.truncate()
|
||||
|
||||
# 2. 删除老日志
|
||||
bretime = time.time() - maxtime # 30天前
|
||||
for filename in os.listdir(dirname):
|
||||
filepath = dirname + os.sep + filename
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
if fnmatch.fnmatchcase(filename, '*.tar.gz'): # 清理压缩包
|
||||
os.remove(filepath)
|
||||
continue
|
||||
if not fnmatch.fnmatchcase(filename, '*.log'):
|
||||
continue
|
||||
if filename.count('.') <= 1:
|
||||
continue
|
||||
try:
|
||||
if os.path.getmtime(filepath) <= bretime:
|
||||
os.remove(filepath)
|
||||
logger.exception(f'日志检查: 删除日志{filepath}成功')
|
||||
except Exception as e:
|
||||
logger.exception(f'日志检查: 删除日志{filepath}失败, msg: {e}')
|
||||
|
||||
|
||||
def join_multiple_files(logger, filepaths, destfilepath, needsize, date_str):
|
||||
"""join_multiple_files: 从filepaths中提取needsize大小的数据并append到destfilepath中
|
||||
|
||||
:param filepaths: 按照时间从老到新的有序文件路径列表
|
||||
:param destfilepath: 目标文件
|
||||
:param needsize: 需要提取的数据大小
|
||||
:param date_str: 时间字符串
|
||||
"""
|
||||
_min_size = 1024 * 1024
|
||||
for filepath in reversed(filepaths):
|
||||
# 获取源文件(截取拷贝等操作)
|
||||
need_delete = False
|
||||
filesize = os.path.getsize(filepath)
|
||||
if filesize == needsize:
|
||||
_tmp_filepath = filepath
|
||||
needsize = 0
|
||||
elif filesize < needsize:
|
||||
_tmp_filepath = filepath
|
||||
needsize = int((needsize - filesize) / _min_size)
|
||||
else:
|
||||
_tmp_filepath = filepath + f'.tmp{date_str}'
|
||||
skip_block = int((filesize - needsize) / _min_size) # 跳过块, 没块大小1MB
|
||||
cmd = f'dd if={filepath} of={_tmp_filepath} skip={skip_block} ibs={_min_size} obs={_min_size}'
|
||||
needsize = 0
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
cmd = f'dd if={filepath} of={_tmp_filepath} skip={skip_block} ibs=1M obs=1M'
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
msg = f'执行命令:{cmd}失败, code: {val}'
|
||||
logger.error(msg)
|
||||
return False, msg
|
||||
need_delete = True
|
||||
|
||||
# 拼接和删除临时文件
|
||||
try:
|
||||
cmd = f'cat {_tmp_filepath} >> {destfilepath}'
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
msg = f'执行命令:{cmd}失败, code: {val}'
|
||||
logger.error(msg)
|
||||
return False, msg
|
||||
finally:
|
||||
if need_delete:
|
||||
os.remove(_tmp_filepath)
|
||||
|
||||
# 判断needsize大小
|
||||
logger.info(f'成功拷贝文件:{filepath}中的内容到:{destfilepath}中')
|
||||
if needsize <= 0:
|
||||
break
|
||||
|
||||
return True, destfilepath
|
||||
|
||||
|
||||
def zip_and_transform_logfile(dirname, logger, fileprefix='yanei', minsize=MAXSIZE, tarfile=None):
|
||||
"""zip_and_transform_logfile, 打包获取日志文件中最近20MB(传入参数)的数据并返回
|
||||
|
||||
:param dirname: 日志目录
|
||||
:param logger: 日志记录对象
|
||||
:param fileprefix: 日志文件前缀
|
||||
:param minsize: 截取的日志文件大小
|
||||
:param tarfile: 如果该值存在, 则会
|
||||
|
||||
NOTE: 该函数仅仅适用于Linux系统
|
||||
"""
|
||||
if not os.path.exists(dirname):
|
||||
msg = f'日志检查: 日志目录({dirname})不存在, 请检查'
|
||||
logger.error(msg)
|
||||
return False, msg
|
||||
|
||||
# 获取当前所有server.log日志以及自动备份的日志: yanei.20211026160000.log
|
||||
can_cutout_filepaths = []
|
||||
current_filename = f'{fileprefix}.log' # 当前正在记录日志的文件
|
||||
current_filepath = dirname + os.sep + current_filename
|
||||
for filename in os.listdir(dirname):
|
||||
filepath = dirname + os.sep + filename
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
if fnmatch.fnmatchcase(filename, '*.tar.gz'):
|
||||
continue
|
||||
if not fnmatch.fnmatchcase(filename, f'{fileprefix}*'):
|
||||
continue
|
||||
if filename == current_filename:
|
||||
continue
|
||||
can_cutout_filepaths.append(filepath)
|
||||
_can_cutout_filepaths = []
|
||||
|
||||
for filepath in can_cutout_filepaths:
|
||||
try:
|
||||
x = str2int(filepath.split('.')[1:2][0])
|
||||
if not isinstance(x, int):
|
||||
api_logger.error(f'发现一个未知格式的文件:{filepath}, 忽略该文件')
|
||||
continue
|
||||
except Exception as msg:
|
||||
api_logger.error(f'发现一个未知格式的文件:{filepath}, 忽略该文件')
|
||||
else:
|
||||
_can_cutout_filepaths.append(filepath)
|
||||
can_cutout_filepaths = _can_cutout_filepaths
|
||||
can_cutout_filepaths = sorted(can_cutout_filepaths, key=lambda x: str2int(x.split('.')[1:2][0]))
|
||||
|
||||
# 截取并拼接文件
|
||||
current_filesize = os.path.getsize(current_filepath)
|
||||
_min_size = 1024 * 1024
|
||||
now = datetime.datetime.now()
|
||||
date_str = '{:04d}{:02d}{:02d}{:02d}{:02d}{:02d}'.format(now.year, now.month, now.day, now.hour, now.minute, now.second)
|
||||
save_filename = f'server.log.tmp.{date_str}'
|
||||
save_filepath = dirname + os.sep + save_filename
|
||||
if current_filesize > minsize:
|
||||
skip_block = int((current_filesize - minsize) / _min_size) # 跳过块, 没块大小1MB
|
||||
cmd = f'dd if={current_filepath} of={save_filepath} skip={skip_block} ibs={_min_size} obs={_min_size}'
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
cmd = f'dd if={current_filepath} of={save_filepath} skip={skip_block} ibs=1M obs=1M'
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
msg = f'执行命令:{cmd}失败, code: {val}'
|
||||
logger.error(msg)
|
||||
return False, msg
|
||||
logger.info(f'日志文件足够大, 截取:{current_filepath}到{save_filepath}中成功')
|
||||
elif current_filesize == minsize:
|
||||
save_filepath = shutil.copyfile(current_filepath, save_filepath)
|
||||
logger.info(f'日志文件大小刚好, 拷贝:{current_filepath}到{save_filepath}中成功')
|
||||
else:
|
||||
save_filepath = shutil.copyfile(current_filepath, save_filepath)
|
||||
logger.info(f'日志文件大小较小, 先拷贝:{current_filepath}到{save_filepath}中成功')
|
||||
needsize = int((minsize - current_filesize) / _min_size)
|
||||
succ, msg = join_multiple_files(logger, can_cutout_filepaths, save_filepath, needsize, date_str)
|
||||
if not succ:
|
||||
return False, msg
|
||||
|
||||
# 打包, 其中压缩包文件会在定时任务函数中清理
|
||||
tar_filename = f'YANEILog_.{date_str}.tar.gz' if not tarfile else tarfile
|
||||
tar_filepath = [dirname, tar_filename]
|
||||
try:
|
||||
cmd = f'cd {dirname} && tar zcf {tar_filename} {save_filename}'
|
||||
val = os.system(cmd)
|
||||
if val:
|
||||
msg = f'执行命令:{cmd}失败, code: {val}'
|
||||
logger.error(msg)
|
||||
return False, msg
|
||||
finally:
|
||||
os.remove(save_filepath)
|
||||
|
||||
return True, tar_filepath
|
||||
|
||||
|
||||
class YaneFilter(logging.Filter):
|
||||
"""自定义logging过滤器, 通过redis或者mysql获取当前记录的最新日志level, 根据
|
||||
该level决定是否记录某一条日志
|
||||
"""
|
||||
|
||||
def filter(self, record):
|
||||
return super(YaneFilter, self).filter(record)
|
||||
|
||||
|
||||
class YaneiErrorFilter(logging.Filter):
|
||||
"""自由控制: 记录error以上的日志"""
|
||||
|
||||
def filter(self, record):
|
||||
try:
|
||||
return record.levelno >= logging.ERROR
|
||||
except Exception:
|
||||
return super(YaneiErrorFilter, self).filter(record)
|
||||
|
||||
|
||||
@Singleton
|
||||
class YaneiLoggerGenerator:
|
||||
DEFAULT_LOG_FILE = 'server.log' # 默认日志存储文件
|
||||
DEFAULT_ERROR_LOG_FILE = 'error.log'
|
||||
|
||||
""" 日志logger生成工具
|
||||
NOTE: 非多进程安全, 并发量高的情况下可能造成日志丢失
|
||||
|
||||
@默认格式: 日志等级 时间 日志所在文件名:日志所在行号 - 固定前缀 - 日志信息
|
||||
@生成logger或获取某一个logger:
|
||||
from app.common.log import g_logger_generator
|
||||
|
||||
# 表示获取api日志对象, 日志会记录到api.log, 固定前缀为API
|
||||
app.apiLogger = g_logger_generator.get_logger('api', 'api.log', prefix='API')
|
||||
|
||||
# 表示获取切换日志对象, 日志会记录到默认文件server.log, 固定前缀为SWITCH
|
||||
app.celeryLogger = g_logger_generator.get_logger('switch', prefix='SWITCH')
|
||||
|
||||
# 表示获取文件同步日志对象, 日志会记录到默认文件server.log, 固定前缀为DELAY
|
||||
app.delayLogger = g_logger_generator.get_logger('delay', prefix='DELAY')
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._loggers = {} # 当前进程中logger集: name -> logger
|
||||
self.common_fmt_prefix = '%(levelname)s %(process)d:%(thread)d %(asctime)s %(filename)s:%(lineno)s'
|
||||
|
||||
# 关闭requests, urllib3告警日志
|
||||
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
||||
warnings.simplefilter('ignore', ResourceWarning)
|
||||
|
||||
def _formatter(self, fmt, prefix):
|
||||
return logging.Formatter(fmt + f' - {prefix} - ' + '%(message)s')
|
||||
|
||||
def update_file_mode(self, filepath, mode=0o666):
|
||||
if os.path.exists(filepath):
|
||||
status = os.stat(filepath)
|
||||
if (status.st_mode & mode) == mode:
|
||||
return
|
||||
|
||||
old_umask = os.umask(0)
|
||||
with open(filepath, 'w', encoding='utf8') as fd:
|
||||
fd.write('.')
|
||||
os.chmod(filepath, mode)
|
||||
os.umask(old_umask)
|
||||
|
||||
def get_logger(self, name, filename=None, level=logging.INFO, prefix='COMMON', stream=True):
|
||||
"""get_logger, 获取name指定的日志句柄对象
|
||||
|
||||
:param name:logger名称
|
||||
:param filename:日志存储的文件名
|
||||
:param level:日志记录等级
|
||||
:param prefix:日志记录前缀, 用以区分同一个日志文件中的不同日志信息
|
||||
:param stream:是否日志打印到stdout, 其中celery异步任务中的日志会自动重定向到标准输出中, 不需要重新设置
|
||||
如果本地开启调试, 则可以传递该值为True, 确保在本地开发时将日志打印到stdout
|
||||
|
||||
@return: 返回一个{name}Logger的logger对象
|
||||
"""
|
||||
|
||||
if name in self._loggers:
|
||||
return self._loggers[name]
|
||||
|
||||
if not os.path.exists('logs'):
|
||||
os.makedirs('logs')
|
||||
filename = self.DEFAULT_LOG_FILE if not filename else filename
|
||||
|
||||
my_formatter = self._formatter(self.common_fmt_prefix, prefix)
|
||||
streamHandler = StreamHandler()
|
||||
streamHandler.setFormatter(my_formatter)
|
||||
_filename = f'logs{os.sep}{filename}'
|
||||
self.update_file_mode(os.path.join(setting.BASE_DIR, _filename))
|
||||
|
||||
logHandler = FileHandler(_filename)
|
||||
logHandler.suffix = '%Y-%m-%d.%M-%S.log'
|
||||
logHandler.setFormatter(my_formatter)
|
||||
|
||||
my_logger = logging.getLogger(name)
|
||||
if not my_logger.handlers:
|
||||
my_logger.addHandler(logHandler)
|
||||
my_logger.setLevel(level)
|
||||
if stream:
|
||||
my_logger.addHandler(streamHandler)
|
||||
|
||||
self._loggers[name] = my_logger
|
||||
return my_logger
|
||||
|
||||
def update_default_logger(self, logger, filename=None, level=logging.INFO, prefix='COMMON', clear=False, stream=True):
|
||||
"""更新某一个logger的日志配置, 参数参考get_logger"""
|
||||
if logger.name not in self._loggers:
|
||||
self._loggers[logger.name] = logger
|
||||
|
||||
filename = self.DEFAULT_LOG_FILE if not filename else filename
|
||||
|
||||
# NOTE: 删除logger默认的handler, 如果需要则删除下面功能
|
||||
if clear:
|
||||
for old_handler in logger.handlers:
|
||||
logger.removeHandler(old_handler)
|
||||
|
||||
my_formatter = self._formatter(self.common_fmt_prefix, prefix)
|
||||
streamHandler = StreamHandler()
|
||||
streamHandler.setFormatter(my_formatter)
|
||||
if stream:
|
||||
logger.addHandler(streamHandler)
|
||||
|
||||
_filename = f'logs{os.sep}{filename}'
|
||||
self.update_file_mode(os.path.join(setting.BASE_DIR, _filename))
|
||||
logHandler = FileHandler(_filename)
|
||||
logHandler.suffix = '%Y-%m-%d.%M-%S.log'
|
||||
logHandler.setFormatter(my_formatter)
|
||||
logger.addHandler(logHandler)
|
||||
|
||||
# 增加一个单独记录错误日志的handler(不需要重定向到标准输出)
|
||||
_filename = f'logs{os.sep}{self.DEFAULT_ERROR_LOG_FILE}'
|
||||
self.update_file_mode(os.path.join(setting.BASE_DIR, _filename))
|
||||
errorhandler = FileHandler(_filename)
|
||||
errorhandler.suffix = '%Y-%m-%d.%M-%S.log'
|
||||
errorhandler.setFormatter(my_formatter)
|
||||
errorhandler.addFilter(YaneiErrorFilter())
|
||||
logger.addHandler(errorhandler)
|
||||
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
__g_logger_generator = YaneiLoggerGenerator()
|
||||
api_logger = __g_logger_generator.get_logger('api', filename='api.log', level=setting.YANEI_LOG_LEVEL, prefix='API', stream=True)
|
||||
logger = __g_logger_generator.get_logger('server', level=setting.YANEI_LOG_LEVEL, prefix='SERVER', stream=True)
|
@ -0,0 +1,8 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class Mask(IntEnum):
|
||||
READ = 1
|
||||
CREATE = 3
|
||||
UPDATE = 5
|
||||
DELETE = 9
|
||||
CRUD = 15
|
@ -0,0 +1,9 @@
|
||||
from enum import Enum
|
||||
from collections import namedtuple
|
||||
|
||||
ModuleItem = namedtuple('ModuleItem', ('id', 'name', 'rank', 'options'))
|
||||
|
||||
class Module(Enum):
|
||||
USER = ModuleItem(1001, '用户管理', 1, {'下载': 1, '编辑': 9})
|
||||
ROLE = ModuleItem(1002, '角色管理', 2, {})
|
||||
TEMPLATE = ModuleItem(1003, '模板管理', 3, {})
|
Binary file not shown.
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
@author: tx
|
||||
@file: tool.py
|
||||
@time: 2023/7/4 19:37
|
||||
@desc:
|
||||
"""
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import pygame
|
||||
|
||||
|
||||
def parse_datetime(time_str, trans_str='%Y-%m-%d %H:%M:%S'):
|
||||
"""
|
||||
时间日期格式转化
|
||||
:param time_str:
|
||||
:param trans_str: 转义输出格式
|
||||
:return:
|
||||
"""
|
||||
possible_formats = [
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y/%m/%d %H:%M:%S",
|
||||
"%Y-%m-%d",
|
||||
"%Y/%m/%d",
|
||||
"%d-%m-%Y %H:%M:%S",
|
||||
"%d/%m/%Y %H:%M:%S",
|
||||
"%d-%m-%Y",
|
||||
"%d/%m/%Y",
|
||||
"%Y年%m月%d日",
|
||||
"%Y年%m月%d日 %H时%M分",
|
||||
"%Y年%m月%d日 %H时%M分%S秒",
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"%Y-%m-%dT%H:%M:%S.%f%z",
|
||||
"%Y-%m-%dT%H:%M:%S%z",
|
||||
"%Y-%m-%d %H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S.%f%z",
|
||||
]
|
||||
|
||||
datetime_list = []
|
||||
for format_str in possible_formats:
|
||||
try:
|
||||
dt = datetime.strptime(time_str, format_str)
|
||||
datetime_list.append(dt)
|
||||
except ValueError:
|
||||
pass
|
||||
if datetime_list:
|
||||
result = datetime_list[0].strftime(trans_str)
|
||||
return result
|
||||
else:
|
||||
return time_str
|
||||
|
||||
|
||||
def get_ip_address():
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 1))
|
||||
ip = s.getsockname()[0]
|
||||
finally:
|
||||
s.close()
|
||||
return ip
|
||||
|
||||
def play_sound(sound_name):
|
||||
"""
|
||||
播放音频文件
|
||||
:param sound_name:
|
||||
:return:
|
||||
"""
|
||||
pygame.mixer.init(frequency=15500, size=-16, channels=4)
|
||||
if os.path.exists(os.path.join("static", "sounds", sound_name)):
|
||||
track2 = pygame.mixer.Sound(os.path.join(os.getcwd(), "static", "sounds", sound_name))
|
||||
track2.play()
|
||||
time.sleep(2.5)
|
@ -0,0 +1,12 @@
|
||||
import random
|
||||
import string
|
||||
|
||||
def genRandomStr(N = 7):
|
||||
res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N))
|
||||
return res
|
||||
|
||||
|
||||
async def genNum(instance):
|
||||
instance.num = genRandomStr(4)
|
||||
instance.tag = "123"
|
||||
await instance.save()
|
@ -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,83 @@
|
||||
import asyncio
|
||||
import time
|
||||
import uuid
|
||||
from typing import List, Dict
|
||||
|
||||
from fastapi.websockets import WebSocket
|
||||
from starlette.websockets import WebSocketState
|
||||
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
self.unacknowledged_messages: Dict[str, Dict[str, any]] = {}
|
||||
async def connect(self, websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
if websocket in self.active_connections:
|
||||
self.active_connections.remove(websocket)
|
||||
|
||||
async def broadcast_stream(self, data: bytes):
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
await connection.send_bytes(data)
|
||||
except (ConnectionClosedError, ConnectionClosedOK):
|
||||
self.disconnect(connection)
|
||||
|
||||
async def broadcast_json(self, event: str, data: str):
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
await connection.send_json({"event": event, "data": data})
|
||||
except (ConnectionClosedError, ConnectionClosedOK):
|
||||
self.disconnect(connection)
|
||||
|
||||
async def send_message(self, event: str, data: str, message_id:str = None):
|
||||
if message_id is None:
|
||||
message_id = str(uuid.uuid4())
|
||||
try:
|
||||
for connection in self.active_connections:
|
||||
if connection.client_state == WebSocketState.CONNECTED:
|
||||
await connection.send_json({"event": event, "data": data, "message_id": message_id})
|
||||
if message_id not in self.unacknowledged_messages:
|
||||
self.unacknowledged_messages[message_id] = {
|
||||
"message": {"event": event, "data": data, "message_id": message_id},
|
||||
"timestamp": time.time(),
|
||||
"websocket": connection
|
||||
}
|
||||
except (ConnectionClosedError, ConnectionClosedOK):
|
||||
self.disconnect(connection)
|
||||
|
||||
async def handle_acknowledgment(self, message_id: str):
|
||||
if message_id in self.unacknowledged_messages:
|
||||
del self.unacknowledged_messages[message_id]
|
||||
|
||||
async def check_unacknowledged_messages(self):
|
||||
while True:
|
||||
for message_id, message_info in list(self.unacknowledged_messages.items()):
|
||||
current_time = time.time()
|
||||
if current_time - message_info["timestamp"] > 5: # 设置超时时间为5秒
|
||||
await self.send_message(
|
||||
message_info["message"]["event"],
|
||||
message_info["message"]["data"],
|
||||
message_info["message"]["message_id"]
|
||||
)
|
||||
await asyncio.sleep(3) # 等待一段时间再次检查
|
||||
|
||||
async def handle_message(self, message:str):
|
||||
message_id = message
|
||||
if message_id is not None:
|
||||
await self.handle_acknowledgment(message_id)
|
||||
async def start_message_check(self):
|
||||
self.message_check_task = asyncio.create_task(self.check_unacknowledged_messages())
|
||||
|
||||
async def stop_message_check(self):
|
||||
if hasattr(self, 'message_check_task'):
|
||||
self.message_check_task.cancel()
|
||||
try:
|
||||
await self.message_check_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
manager = ConnectionManager()
|
@ -0,0 +1,157 @@
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Inches
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH,WD_UNDERLINE
|
||||
from docx import Document
|
||||
from docx.shared import Inches
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
import time
|
||||
from io import BytesIO
|
||||
import os
|
||||
|
||||
#设置表格的边框
|
||||
def set_cell_border(cell, **kwargs):
|
||||
"""
|
||||
Set cell`s border
|
||||
Usage:
|
||||
set_cell_border(
|
||||
cell,
|
||||
top={"sz": 12, "val": "single", "color": "#FF0000", "space": "0"},
|
||||
bottom={"sz": 12, "color": "#00FF00", "val": "single"},
|
||||
left={"sz": 24, "val": "dashed", "shadow": "true"},
|
||||
right={"sz": 12, "val": "dashed"},
|
||||
)
|
||||
"""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
|
||||
# check for tag existnace, if none found, then create one
|
||||
tcBorders = tcPr.first_child_found_in("w:tcBorders")
|
||||
if tcBorders is None:
|
||||
tcBorders = OxmlElement('w:tcBorders')
|
||||
tcPr.append(tcBorders)
|
||||
|
||||
# list over all available tags
|
||||
for edge in ('left', 'top', 'right', 'bottom', 'insideH', 'insideV'):
|
||||
edge_data = kwargs.get(edge)
|
||||
if edge_data:
|
||||
tag = 'w:{}'.format(edge)
|
||||
|
||||
# check for tag existnace, if none found, then create one
|
||||
element = tcBorders.find(qn(tag))
|
||||
if element is None:
|
||||
element = OxmlElement(tag)
|
||||
tcBorders.append(element)
|
||||
|
||||
# looks like order of attributes is important
|
||||
for key in ["sz", "val", "color", "space", "shadow"]:
|
||||
if key in edge_data:
|
||||
element.set(qn('w:{}'.format(key)), str(edge_data[key]))
|
||||
|
||||
class WriteDoc(object):
|
||||
|
||||
def __init__(self, data, path):
|
||||
self.data = data
|
||||
self.path = path
|
||||
self.d = Document()
|
||||
section = self.d.sections[0]
|
||||
header = section.header
|
||||
paragraph = header.paragraphs[0]
|
||||
paragraph.paragraph_format.left_indent = 0
|
||||
paragraph.paragraph_format.right_indent = 0
|
||||
now_date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
text=paragraph.add_run('记录格式编号 : 实施日期 :{} 版本号/修改号 :'.format(now_date))
|
||||
text.font.size = Pt(6)
|
||||
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
# paragraph.runs[-1].underline = WD_UNDERLINE.THICK#页眉下划线
|
||||
|
||||
|
||||
#添加列表
|
||||
def add_table(self):
|
||||
for i in range(len(self.data)):
|
||||
table = self.d.add_table(rows=11, cols=2, style="Table Grid")
|
||||
self.merge_table(table)
|
||||
self.write_data(table,i)
|
||||
self.d.add_paragraph("").paragraph_format.space_after = Pt(25)
|
||||
return self
|
||||
|
||||
|
||||
#写入数据
|
||||
def write_data(self,table,index):
|
||||
# for i in range(len(self.data)):
|
||||
# table =Document.tables[i]
|
||||
i=self.data[index]
|
||||
try:
|
||||
cell = table.cell(0, 0)
|
||||
cell.text = "标准储备液名称:{}".format(i.name)
|
||||
cell = table.cell(0, 1)
|
||||
cell.text = "编号:{}".format(i.seq_no)
|
||||
cell = table.cell(1, 0)
|
||||
cell.text = "溶剂:{}".format(i.solvent)
|
||||
cell = table.cell(2, 0)
|
||||
cell.text = "配制日期:{}".format(i.configuration_date)
|
||||
cell = table.cell(2, 1)
|
||||
cell.text = "有效期:{}".format(i.validity_date)
|
||||
cell = table.cell(3, 0)
|
||||
cell.text = "配制浓度:{}".format(i.config_concentration)
|
||||
cell = table.cell(3, 1)
|
||||
cell.text = "配制量:{}".format(i.allocation)
|
||||
cell = table.cell(4, 0)
|
||||
cell.text = "配制依据:{}".format(i.configuration_basis)
|
||||
cell = table.cell(5, 0)
|
||||
cell.text = "单位编号:{}".format(i.barcode_list)
|
||||
cell = table.cell(6, 0)
|
||||
cell.text = "配制记录:{}".format(i.configuration_record)
|
||||
cell = table.cell(10, 0)
|
||||
set_cell_border(
|
||||
cell,
|
||||
top={"sz": 12, "val": "single", "color": "white", "space": "0"},
|
||||
right={"sz": 12, "val": "single", "color": "white", "space": "0"},
|
||||
)
|
||||
cell = table.cell(10, 1)
|
||||
set_cell_border(
|
||||
cell,
|
||||
top={"sz": 12, "val": "single", "color": "white", "space": "0"},
|
||||
)
|
||||
cell.text = "配制人:{}".format(i.user_name)
|
||||
except Exception as e:
|
||||
print('')
|
||||
|
||||
|
||||
# 保存文件
|
||||
def save(self, file_path):
|
||||
self.d.save(file_path)
|
||||
|
||||
# 关闭文件
|
||||
def close(self):
|
||||
self.d.close()
|
||||
|
||||
#合并单元格,修改列宽
|
||||
def merge_table(self,table):
|
||||
table.cell(1,0).merge(table.cell(1,1))
|
||||
table.cell(4,0).merge(table.cell(4,1))
|
||||
table.cell(5,0).merge(table.cell(5,1))
|
||||
table.cell(6,0).merge(table.cell(9,1))
|
||||
table.cell(0,0).width=Inches(6.5)
|
||||
|
||||
|
||||
# 下载文件
|
||||
@staticmethod
|
||||
def download_doc(filename, chunk_size=512):
|
||||
try:
|
||||
f = Document(filename)
|
||||
b=BytesIO()
|
||||
f.save(b)
|
||||
b.flush()
|
||||
b.seek(0)
|
||||
b_data=b.getvalue()
|
||||
b.close()
|
||||
os.remove(filename)
|
||||
return b_data
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# WriteDoc([1,2,3,4,5,6,7,8,9],'').add_table()
|
@ -0,0 +1,87 @@
|
||||
from tortoise import Tortoise, run_async
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
cwd = os.getcwd()
|
||||
PATHPaths = os.path.join(cwd, "algorithm", "Paths.json")
|
||||
PATHWeights = os.path.join(cwd, "algorithm", "Weights.json")
|
||||
|
||||
Paths = None
|
||||
Weights = None
|
||||
|
||||
with open(PATHPaths, "r") as fp:
|
||||
Paths = json.load(fp)
|
||||
|
||||
with open(PATHWeights, "r") as fp:
|
||||
Weights = json.load(fp)
|
||||
|
||||
# print("Paths: ", Paths)
|
||||
# print("Weights: ", Weights)
|
||||
|
||||
async def init():
|
||||
# Here we create a SQLite DB using file "db.sqlite3"
|
||||
# also specify the app name of "models"
|
||||
# which contain models from "app.models"
|
||||
await Tortoise.init(
|
||||
config={
|
||||
"connections": {
|
||||
"default": "mysql://root:123456@127.0.0.1:3306/agv?charset=utf8mb4"
|
||||
},
|
||||
"apps": {
|
||||
"models": {
|
||||
"models": ["aerich.models", "models"],
|
||||
"default_connection": "default"
|
||||
}
|
||||
},
|
||||
'use_tz': False,
|
||||
# 'timezone': setting.TIMEZONE
|
||||
}
|
||||
)
|
||||
# Generate the schema
|
||||
# await Tortoise.generate_schemas()
|
||||
|
||||
# run_async is a helper function to run simple async Tortoise scripts.
|
||||
run_async(init())
|
||||
|
||||
from models.path import Path
|
||||
|
||||
async def insertData():
|
||||
cntRow = len(Weights)
|
||||
#exclude itself
|
||||
cntColumn = len(Weights[0]) - 1
|
||||
keyRow = sorted(Paths.keys(), key=lambda x: int(x))
|
||||
|
||||
print("cntRow: {}, cntColumn: {}".format(cntRow, cntColumn))
|
||||
|
||||
for idxRow in range(0, cntRow):
|
||||
keySource = keyRow[idxRow]
|
||||
print("Path Source: ", keySource)
|
||||
paths = Paths[keySource]
|
||||
pathsKey = sorted(paths.keys(), key=lambda x: int(x))
|
||||
for idxColumn in range(0, cntColumn):
|
||||
print("{} -> {}".format(keySource, str(pathsKey[idxColumn])))
|
||||
print("Weight: ", Weights[idxRow][idxColumn])
|
||||
print("Path: ", Paths[str(keySource)][str(pathsKey[idxColumn])])
|
||||
|
||||
await Path.create(
|
||||
start = keySource,
|
||||
stop = str(pathsKey[idxColumn]),
|
||||
weight = Weights[idxRow][idxColumn],
|
||||
paths = Paths[str(keySource)][str(pathsKey[idxColumn])]
|
||||
)
|
||||
|
||||
|
||||
async def getCnt():
|
||||
return await Path.all().count()
|
||||
|
||||
async def main():
|
||||
print("cnt: ", await getCnt())
|
||||
await insertData()
|
||||
|
||||
|
||||
asyncio.run( main() )
|
||||
|
||||
exit()
|
||||
|
@ -0,0 +1 @@
|
||||
modbus-tk
|
@ -0,0 +1,7 @@
|
||||
from fastapi import APIRouter
|
||||
from .views import agv_router
|
||||
|
||||
router = APIRouter(prefix='/api/scheduler')
|
||||
|
||||
|
||||
router.include_router(agv_router, tags=["AGV小车状态"])
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,112 @@
|
||||
from fastapi import APIRouter
|
||||
from starlette.requests import Request
|
||||
from tortoise.queryset import QuerySet, Prefetch
|
||||
from models.agv import Agv
|
||||
from helper import respond_to
|
||||
from tortoise.contrib.pydantic import pydantic_model_creator
|
||||
import requests
|
||||
from typing import List
|
||||
|
||||
from plc.cmd import(
|
||||
cmd_type,
|
||||
setSubPaths,
|
||||
pass_door,
|
||||
setLayer,
|
||||
moveArm,
|
||||
taskNum,
|
||||
setTasks,
|
||||
readStatus
|
||||
)
|
||||
|
||||
agv_router = APIRouter(prefix='/agv')
|
||||
|
||||
endpoint_getassgin = "http://127.0.0.1:8000/api/scheduler/assign/"
|
||||
endpoint_done = "http://127.0.0.1:8000/api/scheduler/assign/done/"
|
||||
endpoint_failed = "http://127.0.0.1:8000/api/scheduler/assign/failed/"
|
||||
|
||||
|
||||
# @agv_router.get("/read")
|
||||
# async def getassgin():
|
||||
|
||||
|
||||
|
||||
# return respond_to(data=obj)
|
||||
|
||||
@agv_router.get("/getassgin")
|
||||
async def getassgin():
|
||||
res = requests.get(endpoint_getassgin)
|
||||
obj = None
|
||||
if res.status_code < 300:
|
||||
obj = res.json()
|
||||
|
||||
try:
|
||||
assignment = obj["data"]
|
||||
|
||||
agvs = await Agv.filter(
|
||||
agvid = assignment["agvid"],
|
||||
is_valid = True,
|
||||
is_active = True,
|
||||
)
|
||||
|
||||
agv = None
|
||||
if len(agvs) > 0:
|
||||
agv = agvs[0]
|
||||
agv.curorder = assignment["orderid"]
|
||||
agv.curitinerary = assignment["itinerary"]
|
||||
await agv.save()
|
||||
else:
|
||||
agv = await Agv.create(
|
||||
agvid = assignment["agvid"],
|
||||
curorder = assignment["orderid"],
|
||||
curitinerary = assignment["itinerary"]
|
||||
)
|
||||
|
||||
# for task in assignment["cmd"]:
|
||||
# task
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("getassgin: ", e)
|
||||
|
||||
|
||||
return respond_to(code=res.status_code, data=obj)
|
||||
|
||||
@agv_router.get("/done/{assignment_id}")
|
||||
async def get_done(assignment_id: str):
|
||||
print("assignment_id: ", endpoint_done + assignment_id)
|
||||
res = requests.put(endpoint_done + assignment_id)
|
||||
obj = None
|
||||
if res.status_code < 300:
|
||||
obj = res.json()
|
||||
|
||||
return respond_to(code=res.status_code, data=obj)
|
||||
|
||||
'http://localhost:8000/api/scheduler/assign/done/e56ea677-4dd1-46e6-8e4f-c7f25ff5893a'
|
||||
@agv_router.get("/failed/{assignment_id}")
|
||||
async def get_failed(assignment_id: str):
|
||||
print("assignment_id: ", endpoint_failed + assignment_id)
|
||||
res = requests.put(endpoint_failed + assignment_id)
|
||||
obj = None
|
||||
if res.status_code < 300:
|
||||
obj = res.json()
|
||||
|
||||
return respond_to(code=res.status_code, data=obj)
|
||||
|
||||
@agv_router.get('/list', summary="列出所有AGV小车的状态")
|
||||
async def index(request: Request, page_no: int = 1, page_size: int = 20):
|
||||
"""
|
||||
列出所有AGV小车的状态
|
||||
:param page_no: 1
|
||||
:param page_size: 20
|
||||
:return:
|
||||
"""
|
||||
offset = (page_no - 1) * page_size
|
||||
|
||||
query = QuerySet(Agv).filter(is_valid=True, is_active=True)
|
||||
|
||||
count = await query.count()
|
||||
agvs = await query.limit(page_size).offset(offset)
|
||||
|
||||
return respond_to(code=200, data=dict(count=count, data=agvs))
|
||||
|
||||
|
@ -0,0 +1,106 @@
|
||||
{
|
||||
"code": 200,
|
||||
"desc": "成功",
|
||||
"data": {
|
||||
"id": "e619a476-4d13-460c-8b5e-65c1a525657b",
|
||||
"num": "3SYB",
|
||||
"orderid": "tr43frr",
|
||||
"sequence": 0,
|
||||
"ordertime": "2024-08-23 00:00:00+08:00",
|
||||
"agvid": "001",
|
||||
"itinerary": [
|
||||
"A0",
|
||||
"B1",
|
||||
"N2",
|
||||
"I2",
|
||||
"Q1",
|
||||
"A0"
|
||||
],
|
||||
"cmd": [
|
||||
{
|
||||
"con1-A1": {
|
||||
"orderid": "tr43frr",
|
||||
"name": "试剂1",
|
||||
"typetask": 1,
|
||||
"action": 5,
|
||||
"quantity": 6,
|
||||
"coordinates": "B1",
|
||||
"traynum": "1X01-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"con1-B1": {
|
||||
"orderid": "tr43frr",
|
||||
"name": "试剂3",
|
||||
"typetask": 3,
|
||||
"action": 5,
|
||||
"quantity": 2,
|
||||
"coordinates": "N2",
|
||||
"traynum": "1R07-08"
|
||||
}
|
||||
},
|
||||
{
|
||||
"con1-C1": {
|
||||
"orderid": "tr43frr",
|
||||
"name": "试剂2",
|
||||
"typetask": 9,
|
||||
"action": 5,
|
||||
"quantity": 6,
|
||||
"coordinates": "I2",
|
||||
"traynum": "1V04"
|
||||
}
|
||||
}
|
||||
],
|
||||
"last": "I2",
|
||||
"loada": 6,
|
||||
"loadb": 2,
|
||||
"loadc": 0,
|
||||
"loadd": 0,
|
||||
"loade": 6,
|
||||
"is_canceled": false,
|
||||
"cancel_time": "None",
|
||||
"is_processing": false,
|
||||
"is_failed": false,
|
||||
"is_done": false,
|
||||
"is_valid": true,
|
||||
"updated_at": "2024-09-07 19:50:48.803537+08:00",
|
||||
"created_at": "2024-09-07 19:50:48.775540+08:00",
|
||||
"owner": {
|
||||
"id": "4f7cbe2e-dedf-4a6c-ae19-fb48820dc5fc",
|
||||
"username": "2346",
|
||||
"name": "2346",
|
||||
"is_locked": true,
|
||||
"phone": "7459344674",
|
||||
"email": "lsr@126.com"
|
||||
},
|
||||
"subtasks": [
|
||||
{
|
||||
"id": "91a18cc4-2c6f-4277-82db-0ac0c04182d2",
|
||||
"num": "SC8D",
|
||||
"name": "试剂2",
|
||||
"quantity": 6,
|
||||
"action": 5,
|
||||
"typetask": 9,
|
||||
"created_at": "2024-09-07 19:50:48.660618+08:00"
|
||||
},
|
||||
{
|
||||
"id": "33908c4e-07db-4fae-b2b8-b43f17b68d9d",
|
||||
"num": "ZEE8",
|
||||
"name": "试剂1",
|
||||
"quantity": 6,
|
||||
"action": 5,
|
||||
"typetask": 1,
|
||||
"created_at": "2024-09-07 19:50:48.628290+08:00"
|
||||
},
|
||||
{
|
||||
"id": "f9d1d905-fdf6-4957-84d2-2aa45df26b50",
|
||||
"num": "ZUZZ",
|
||||
"name": "试剂3",
|
||||
"quantity": 2,
|
||||
"action": 5,
|
||||
"typetask": 3,
|
||||
"created_at": "2024-09-07 19:50:48.710594+08:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
.
|
@ -0,0 +1,123 @@
|
||||
.INFO 12272:28072 2024-09-06 16:04:38,142 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 16:04:38,144 - INFO - Application startup complete.
|
||||
2024-09-06 16:07:15,590 - ERROR - Exception in ASGI application
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\base.py", line 78, in call_next
|
||||
message = await recv_stream.receive()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\anyio\streams\memory.py", line 94, in receive
|
||||
return self.receive_nowait()
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\anyio\streams\memory.py", line 87, in receive_nowait
|
||||
raise EndOfStream
|
||||
anyio.EndOfStream
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 435, in run_asgi
|
||||
result = await app( # type: ignore[func-returns-value]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 78, in __call__
|
||||
return await self.app(scope, receive, send)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\applications.py", line 276, in __call__
|
||||
await super().__call__(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\applications.py", line 122, in __call__
|
||||
await self.middleware_stack(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
|
||||
raise exc
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
|
||||
await self.app(scope, receive, _send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\cors.py", line 84, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\base.py", line 108, in __call__
|
||||
response = await self.dispatch_func(request, call_next)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\AutoTaskSchedule\simAgv\main.py", line 54, in process_time_header
|
||||
response = await next(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\base.py", line 84, in call_next
|
||||
raise app_exc
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\base.py", line 70, in coro
|
||||
await self.app(scope, receive_or_disconnect, send_no_error)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
|
||||
raise exc
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
|
||||
await self.app(scope, receive, sender)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 21, in __call__
|
||||
raise e
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 718, in __call__
|
||||
await route.handle(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 276, in handle
|
||||
await self.app(scope, receive, send)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\starlette\routing.py", line 66, in app
|
||||
response = await func(request)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\routing.py", line 237, in app
|
||||
raw_response = await run_endpoint_function(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastapi\routing.py", line 163, in run_endpoint_function
|
||||
return await dependant.call(**values)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\AutoTaskSchedule\simAgv\logic\agv\views.py", line 35, in index
|
||||
query = QuerySet(Agv).filter(is_valid=True, is_active=True)
|
||||
^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\tortoise\queryset.py", line 299, in __init__
|
||||
super().__init__(model)
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\tortoise\queryset.py", line 96, in __init__
|
||||
self.capabilities: Capabilities = model._meta.db.capabilities
|
||||
^^^^^^^^^^^^^^
|
||||
File "C:\Users\hp\AppData\Local\Programs\Python\Python311\Lib\site-packages\tortoise\models.py", line 285, in db
|
||||
raise ConfigurationError(
|
||||
tortoise.exceptions.ConfigurationError: default_connection for the model <class 'models.agv.Agv'> cannot be None
|
||||
2024-09-06 16:08:17,538 - INFO - Shutting down
|
||||
2024-09-06 16:08:17,647 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 16:08:17,649 - INFO - Application shutdown complete.
|
||||
2024-09-06 16:08:17,650 - INFO - Finished server process [12272]
|
||||
INFO 11504:26992 2024-09-06 16:19:14,235 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 16:19:14,237 - INFO - Application startup complete.
|
||||
2024-09-06 16:28:08,549 - INFO - Shutting down
|
||||
2024-09-06 16:28:08,658 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 16:28:08,661 - INFO - Application shutdown complete.
|
||||
2024-09-06 16:28:08,662 - INFO - Finished server process [11504]
|
||||
INFO 14408:36888 2024-09-06 16:28:09,897 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 16:28:09,899 - INFO - Application startup complete.
|
||||
2024-09-06 16:28:11,095 - INFO - Shutting down
|
||||
2024-09-06 16:28:11,205 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 16:28:11,207 - INFO - Application shutdown complete.
|
||||
2024-09-06 16:28:11,208 - INFO - Finished server process [14408]
|
||||
INFO 33012:30064 2024-09-06 17:33:05,949 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:33:05,951 - INFO - Application startup complete.
|
||||
2024-09-06 17:33:15,443 - INFO - Shutting down
|
||||
2024-09-06 17:33:15,553 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 17:33:15,554 - INFO - Application shutdown complete.
|
||||
2024-09-06 17:33:15,555 - INFO - Finished server process [33012]
|
||||
INFO 33740:5880 2024-09-06 17:33:22,981 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:33:22,983 - INFO - Application startup complete.
|
||||
2024-09-06 17:33:26,258 - INFO - Shutting down
|
||||
2024-09-06 17:33:26,367 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 17:33:26,367 - INFO - Application shutdown complete.
|
||||
2024-09-06 17:33:26,367 - INFO - Finished server process [33740]
|
||||
INFO 35040:17000 2024-09-06 17:34:07,447 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:34:07,449 - INFO - Application startup complete.
|
||||
2024-09-06 17:49:24,793 - INFO - Shutting down
|
||||
2024-09-06 17:49:24,901 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 17:49:24,905 - INFO - Application shutdown complete.
|
||||
2024-09-06 17:49:24,906 - INFO - Finished server process [35040]
|
||||
INFO 40716:17136 2024-09-06 17:49:25,709 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:49:25,710 - INFO - Application startup complete.
|
||||
2024-09-06 17:52:02,164 - INFO - Shutting down
|
||||
2024-09-06 17:52:02,273 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 17:52:02,274 - INFO - Application shutdown complete.
|
||||
2024-09-06 17:52:02,275 - INFO - Finished server process [40716]
|
||||
INFO 34940:9244 2024-09-06 17:52:03,508 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:52:03,510 - INFO - Application startup complete.
|
||||
2024-09-06 17:59:04,023 - INFO - Shutting down
|
||||
2024-09-06 17:59:04,130 - INFO - Waiting for application shutdown.
|
||||
2024-09-06 17:59:04,132 - INFO - Application shutdown complete.
|
||||
2024-09-06 17:59:04,132 - INFO - Finished server process [34940]
|
||||
INFO 36040:23864 2024-09-06 17:59:05,373 agvtasks.py:273 - SERVER - 准备启动AGV小车任务派发定时任务, 间隔时间: 12
|
||||
2024-09-06 17:59:05,375 - INFO - Application startup complete.
|
@ -0,0 +1,79 @@
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
|
||||
import logging
|
||||
from tortoise import Tortoise
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from tortoise.exceptions import DoesNotExist, DBConnectionError
|
||||
|
||||
from conf import setting, register_mysql
|
||||
from logic import agv
|
||||
from agvtask.agvtasks import start_scheduler
|
||||
|
||||
app = FastAPI(debug=setting.DEBUG_MODE)
|
||||
app.mount('/static', StaticFiles(directory="static"), name='static')
|
||||
|
||||
|
||||
def enable_debug_sql():
|
||||
import logging, sys
|
||||
|
||||
fmt = logging.Formatter(
|
||||
fmt="%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
sh = logging.StreamHandler(sys.stdout)
|
||||
sh.setLevel(logging.DEBUG)
|
||||
sh.setFormatter(fmt)
|
||||
|
||||
# will print debug sql
|
||||
logger_db_client = logging.getLogger("tortoise.db_client")
|
||||
logger_db_client.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
if setting.SQL_DEBUG_MODE:
|
||||
enable_debug_sql()
|
||||
|
||||
# 注册连接数据库
|
||||
register_mysql(app)
|
||||
Tortoise.init_models(["models.agv"], "models")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
await start_scheduler()
|
||||
|
||||
# 执行时间中间件
|
||||
@app.middleware('http')
|
||||
async def process_time_header(request: Request, next):
|
||||
start_time = time.time()
|
||||
response = await next(request)
|
||||
process_time = time.time() - start_time
|
||||
response.headers['X-Process-Time'] = str(process_time)
|
||||
return response
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_log_event():
|
||||
logger = logging.getLogger("uvicorn.access")
|
||||
access_error = logging.getLogger("uvicorn.error")
|
||||
handler = logging.handlers.TimedRotatingFileHandler('logs/server.log', when='midnight', backupCount=30)
|
||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.WARNING)
|
||||
access_error.addHandler(handler)
|
||||
|
||||
|
||||
# 跨域中间件
|
||||
app.add_middleware(
|
||||
CORSMiddleware, allow_origins=setting.YANEI_CORS_ORIGINS, allow_credentials=True, allow_methods=['*'], allow_headers=['*']
|
||||
)
|
||||
|
||||
app.include_router(agv.router)
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(app='main:app', host='0.0.0.0', port=setting.PORT, reload=setting.DEBUG_DEV_MODE)
|
@ -0,0 +1,58 @@
|
||||
from tortoise import BaseDBAsyncClient
|
||||
|
||||
|
||||
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS `aerich` (
|
||||
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`version` VARCHAR(255) NOT NULL,
|
||||
`app` VARCHAR(100) NOT NULL,
|
||||
`content` JSON NOT NULL
|
||||
) CHARACTER SET utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `Agvs` (
|
||||
`id` CHAR(36) NOT NULL PRIMARY KEY,
|
||||
`num` VARCHAR(255) COMMENT '短编号',
|
||||
`agvid` VARCHAR(255) COMMENT 'AGV小车编号',
|
||||
`percent` DECIMAL(3,2) NOT NULL COMMENT '小车电量' DEFAULT 1,
|
||||
`records` JSON COMMENT '记录列表',
|
||||
`curorder` VARCHAR(255) COMMENT '当前订单号',
|
||||
`curstop` VARCHAR(255) COMMENT '当前站点',
|
||||
`curitinerary` JSON COMMENT '订单对应的全局最短路径',
|
||||
`is_charging` BOOL NOT NULL COMMENT '充电中' DEFAULT 0,
|
||||
`is_busy` BOOL NOT NULL COMMENT '设备忙' DEFAULT 0,
|
||||
`is_abnormal` BOOL NOT NULL COMMENT '是否异常' DEFAULT 0,
|
||||
`abnormal_time` DATETIME(6) COMMENT '异常时间',
|
||||
`is_done` BOOL NOT NULL COMMENT '是否完成' DEFAULT 0,
|
||||
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`updated_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
`is_valid` BOOL NOT NULL COMMENT '是否有效' DEFAULT 1,
|
||||
`is_active` BOOL NOT NULL COMMENT '是否删除' DEFAULT 1
|
||||
) CHARACTER SET utf8mb4 COMMENT='AGV管理表';
|
||||
CREATE INDEX `num` ON `Agvs` (`num`);
|
||||
CREATE INDEX `agvid` ON `Agvs` (`agvid`);
|
||||
CREATE INDEX `agvs_is_busy` ON `Agvs` (`is_busy`);
|
||||
CREATE INDEX `agvs_is_abnormal` ON `Agvs` (`is_abnormal`);
|
||||
CREATE INDEX `agvs_is_done` ON `Agvs` (`is_done`);
|
||||
CREATE INDEX `agvs_is_charging` ON `Agvs` (`is_charging`);
|
||||
CREATE INDEX `is_valididx` ON `Agvs` (`is_valid`);
|
||||
CREATE INDEX `is_activeidx` ON `Agvs` (`is_active`);
|
||||
CREATE TABLE IF NOT EXISTS `Paths` (
|
||||
`id` CHAR(36) NOT NULL PRIMARY KEY,
|
||||
`start` VARCHAR(255) COMMENT '起点',
|
||||
`stop` VARCHAR(255) COMMENT '终点',
|
||||
`weight` INT NOT NULL COMMENT '距离' DEFAULT 0,
|
||||
`paths` JSON COMMENT '路径',
|
||||
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
`updated_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
`is_valid` BOOL NOT NULL COMMENT '是否有效' DEFAULT 1,
|
||||
`is_active` BOOL NOT NULL COMMENT '是否删除' DEFAULT 1
|
||||
) CHARACTER SET utf8mb4 COMMENT='AGV管理表';
|
||||
CREATE INDEX `start` ON `Paths` (`start`);
|
||||
CREATE INDEX `stop` ON `Paths` (`stop`);
|
||||
CREATE INDEX `is_valididx` ON `Paths` (`is_valid`);
|
||||
CREATE INDEX `is_activeidx` ON `Paths` (`is_active`);"""
|
||||
|
||||
|
||||
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
"""
|
@ -0,0 +1,57 @@
|
||||
from tortoise import BaseDBAsyncClient
|
||||
|
||||
|
||||
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
ALTER TABLE `Paths` DROP INDEX `stop`;
|
||||
ALTER TABLE `Paths` DROP INDEX `is_valididx`;
|
||||
ALTER TABLE `Paths` DROP INDEX `is_activeidx`;
|
||||
ALTER TABLE `Paths` DROP INDEX `start`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_charging`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_done`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `is_valididx`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `num`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvid`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `is_activeidx`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_abnormal`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_busy`;
|
||||
CREATE INDEX `agvs_is_charging` ON `Agvs` (`is_charging`);
|
||||
CREATE INDEX `is_valididx` ON `Agvs` (`is_valid`);
|
||||
CREATE INDEX `agvs_is_busy` ON `Agvs` (`is_busy`);
|
||||
CREATE INDEX `agvs_is_done` ON `Agvs` (`is_done`);
|
||||
CREATE INDEX `agvid` ON `Agvs` (`agvid`);
|
||||
CREATE INDEX `agvs_is_abnormal` ON `Agvs` (`is_abnormal`);
|
||||
CREATE INDEX `is_activeidx` ON `Agvs` (`is_active`);
|
||||
CREATE INDEX `num` ON `Agvs` (`num`);
|
||||
CREATE INDEX `stop` ON `Paths` (`stop`);
|
||||
CREATE INDEX `start` ON `Paths` (`start`);
|
||||
CREATE INDEX `is_activeidx` ON `Paths` (`is_active`);
|
||||
CREATE INDEX `is_valididx` ON `Paths` (`is_valid`);"""
|
||||
|
||||
|
||||
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
ALTER TABLE `Paths` DROP INDEX `is_valididx`;
|
||||
ALTER TABLE `Paths` DROP INDEX `is_activeidx`;
|
||||
ALTER TABLE `Paths` DROP INDEX `start`;
|
||||
ALTER TABLE `Paths` DROP INDEX `stop`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `num`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `is_activeidx`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_abnormal`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvid`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_done`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_busy`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `is_valididx`;
|
||||
ALTER TABLE `Agvs` DROP INDEX `agvs_is_charging`;
|
||||
CREATE INDEX `agvs_is_busy` ON `Agvs` (`is_busy`);
|
||||
CREATE INDEX `agvs_is_abnormal` ON `Agvs` (`is_abnormal`);
|
||||
CREATE INDEX `is_activeidx` ON `Agvs` (`is_active`);
|
||||
CREATE INDEX `agvid` ON `Agvs` (`agvid`);
|
||||
CREATE INDEX `num` ON `Agvs` (`num`);
|
||||
CREATE INDEX `is_valididx` ON `Agvs` (`is_valid`);
|
||||
CREATE INDEX `agvs_is_done` ON `Agvs` (`is_done`);
|
||||
CREATE INDEX `agvs_is_charging` ON `Agvs` (`is_charging`);
|
||||
CREATE INDEX `start` ON `Paths` (`start`);
|
||||
CREATE INDEX `is_activeidx` ON `Paths` (`is_active`);
|
||||
CREATE INDEX `is_valididx` ON `Paths` (`is_valid`);
|
||||
CREATE INDEX `stop` ON `Paths` (`stop`);"""
|
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
from .agv import Agv
|
||||
from .path import Path
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,85 @@
|
||||
from tortoise import fields, models
|
||||
from tortoise.indexes import Index
|
||||
from tortoise.contrib.pydantic import pydantic_model_creator
|
||||
from tortoise.signals import post_save
|
||||
from typing import Type
|
||||
from helper.tools import genRandomStr
|
||||
|
||||
# from conf.setting import setting
|
||||
|
||||
class Agv(models.Model):
|
||||
id = fields.UUIDField(pk=True)
|
||||
num = fields.CharField(null=True, max_length=255, description='短编号')
|
||||
|
||||
agvid = fields.CharField(null=True, max_length=255, description='AGV小车编号')
|
||||
|
||||
percent = fields.DecimalField(default=1.0, max_digits=3, decimal_places=2, description='小车电量')
|
||||
|
||||
records = fields.JSONField(null=True, description='记录列表')
|
||||
|
||||
curorder = fields.CharField(null=True, max_length=255, description='当前订单号')
|
||||
curstop = fields.CharField(null=True, max_length=255, description='当前站点')
|
||||
curitinerary = fields.JSONField(null=True, description='订单对应的全局最短路径')
|
||||
|
||||
is_charging = fields.BooleanField(default=False, description='充电中')
|
||||
is_busy = fields.BooleanField(default=False, description='设备忙')
|
||||
|
||||
is_abnormal = fields.BooleanField(default=False, description='是否异常')
|
||||
abnormal_time = fields.DatetimeField(null=True, description='异常时间')
|
||||
|
||||
is_done = fields.BooleanField(default=False, description='是否完成')
|
||||
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
updated_at = fields.DatetimeField(auto_now=True)
|
||||
is_valid = fields.BooleanField(default=True, description='是否有效')
|
||||
is_active = fields.BooleanField(default=True, description='是否删除')
|
||||
|
||||
|
||||
class Meta:
|
||||
table = 'Agvs'
|
||||
table_description = 'AGV管理表'
|
||||
|
||||
indexes = [
|
||||
Index(fields={"num"}, name="num"),
|
||||
Index(fields={"agvid"}, name="agvid"),
|
||||
Index(fields={"is_busy"}, name="agvs_is_busy"),
|
||||
Index(fields={"is_abnormal"}, name="agvs_is_abnormal"),
|
||||
Index(fields={"is_done"}, name="agvs_is_done"),
|
||||
Index(fields={"is_charging"}, name="agvs_is_charging"),
|
||||
Index(fields={"is_valid"}, name="is_valididx"),
|
||||
Index(fields={"is_active"}, name="is_activeidx"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f'Agv(id={self.id} num={self.num}, agvid={self.agvid}, created_at={self.created_at})'
|
||||
|
||||
def as_json(self):
|
||||
return dict(id="{}".format(self.id), num=self.num, agvid=self.agvid, created_at=self.created_at)
|
||||
|
||||
Agv_Pydantic = pydantic_model_creator(Agv, name="Agv")
|
||||
|
||||
|
||||
@post_save(Agv)
|
||||
async def agv_post_save(
|
||||
sender: "Type[Agv]",
|
||||
instance: Agv,
|
||||
created,
|
||||
using_db,
|
||||
update_fields,
|
||||
) -> None:
|
||||
|
||||
if not instance.num or "null" == instance.num:
|
||||
# print("instance.num: ", instance.num)
|
||||
# check duplicated num
|
||||
async def checkNum():
|
||||
num = genRandomStr(4)
|
||||
# print("new num: ", num)
|
||||
if await Agv.filter(num = num).exists():
|
||||
# print("num exist, ", num)
|
||||
checkNum()
|
||||
else:
|
||||
# print("num not exist, ", num)
|
||||
instance.num = num
|
||||
await instance.save()
|
||||
|
||||
await checkNum()
|
@ -0,0 +1,41 @@
|
||||
from tortoise import fields, models
|
||||
from tortoise.indexes import Index
|
||||
from tortoise.contrib.pydantic import pydantic_model_creator
|
||||
from tortoise.signals import post_save
|
||||
from typing import Type
|
||||
|
||||
|
||||
class Path(models.Model):
|
||||
id = fields.UUIDField(pk=True)
|
||||
|
||||
start = fields.CharField(null=True, max_length=255, description='起点')
|
||||
stop = fields.CharField(null=True, max_length=255, description='终点')
|
||||
weight = fields.IntField(default=0, description='距离')
|
||||
paths = fields.JSONField(null=True, max_length=255, description='路径')
|
||||
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
updated_at = fields.DatetimeField(auto_now=True)
|
||||
is_valid = fields.BooleanField(default=True, description='是否有效')
|
||||
is_active = fields.BooleanField(default=True, description='是否删除')
|
||||
|
||||
|
||||
class Meta:
|
||||
table = 'Paths'
|
||||
table_description = 'AGV管理表'
|
||||
|
||||
indexes = [
|
||||
Index(fields={"start"}, name="start"),
|
||||
Index(fields={"stop"}, name="stop"),
|
||||
Index(fields={"is_valid"}, name="is_valididx"),
|
||||
Index(fields={"is_active"}, name="is_activeidx"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f'Path(id={self.id} start={self.start}, stop={self.stop}, created_at={self.created_at})'
|
||||
|
||||
def as_json(self):
|
||||
return dict(id="{}".format(self.id), start=self.start, stop=self.stop, created_at=self.created_at)
|
||||
|
||||
Path_Pydantic = pydantic_model_creator(Path, name="Path")
|
||||
|
||||
|
@ -0,0 +1,141 @@
|
||||
from tortoise import fields, models
|
||||
from tortoise.indexes import Index
|
||||
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
|
||||
from tortoise.signals import post_save
|
||||
from typing import Type
|
||||
from helper.tools import genRandomStr
|
||||
|
||||
class Vacancy(models.Model):
|
||||
id = fields.UUIDField(pk=True)
|
||||
number = fields.CharField(max_length=255, null=True, description='空位编号')
|
||||
traynum = fields.CharField(max_length=255, null=True, description='托盘编号')
|
||||
|
||||
orderids = fields.JSONField(null=True, description='托盘编号对应的订单号列表')
|
||||
|
||||
memo = fields.JSONField(null=True, description='备忘录')
|
||||
|
||||
is_shelf = fields.BooleanField(default=False, description='是货架库存')
|
||||
is_connect = fields.BooleanField(default=False, description='是接驳区库存')
|
||||
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
updated_at = fields.DatetimeField(auto_now=True)
|
||||
is_valid = fields.BooleanField(default=True, description='是否取消')
|
||||
is_active = fields.BooleanField(default=True, description='是否删除')
|
||||
|
||||
|
||||
class PydanticMeta:
|
||||
# Let's exclude the created timestamp
|
||||
exclude = ("created_at",)
|
||||
|
||||
class Meta:
|
||||
table = 'Vacancys'
|
||||
table_description = '暂存货架或接驳机空位编号表'
|
||||
|
||||
ordering = ["number", "created_at"]
|
||||
|
||||
indexes = [
|
||||
Index(fields={"number"}, name="numberidx"),
|
||||
Index(fields={"traynum"}, name="traynumidx"),
|
||||
Index(fields={"is_shelf"}, name="is_shelfidx"),
|
||||
Index(fields={"is_connect"}, name="is_connectidx"),
|
||||
Index(fields={"is_valid"}, name="is_valididx"),
|
||||
Index(fields={"is_active"}, name="is_activeidx"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f'Vacancy(id={self.id} number={self.number}, traynum={self.traynum}, created_at={self.created_at})'
|
||||
|
||||
def as_json(self):
|
||||
return dict(id="{}".format(self.id), number=self.number, traynum=self.traynum, created_at="{}".format(self.created_at))
|
||||
|
||||
|
||||
Vacancy_Pydantic = pydantic_model_creator(Vacancy, name="Vacancy")
|
||||
Vacancy_Pydantic_List = pydantic_queryset_creator(Vacancy)
|
||||
|
||||
|
||||
class Schelve(models.Model):
|
||||
id = fields.UUIDField(pk=True)
|
||||
num = fields.CharField(null=True, max_length=255, description='短编号')
|
||||
name = fields.CharField(null=True, max_length=255, description='暂存货架编号')
|
||||
vacancies = fields.ManyToManyField('models.Vacancy', related_name='schelvevacancies')
|
||||
|
||||
memo = fields.JSONField(null=True, description='备忘录')
|
||||
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
updated_at = fields.DatetimeField(auto_now=True)
|
||||
is_valid = fields.BooleanField(default=True, description='是否取消')
|
||||
is_active = fields.BooleanField(default=True, description='是否删除')
|
||||
|
||||
|
||||
class PydanticMeta:
|
||||
# Let's exclude the created timestamp
|
||||
exclude = ("created_at",)
|
||||
allow_cycles = True
|
||||
max_recursion = 4
|
||||
|
||||
class Meta:
|
||||
table = 'Schelves'
|
||||
table_description = '暂存货架库存表'
|
||||
|
||||
ordering = ["name", "created_at"]
|
||||
|
||||
indexes = [
|
||||
Index(fields={"name"}, name="nameidx"),
|
||||
Index(fields={"num"}, name="numidx"),
|
||||
Index(fields={"is_valid"}, name="is_valididx"),
|
||||
Index(fields={"is_active"}, name="is_activeidx"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f'Schelve(id={self.id} num={self.num}, created_at={self.created_at})'
|
||||
|
||||
def as_json(self):
|
||||
return dict(
|
||||
id="{}".format(self.id),
|
||||
num=self.num,
|
||||
name =self.name,
|
||||
memo =self.memo,
|
||||
is_valid =self.is_valid,
|
||||
is_active =self.is_active,
|
||||
updated_at="{}".format(self.updated_at),
|
||||
created_at="{}".format(self.created_at))
|
||||
|
||||
Schelve_Pydantic = pydantic_model_creator(Schelve, name="Schelve")
|
||||
|
||||
@post_save(Schelve)
|
||||
async def schelve_post_save(
|
||||
sender: "Type[Schelve]",
|
||||
instance: Schelve,
|
||||
created,
|
||||
using_db,
|
||||
update_fields,
|
||||
) -> None:
|
||||
|
||||
if not instance.num or "null" == instance.num:
|
||||
# print("instance.num: ", instance.num)
|
||||
# check duplicated num
|
||||
async def checkNum():
|
||||
num = genRandomStr(4)
|
||||
# print("new num: ", num)
|
||||
if await Schelve.filter(num = num).exists():
|
||||
# print("num exist, ", num)
|
||||
checkNum()
|
||||
else:
|
||||
# print("num not exist, ", num)
|
||||
instance.num = num
|
||||
await instance.save()
|
||||
|
||||
await checkNum()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
import traceback
|
||||
import click
|
||||
from modbus_tk import modbus_tcp
|
||||
|
||||
|
||||
class ModbusConnection:
|
||||
'''
|
||||
Modbus连接管理器
|
||||
'''
|
||||
|
||||
def __init__(self, host: str, port: int) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.master = None
|
||||
|
||||
def __enter__(self) -> modbus_tcp.TcpMaster:
|
||||
self.master = modbus_tcp.TcpMaster(self.host, self.port)
|
||||
self.master.set_timeout(5.0)
|
||||
return self.master
|
||||
|
||||
def __exit__(self, exc_type, exc_value, _traceback) -> None:
|
||||
try:
|
||||
if self.master:
|
||||
self.master.close()
|
||||
|
||||
if exc_type is not None:
|
||||
click.echo("发生异常:")
|
||||
click.echo(f"类型: {exc_type}")
|
||||
click.echo(f"值: {exc_value}")
|
||||
click.echo("调用栈:")
|
||||
traceback.print_tb(_traceback)
|
||||
except Exception as e :
|
||||
print('PLC通信异常:::',str(e))
|
||||
|
||||
|
@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf_8 -*-
|
||||
import struct
|
||||
import modbus_tk.defines as cst
|
||||
from plc.tools import bytes_to_int_list, get_bit_bool, int_list_to_bytes
|
||||
from plc.modbus import ModbusConnection
|
||||
from conf.setting import AGVPLC
|
||||
from models.path import Path
|
||||
import time
|
||||
|
||||
|
||||
'''
|
||||
智能柜地址定义
|
||||
'''
|
||||
ADDR_DRAWER = 6051
|
||||
ADDR_CABINET = 6052
|
||||
ADDR_OPEN_DRAWER = 6053
|
||||
ADDR_CLOSE_DRAWER = 6054
|
||||
|
||||
|
||||
class Cabinet:
|
||||
'''
|
||||
智能柜配置
|
||||
'''
|
||||
HOST = '192.168.193.211'
|
||||
PORT = 6000
|
||||
|
||||
class Worktop:
|
||||
'''
|
||||
操作台配置
|
||||
'''
|
||||
HOST = '192.168.193.212'
|
||||
PORT = 502
|
||||
|
||||
class AmrDrug:
|
||||
'''
|
||||
药剂AMR配置PLC
|
||||
'''
|
||||
HOST = '192.168.193.202'
|
||||
PORT = 502
|
||||
class AmrInstrument:
|
||||
'''
|
||||
仪器AMR配置PLC
|
||||
'''
|
||||
HOST = '192.168.193.203'
|
||||
PORT = 502
|
||||
|
||||
|
||||
"""
|
||||
操作台地址定义
|
||||
"""
|
||||
class WorktopAddr:
|
||||
ADDR_POWER_ONE = 6080
|
||||
ADDR_POWER_TWO = 6081
|
||||
ADDR_TRANSFER_TRAY = 6082
|
||||
ADDR_POWER_ONE_STATUS = 6083
|
||||
ADDR_POWER_TWO_STATUS = 6084
|
||||
ADDR_TANSFER_TRAY_STATUS = 6085
|
||||
ADDR_WORKTOP_ERR = 6086
|
||||
TASK_ADDR_BEGIN = 6080 # 发送任务的起始未知
|
||||
STATUS_ADDR_BEGIN = 6083 # 状态的起始状态
|
||||
STATUS_ADDR_LENGTH = 4
|
||||
|
||||
class AmrAddr:
|
||||
|
||||
# 起始地址
|
||||
TASK_ADDR_BEGIN = 6001
|
||||
|
||||
# 门状态
|
||||
CABIENT_STATUS_ADDR_BEGIN = 6061
|
||||
|
||||
# 抓试剂点位的起始地址
|
||||
GRAB_ADDR_BEGIN = 7000
|
||||
# 抓取校验点的起始地址
|
||||
GRAB_MARK_ADDR_BEGIN = 7600
|
||||
|
||||
# 放试剂点位的起始地址
|
||||
DEPOSIT_ADDR_BEGIN= 9000
|
||||
# 松抓校验点的起始地址
|
||||
DEPOSIT_MARK_ADDR_BEGIN= 7900
|
||||
|
||||
|
||||
# 状态起始地址及长度
|
||||
STATUS_ADDR_BEGIN = 6100
|
||||
STATUS_ADDR_LENGTH = 42
|
||||
|
||||
# 取货起始地址及长度
|
||||
LOAD_LOCATION_BEGIN = 7000
|
||||
LOAD_LOCATION_COUNT = 25
|
||||
|
||||
# 卸货起始地址及长度
|
||||
UNLOAD_LOCATION_BEGIN = 7300
|
||||
UNLOAD_LOCATION_COUNT = 25
|
||||
|
||||
TASK_STATUS = 6100 # 状态显示1
|
||||
AMR_STATUS = 6101 # 状态显示2
|
||||
INSTRUMENT_STATUS_1 = 6102 # 状态显示3
|
||||
D6103 = 6103
|
||||
D6104 = 6104
|
||||
INSTRUMENT_STATUS_2 = 6105
|
||||
D6105 = 6105
|
||||
D6106 = 6106
|
||||
D6107 = 6107
|
||||
D6108 = 6108
|
||||
D6109 = 6109
|
||||
INSTRUMENT_CAM_HIGH = 6110
|
||||
CUR_LOAD_LAYER_COMPLETE_NUM = 6112 # 当前层已取试剂数量
|
||||
CUR_LOAD_LAYER_COMPLETE = 6113 # 当前层试剂已取完
|
||||
CUR_LOAD_LAYER_ID = 6114 # 当前在取层数
|
||||
LOAD_NUMBER = 6115 # 合计一共已取试剂数量
|
||||
D6116 = 6116
|
||||
D6117 = 6117
|
||||
D6118 = 6118
|
||||
D6119 = 6119
|
||||
ROBOT_CUR_NAV_LOCATION = 6120 # 机器人当前导航站点
|
||||
ROBOT_LOCATING_STATUS = 6121 # 机器人定位状态
|
||||
ROBOT_CUR_NAV_STATUS = 6122 # 机器人当前导航状态
|
||||
ROBOT_CUR_NAV_TYPE = 6123 # 机器人当前导航类型
|
||||
ROBOT_CONFIDENCE_LEVEL = 6124 # 机器人定位置信度
|
||||
BATTERY_STATUS = 6126 # 电池电量
|
||||
BATTERY_TEM = 6128 # 电池温度
|
||||
BATTERY_VOLTAGE = 6130 # 电池电压
|
||||
ROBOT_CUR_LOCATION = 6132 # 机器人当前所在站点
|
||||
ROBOT_PRE_LOCATION = 6133 # 机器人上一个所在站点
|
||||
ROBOT_NXT_LOCATION = 6134 # 机器人下一个要经过的站点
|
||||
BLOCK_REASON = 6135 # 被阻挡的原因
|
||||
DECELERATION_REASON = 6136 # 机器人减速原因
|
||||
ROBOT_CUR_STATUS = 6137 # 状态指示
|
||||
|
||||
|
||||
|
||||
def get_worktop_status():
|
||||
"""
|
||||
获取操作台状态[0,0,88,0]
|
||||
"""
|
||||
with ModbusConnection(
|
||||
Worktop.HOST,
|
||||
Worktop.PORT
|
||||
) as master:
|
||||
worktop_list = master.execute(
|
||||
1,
|
||||
cst.READ_HOLDING_REGISTERS,
|
||||
WorktopAddr.STATUS_ADDR_BEGIN,
|
||||
WorktopAddr.STATUS_ADDR_LENGTH,
|
||||
)
|
||||
print('获取旋转托盘状态:::',worktop_list)
|
||||
# return parser_worktop_status(worktop_list)
|
||||
|
||||
|
||||
|
||||
def get_drawer_status():
|
||||
'''
|
||||
解析抽屉状态
|
||||
'''
|
||||
with ModbusConnection(
|
||||
Cabinet.HOST,
|
||||
Cabinet.PORT
|
||||
) as master:
|
||||
drawer_short_list = master.execute(
|
||||
1, cst.READ_HOLDING_REGISTERS, ADDR_DRAWER, 1)
|
||||
print('get_drawer_status:::', drawer_short_list)
|
||||
# return parser_drawer_status(drawer_short_list[0])
|
||||
|
||||
|
||||
def get_cabinet_task_status():
|
||||
'''
|
||||
解析任务状态
|
||||
'''
|
||||
with ModbusConnection(
|
||||
Cabinet.HOST,
|
||||
Cabinet.PORT
|
||||
) as master:
|
||||
task_status_data_list = master.execute(
|
||||
1, cst.READ_HOLDING_REGISTERS, ADDR_CABINET, 1)
|
||||
print('get_cabinet_task_status:::', task_status_data_list)
|
||||
# return parser_task_status(task_status_data_list[0])
|
||||
|
||||
|
||||
def get_instrument_amr_status():
|
||||
'''
|
||||
获取试剂amr 并解析AMR状态
|
||||
'''
|
||||
try:
|
||||
with ModbusConnection(
|
||||
AmrInstrument.HOST,
|
||||
AmrInstrument.PORT
|
||||
) as master:
|
||||
drawer_short_list = master.execute(
|
||||
1,
|
||||
cst.READ_HOLDING_REGISTERS,
|
||||
AmrAddr.STATUS_ADDR_BEGIN,
|
||||
AmrAddr.STATUS_ADDR_LENGTH
|
||||
)
|
||||
print('get_instrument_amr_status::',drawer_short_list)
|
||||
# return parser_ams_status(int_list_to_bytes(drawer_short_list))
|
||||
except Exception as e :
|
||||
# amr_status = AmrStatus()
|
||||
# return amr_status
|
||||
|
||||
print("get_instrument_amr_status Exception: ", e)
|
||||
|
||||
|
||||
def get_drug_amr_status():
|
||||
'''
|
||||
获取试剂amr 并解析AMR状态
|
||||
'''
|
||||
|
||||
with ModbusConnection(
|
||||
AmrDrug.HOST,
|
||||
AmrDrug.PORT
|
||||
) as master:
|
||||
drawer_short_list = master.execute(
|
||||
1,
|
||||
cst.READ_HOLDING_REGISTERS,
|
||||
AmrAddr.STATUS_ADDR_BEGIN,
|
||||
AmrAddr.STATUS_ADDR_LENGTH
|
||||
)
|
||||
print('get_drug_amr_status::',drawer_short_list)
|
||||
# return parser_ams_status(int_list_to_bytes(drawer_short_list))
|
||||
|
||||
|
||||
|
||||
|
||||
get_worktop_status()
|
||||
get_drawer_status()
|
||||
get_cabinet_task_status()
|
||||
get_instrument_amr_status()
|
||||
get_drug_amr_status()
|
@ -0,0 +1,4 @@
|
||||
[tool.aerich]
|
||||
tortoise_orm = "settings.TORTOISE_ORM"
|
||||
location = "./migrations"
|
||||
src_folder = "./."
|
Loading…
Reference in new issue