First commit after init

main
sunsocool 4 months ago
parent a70e15932a
commit 8b48f3177a

27
.env

@ -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.

@ -0,0 +1,39 @@
#重新配置需要先删除目录 migrations目录下的所有文件
# 1、执行aerich初始化aerich init -t 指定配置
aerich init -t settings.TORTOISE_ORM
# 将会在目录下生成空的 migrations 文件夹和 aerich.ini 文件
# 2、将模型映射到数据库中
aerich init-db
# 此时数据库中就会生成对应的表
# migrations 下将会生成SQL语句
# aerich表下会存每个模型的内容
# 3、重新生成SQL语句
aerich migrate
# 4、把新生成的SQL推送到数据库
aerich upgrade
# 表信息
# 5、如果要回到上一个版本
aerich downgrade
# 6、查看历史迁移记录
aerich history
# 7、查看形成当前版本的迁移记录文件
aerich heads
# 8、aerich 除了提供命令行之外还提供了代码内执行的办法从aerich引入 Command类即可提供的方法与命令行一样
# 注根据实测在MySQL5.7上使用会报错:
# 换成MySQL8以后使用正常

@ -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

@ -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

@ -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
# )

@ -0,0 +1,76 @@
import os
import sys
import logging
from pydantic import BaseSettings
class AGVPLC:
'''
AGV配置PLC
'''
HOST = '192.168.193.202'
PORT = 502
class Setting(BaseSettings):
# 项目信息
PROJECT_NAME: str = 'agv'
VERSION: str = '1.0.0'
DESCRIPTION: str = ''
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# 查看models/module.py的BelongTo类
RUN_AT_OS: int = 0
# log
YANEI_LOG = BASE_DIR + os.sep + 'logs' # drcc日志目录
YANEI_LOG_LEVEL = logging.INFO
LOG_MAX_SIZE = 200 * 1024 * 1024 # 文件最大值, 超过该size则切割(b-字节)
LOG_MAX_TIME = 30 * 24 * 3600 # 切割日志文件最大保留时间(s-秒)
LOG_PRE_FIX = 'yanei'
# 调试模式
DEBUG_MODE: bool = True
DEBUG_DEV_MODE: bool = True # 如是否自动热加载服务等
# SQL 调试
SQL_DEBUG_MODE: bool = False
# 服务端口号
PORT: int = 8003
# 数据库连接地址(必填)
DB_URL: str
REDIS_URL: str = 'redis://localhost'
# 终端ID(必填)
TERMINAL_ID: str
# 时区
TIMEZONE: str = 'Asia/Shanghai'
# Jwt
JWT_SECRET_KEY: str
JWT_ALGORITHM: str = 'HS256'
YANEI_CORS_ORIGINS: list = ['*'] # 默认的跨域请求, 但是在很多情况下不能直接写成*
# 账号注册初始密码
REGISTER_INIT_PASSWORD: str = '000000'
AGVID = "001"
# set AGV tray capacity
# 设置AGV小车托盘库存
AGVCAP = {
1: 6, #标准品A(1)
3: 2, #标准品B(3)
5: 0, #标准品C(5)
7: 0, #标准品D(7)
9: 20 #非标准品E(9) 假定一个托盘之多装20个非标品需要根据实际情况确定
}
# 最大搜索距离
# MAXSEARCHDEPTH = 3000
MAXSEARCHDEPTH = 100000000000
class Config:
env_file = '.env'
setting = Setting()

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,168 @@
import requests
import time
import json
import uuid
import sys
import os
import threading
# 获取分辨率索引
url = "http://localhost:6543/GetAllDisplayInfo"
param = {
"dev_idx": "0",
}
import inspect
import ctypes
# 关闭线程
def _async_raise(tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def stop_thread(thread):
_async_raise(thread.ident, SystemExit)
class GaoPaiYi:
# path = "/home/yanyi/Project/MainManage/img" # 默认存储地址
path = os.path.join(os.getcwd(), "img")
if not os.path.exists(path):
os.umask(0)
os.makedirs(path)
def __init__(self):
url = "http://localhost:6543/GetAllDisplayInfo"
try:
res = requests.get(url=url) # 启动高拍仪
print(json.loads(res.text))
print(json.loads(res.text)['data'])
print(json.loads(res.text)['code'] == '0')
except:
pass
def openCamera(self):
try:
url = "http://127.0.0.1:6543/GetAllDisplayInfo"
param = {}
header = {'Content-Type': 'application/json;charset=utf-8'}
res = requests.get(url=url, data=json.dumps(param), headers=header, timeout=10)
print(json.loads(res.text))
url = 'http://127.0.0.1:6543/video=stream&camidx=0'
res = requests.get(url=url, data=json.dumps(param), headers=header, timeout=10)
print(json.loads(res.text))
print(json.loads(res.text)['filepath'])
print('打开相机:::', json.loads(res.text)['code'])
except Exception as e:
print(e)
return {'code': -1, 'msg': e}
# win 版本
# def getPic(self):
# try:
# p = threading.Thread(target=self.openCamera)
# p.start()
# time.sleep(2)
# # 摄像头拍照
# url = 'http://127.0.0.1:6543/video=grabimage'
# # url = 'https://restapi.getui.com/v2/h8m8bkLRUG6C313Xw8sNV8/auth'
# param = {
# "filepath": f"{self.path}\\{str(uuid.uuid1())}.jpg",
# "rotate": "90", #// 图像旋转角度90的整数倍默认"0"
# "deskew": "0", #// 纠偏主头有效参数0:不纠偏1:纠偏
# "deskewval":"0", #// 纠偏像素值正常给0正数时多裁负数时少裁
# "camidx": "0", #// 摄像头索引参数0:主头1:副头
# "ColorMode": "0", #// 色彩模式图片保存本地时调用。0彩色 1灰色 2黑白 3白纸印章 4去背景色(普通文件) 5:去背景色(身份证)
# "quality": "0", #// 图片质量,图片保存本地调用
# # 。0:默认质量1:高质量2:较高质量3:中质量4:较低质量5:低质量
# "bAutoAdjust":"1", #// 是否自动摆正: 0不摆正 1摆正
# "bIsPrint1to1":"", #// 是否1:1打印
# "watermark": {
# "pos": "", #// 水印在图像中的位置0:左上1:右上2:左下3:右下4:中间
# "content": "", #// 水印内容必须utf-8编码当水印内容为空将当前时间作为水印
# "transparency": "", #// 透明度0~255,0:完全透明255:不透明
# "fontsize": "", #// 字体大小默认32
# "font": "", #// 字体
# "color": "" # // 水印颜色colorname
# }
# }
# header ={'Content-Type':'application/json;charset=utf-8'}
# res = requests.post(url = url,data=json.dumps(param),headers=header)
# print(json.loads(res.text).get('filepath'))
# if json.loads(res.text)['code'] == "0":
# res_data = {'code':0,'msg':"图片保存成功","path":json.loads(res.text)['filepath'].split("\\")[-1]}
# else:
# res_data= {'code':-1, 'msg':"图片保存失败", "path":json.loads(res.text)['code']}
# print(res_data)
# # 关闭摄像头
# url = 'http://127.0.0.1:6543/video=close'
# param = {"camidx":"0"}
# header = {'Content-Type': 'application/json;charset=utf-8'}
# res = requests.post(url=url, data=json.dumps(param), headers=header,timeout=10)
# print('关闭摄像头:::::::',json.loads(res.text)['code'])
# stop_thread(thread=p)
# return res_data
# except Exception as e:
# print('报错信息: ' + e + ' ,File: ' + __file__ + ', Line ' + str(sys._getframe().f_lineno))
# return {'code':-1,'msg':e}
# # linux 版本
def getPic(self):
try:
# if path:
# self.path = path
# print('----------预览视频-------------------------')
# 打開視頻
url = "http://localhost:6543/StartPreview?dev_idx=0&res_id=0&pixfmt=pixfmt"
res = requests.get(url=url)
print(res.text)
# print(json.loads(res.text)['data'])
# print(json.loads(res.text)['code']=='0')
if json.loads(res.text)['returnCode'] != 0:
return {'code': -1, 'msg': "打开视频失败"}
time.sleep(5)
# print('----------拍照-------------------------',__file__)
# 打開視頻
url = "http://localhost:6543/getPic?savepath=" + self.path + "&quality=80"
res = requests.get(url=url)
# print(json.loads(res.text)['data']["path"])
# print(json.loads(res.text)['returnCode']==0) /home/yanyi/Project/MainManage/img/2022-9-9 15-36-35.jpg
if json.loads(res.text)['returnCode'] == 0:
res_data = {'code': 0, 'msg': "图片保存成功", "path": json.loads(res.text)['data']["path"].split("/")[-1]}
else:
res_data = {'code': -1, 'msg': "图片保存失败", "path": json.loads(res.text)['returnCode']}
# print('----------关闭视频-------------------------')
url = "http://localhost:6543/StopPreview?dev_idx=0"
res = requests.get(url=url)
return res_data
except Exception as e:
print('报错信息: ' + str(e) + ' ,File: ' + __file__ + ', Line ' + str(sys._getframe().f_lineno))
return {'code': -1, 'msg': "高拍仪连接失败,请打开高拍仪地址重新连接一下,{0}".format(str(e))}
if __name__ == "__main__":
obj = GaoPaiYi()
obj.getPic()
path = '/tmp'
# pic_url = GaoPaiYi().getPic(path)
# print(pic_url)

@ -0,0 +1,216 @@
import binascii
import re
import time
import serial
from time import sleep
import threading
class SerialPort(object):
def __init__(self,Name,BaudRate,timeout=0.1):
# 串口通信同步锁
self.lockSend=threading.Lock()
self.ser = None
self.encoding = '' # 编码格式
try:
self.ser = serial.Serial(Name, BaudRate, timeout=timeout,)
except Exception as e:
print('serialserialserial',str(e))
# 发送的指令'00 01 00 00 23 ...'
def send_cmd_hex(self,cmd,readCount=0):
try:
self.lockSend.acquire()
data=None
cmd = bytes.fromhex(cmd) # 将发送的
retry_times = 0 # 默认读取一次
self.ser.flushInput()
self.ser.write(cmd)
time.sleep(0.2)
while retry_times ==0:
count = self.ser.inWaiting() #获取接收缓存区的字节数
retry_times += 1
if(readCount==0):
data = self.ser.readall()
if data == None:
continue
else:
break
else:
if(readCount==1):
data = self.ser.read(count)
else:
data = self.ser.read(readCount)
if data == None:
continue
else:
break
#sleep(0.002)
# self.ser.flushInput()
#print('ggggggggggg::',data) # b'\x01\x03\x04\x00\x00\x00\x00\xfa3'
return data
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
# 发送给的指令 #~000~qst~start~@对字符串进行转换成byte类型再发送传给
def send_cmd(self,cmd,readCount=0):
try:
self.lockSend.acquire()
data=None
if self.encoding:
cmd = cmd.encode(self.encoding)
else:
cmd = cmd.encode()
retry_times = 0 # 默认读取一次
self.ser.flushInput()
self.ser.write(cmd)
time.sleep(0.2)
while retry_times ==0:
count = self.ser.inWaiting() #获取接收缓存区的字节数
retry_times += 1
if(readCount==0):
data = self.ser.readall()
if self.encoding:
data = data.decode(self.encoding)
else:
data = data.decode()
if data == None:
continue
else:
break
else:
if(readCount==1):
data = self.ser.read(count)
else:
data = self.ser.read(readCount)
if data == None:
continue
else:
break
#sleep(0.002)
# self.ser.flushInput()
print('ggggggggggg::',data)
return data.replace("\r\n", '')
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
def Read(self,readCount=0):
try:
self.lockSend.acquire()
data=None
while True:
count = self.ser.inWaiting() #获取接收缓存区的字节数
if(readCount==0):
data = self.ser.readall()
if data == None:
continue
else:
break
else:
if(readCount==1):
data = self.ser.read(count)
else:
data = self.ser.read(readCount)
if data == None:
continue
else:
break
#sleep(0.002)
# self.ser.flushInput()
return data
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
def ReadAll(self,readCount=0):
try:
self.lockSend.acquire()
data=None
while True:
#count = self.ser.inWaiting() #获取接收缓存区的字节数
if(readCount==0):
data = self.ser.readall()
if data == None:
continue
else:
break
else:
data = self.ser.read(readCount)
if data == None:
continue
else:
break
#sleep(0.002)
#self.ser.flushInput()
return data
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
def FlushInput(self): #清空缓存
try:
self.lockSend.acquire()
self.ser.flushInput()
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
def Write(self,data):
try:
self.lockSend.acquire()
self.ser.flushInput()
self.ser.write(data)
except Exception as e:
raise e
except OSError as e:
raise e
finally:
self.lockSend.release()
def Close(self):
try:
self.ser.close()
except Exception as e:
print('serialClose:',e)
if __name__ == "__main__":
# sp = SerialPort.SerialPort('/dev/ttyS0',115200)
# while True:
# #cmd=input('输入发送:')
# sp.Write('#~000~qst~lock0~@\r\n'.encode())
# #print("发送:"+cmd)
# d = sp.Read()
# print("接收:"+bytes.decode(d))
# import SerialPort
ser = SerialPort('com1', 9600)
# ser = SerialPort('/dev/ttyUSB0', 9600)
print('rrrrrrrrrrrrrrrrrrrrrrrrrrrr:::::',ser)
cmd = "01 03 12 07 00 14 F1 7C"
data = ser.send_cmd_hex(cmd)
print('ssssssssssssssssssssssssssssssssssssssss:::',data)

@ -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.

@ -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,207 @@
import cv2
import time
import threading
import platform
import os
import shutil
# 背景介绍:
# 1、采用USB摄像头后每台柜子的成本能降低200元以每年300台柜子的比例来说一年就能节约60000元由于旧的USB摄像头容易松动从而导致售后问题只要每年因为摄像头的售后费用低于60000元采用USB摄像头是可行的
# 2、旧的3.0系统发了很多的客户主柜基本都是USB摄像头考虑这批客户的USB摄像头都不能使用(大约数百个至1000个左右)全部采用海康的摄像头后一是增加了成本二是大量的旧件无法使用故需要考虑在5.0系统中集成USB摄像头
#
# 其他:
# 1、5.0的方案中由于下位机采用了网络通讯,故交换机并不会受到摄像头方案切换的影响(不能带入方案成本计算中)
# 2、5.0的方案中由于考虑了副柜的情况故如果有副柜的主柜常常会配置扩展硬盘常见是1TB用于视频录制在摄像头方案的成本的时候只有主柜才会有扩展硬盘硬盘选择与否与摄像头是网络摄像头还是USB摄像头无关
# 3、如果有其他人问到USB摄像头和网络摄像头的方案技术选择问题请转述上述的2个观点如果有动机相关的问题请转述“背景介绍”中的内容
#
# 目前的方案如下共2种
# 1、RTSP -> SRS -> liveStream -> OpenCv -> Detect -> MP4 (RMS5.0初代,只支持网络摄像头)
# + -> webrtc前端渲染
#
# 2、USB Camera -> VLC -> liveStream -> OpenCv -> Detect -> MP4 支持USB摄像头和网络摄像头
# + -> 前端渲染
#
# 主柜采用黑色的USB摄像头需要注意
# 1、当主柜的人脸USB摄像头也接入的时候camera_path可能需要调整或者通过udev绑定设备
# 2、如果有其他的程序使用了大量的磁盘空间可能会导致所有的视频文件被删除
# 3、人脸和人体检测会导致CPU负载上升
class Camera:
def __init__(self,debug_flag=False,camera_path=0,folder_path="usb_camera",face_detect=False,body_detect=False):
# 用于本地调试
self.debug = debug_flag
# 人脸检测和上半身检测开关
self.face_detect=face_detect
self.body_detect=body_detect
# 获取操作系统类型
self.sys_plat = platform.system().lower()
# 初始化Linux下的视频文件夹
self.video_folder_in_linux = "/data/"+folder_path
self.init_linux_video_folder()
# 初始化USB摄像头
self.cap = cv2.VideoCapture(camera_path)
# camera_worker状态控制
self.worker_status = False
# 获取摄像头的宽度和高度
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 视频编解码器和VideoWriter对象
self.fourcc = cv2.VideoWriter_fourcc(*'mp4v')
self.out = None
# 初始化前一帧、移动侦测状态和人脸检测器
self.ret, frame1 = self.cap.read()
self.prev_frame = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
self.motion_detected = False
self.motion_start_time = None
# 人脸检测
self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
# 上半身检测
self.upperbody_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_upperbody.xml')
def start(self):
self.worker_status=True
p = threading.Thread(target=self.camera_worker)
p.start()
def camera_worker(self):
while self.worker_status:
ret, frame2 = self.cap.read()
if not ret:
break
# 将当前帧转换为灰度图像
curr_frame = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
# 计算当前帧和前一帧的差异
frame_diff = cv2.absdiff(curr_frame, self.prev_frame)
# 应用阈值处理
_, thresh = cv2.threshold(frame_diff, 30, 255, cv2.THRESH_BINARY)
# 找到轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 移动侦测并绘制矩形框显示移动区域(调试)
for contour in contours:
if cv2.contourArea(contour) > 1000:
if self.debug:
(x, y, w, h) = cv2.boundingRect(contour)
cv2.rectangle(frame2, (x, y), (x+w, y+h), (0, 255, 0), 2)
self.motion_detected = True
self.motion_start_time = time.time()
# 人脸和上半身检测(注意性能)
faces=[]
upperbodies=[]
if self.face_detect:
faces = self.face_cascade.detectMultiScale(curr_frame, 1.1, 4)
if self.body_detect:
upperbodies = self.upperbody_cascade.detectMultiScale(curr_frame, 1.1, 4)
# 如果检测到移动、人脸、上半身,则开始录制视频
if self.motion_detected or len(faces) > 0 or len(upperbodies) > 0:
self.motion_start_time = time.time()
if self.out is None:
filename = self.new_video_filename()
self.out = cv2.VideoWriter(filename, self.fourcc, 20.0, (self.width, self.height))
self.out.write(frame2)
else:
if self.motion_start_time is not None and time.time() - self.motion_start_time > 3:
if self.out is not None:
self.out.release()
self.out = None
# 如果是Linux如果磁盘空间超过阈值则删除目录下的文件
if self.sys_plat == "linux":
self.delete_oldest_mp4_files(self.video_folder_in_linux)
if self.debug:
# 显示结果
cv2.imshow('Motion Detection', frame2)
# 更新前一帧和移动侦测状态
self.prev_frame = curr_frame
self.motion_detected = False
if self.debug:
# 按下q键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
def stop(self):
self.worker_status=False
# 释放资源
# self.cap.release()
if self.out is not None:
self.out.release()
if self.debug:
cv2.destroyAllWindows()
def new_video_filename(self):
if self.sys_plat == "windows":
timestamp = time.strftime("%Y%m%d%H%M%S")
filename = "{}.mp4".format(timestamp)
else:
timestamp = time.strftime("%Y%m%d%H%M%S")
filename = "{}.mp4".format(timestamp)
filename = self.video_folder_in_linux+"/"+filename
return filename
def init_linux_video_folder(self):
if self.sys_plat == "linux":
self.create_directory(self.video_folder_in_linux)
# 递归创建文件夹
def create_directory(self,path):
if not os.path.exists(path):
parent_directory = os.path.dirname(path)
if parent_directory != '':
self.create_directory(parent_directory)
os.mkdir(path)
# 磁盘空间超过阈值的时候有且只删除最久的10个MP4文件
def delete_oldest_mp4_files(self,folder_path):
total, used, free = shutil.disk_usage(folder_path)
if (used / total) > 0.7:
mp4_files = []
for root, dirs, filenames in os.walk(folder_path):
for file in filenames:
if file.endswith(".mp4"):
mp4_files.append(os.path.join(root, file))
mp4_files.sort(key=os.path.getmtime)
for file in mp4_files[:10]:
os.remove(file)
class UsbCamera(Camera):
pass
class NetworkCamera(Camera):
def __init__(self, debug_flag=False, camera_path=0, folder_path="usb_camera", face_detect=False, body_detect=False):
super().__init__(debug_flag, camera_path, folder_path, face_detect, body_detect)
def start_service():
# 正常业务逻辑
from conf import setting
from models.hikvision import Hikvision
current_terminal_id = setting.TERMINAL_ID
camera_list = Hikvision.filter(terminal_id=current_terminal_id).all()
usb_cameras = list(filter(lambda x:x.camera_type==0,camera_list))
network_cameras = list(filter(lambda x:x.camera_type==1,camera_list))
if usb_cameras:
print("启动USB摄像头录制")
UsbCamera().start()
if network_cameras:
for c in network_cameras:
print(f"启动网络摄像头:{c.username}:{c.password}@{c.ip} , channel:{c.channel}")
NetworkCamera(False,f"rtsp://{c.username}:{c.password}@{c.ip}:554/Streaming/Channels/102",c.channel).start()
if __name__ == "__main__":
Camera(True).start()

@ -0,0 +1,410 @@
import os
import base64
import socket
from functools import lru_cache, wraps
from typing import Union
import json
import time
import math
import datetime
import platform
import subprocess
import ntpath
def compute_last_time(time1, ctime):
"""计算time1与当前时间的差值转换为D H M S格式"""
day, hour, mins, sec = 0, 0, 0, 0
dt1 = datetime.datetime.strptime(str(time1), '%Y-%m-%d %H:%M:%S')
dt2 = datetime.datetime.strptime(str(ctime), '%Y-%m-%d %H:%M:%S')
diff_str = str(dt2 - dt1)
if diff_str == '0:00:00':
return '0秒'
# 时、分、秒用小写字母h、m、s天用大写字母D
if 'day' not in diff_str:
sec, mins, hour = list(map(int, diff_str.split(":")))[::-1]
else:
day = diff_str.split(',')[0].split(' ')[0]
sec_min_hour = diff_str.split(",")[1].strip().split(":")
sec, mins, hour = list(map(int, sec_min_hour))[::-1]
day = int(day)
all_time = (F'{day}', F'{hour}', F'{mins}', F'{sec}')
# 处理为仅仅两个单位找到第一个非0的单位取两个值即可
start = 0
for i, e in enumerate(all_time):
if int(e[:-1]) != 0:
start = i
break
real_time_str = ' '.join(all_time[start : start + 2])
return real_time_str
def is_today_time(time1):
"""判断是否今天内发生的"""
today = str(datetime.datetime.today()).split(' ')[0] + ' 0:0:0'
dt1 = datetime.datetime.strptime(str(time1), '%Y-%m-%d %H:%M:%S')
dt2 = datetime.datetime.strptime(today, '%Y-%m-%d %H:%M:%S')
diff_str = str(dt1 - dt2)
if 'day' in diff_str:
return False, time1
else:
return True, time1.split(' ')[1]
def time_convert(s: int) -> str:
"""
秒转化为
:param s: int
:return: str 转化后的时间 数据加单位
"""
m, s = divmod(s, 60)
if not m:
return F"{s}"
h, m = divmod(m, 60)
if not h:
return F"{m}{s}"
d, h = divmod(h, 24)
if not d:
return F"{h}{m}"
return F"{d}{h}"
def time_convert_2_list(s: int) -> list:
"""
秒转化为
:param s: int
:return: list list[0]转化后的时间list[1]数据单位
"""
m, s = divmod(s, 60)
if not m:
return [s, ""]
h, m = divmod(m, 60)
if not h:
return [m, ""]
d, h = divmod(h, 24)
if not d:
return [h, ""]
ten, d = divmod(d, 10)
if not ten:
return [d, ""]
return [10, "天+"]
def byte_convert_2_kb(s):
"""
秒转化为
:param s: int byte
:return: float kb
"""
return round(float(s) / 1024, 3)
def ms_2_s(s):
"""
毫秒转为秒
"""
return int(round(float(s) / 1000, 0))
def bytes_to_tb(bytes: int) -> Union[int, float]:
tb = bytes / (1024**4)
tb_rounded = round(tb, 2)
if tb_rounded.is_integer():
return int(tb_rounded)
else:
return tb_rounded
class lazyproperty:
"""惰性属性"""
def __init__(self, fun):
self.fun = fun
def __get__(self, instance, owner):
if instance is None:
return self
value = self.fun(instance)
setattr(instance, self.fun.__name__, value)
return value
def ping_connect(ip, retry=1):
"""ping测试"""
system = platform.system()
cmd = f'ping -n 1 -w 1 {ip}' if system == 'Windows' else f'ping -c 3 {ip}'
return_code = -1
for i in range(retry):
with subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE) as p:
p.wait()
if p.returncode == 0:
return_code = p.returncode
break
time.sleep(1)
return return_code == 0
def Singleton(cls):
"""单例模式"""
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
class SingletonArguments(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonArguments, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def str2int(v):
try:
return int(v)
except Exception:
return v
def bytes2str(v):
try:
if isinstance(v, bytes):
return v.decode('utf8')
except Exception:
return v
def int2timestr(v):
try:
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(v / 1000))
except Exception as msg:
return v
def int2datetime(v):
try:
return datetime.datetime.fromtimestamp(v / 1000)
except Exception as msg:
return v
def str2datetime(v):
try:
return datetime.datetime.strptime(v, '%Y-%m-%s %H:%M:%S')
except Exception as msg:
return datetime.datetime.now()
def str2float(v):
try:
return float(v)
except Exception:
return v
def upper(v):
try:
return v.upper()
except Exception:
return v
def lower(v):
try:
return v.lower()
except Exception:
return v
def basename(path):
"""获取路径的文件名"""
return ntpath.basename(path)
def yanei_decimal(value: float, num: int):
"""保留num位有效数字
:param value:float: 待处理浮点数
:param num:int: 有效位数
"""
try:
if not isinstance(value, float):
return value
s = str(value)
pre, post = s.split('.')
return float(pre + '.' + post[:num])
except Exception:
return value
def seconds_format(time_cost: int):
"""
耗费时间格式转换
:param time_cost:
:return:
"""
min_value = 60
hour = 60 * 60
day = 60 * 60 * 24
if not time_cost or time_cost < 0:
return '0秒'
elif time_cost < min_value:
return '%s' % int(time_cost)
elif time_cost < hour:
# return '%s分%s秒' % (divmod(time_cost, min_value))
return '%s%s' % (divmod(int(time_cost), int(min_value)))
elif time_cost < day:
cost_hour, cost_min = divmod(int(time_cost), int(hour))
if cost_min > min_value:
return '%s%s' % (int(cost_hour), seconds_format(cost_min))
else:
return '%s时0分%s' % (int(cost_hour), seconds_format(cost_min))
else:
cost_day, cost_hour = divmod(int(time_cost), int(day))
if cost_hour >= hour:
return '%s%s' % (int(cost_day), seconds_format(cost_hour))
elif cost_hour >= min_value:
return '%s天0时%s' % (int(cost_day), seconds_format(cost_hour))
else:
return '%s天0时0分%s' % (int(cost_day), seconds_format(cost_hour))
def calculate_rto(end, start, ceil=True):
"""其中end, start都是ms级别的时间戳, ceil表示是否向上取整"""
total_miscro = float(end - start)
if ceil:
total = math.ceil(total_miscro / 1000)
else:
if total_miscro < 1000:
total = math.ceil(total_miscro / 1000)
else:
total = int(total_miscro / 1000)
return seconds_format(total)
def transform_duration_str_to_seconds(duration_str: str):
"""
将时长字符串转换为秒
:param duration_str: 时长字符串 eg: 1天2时3分4秒
:return:
"""
if not duration_str:
return None
try:
day, hour, minute, second = 0, 0, 0, 0
if "" in duration_str:
_values = duration_str.split("", 1)
day = int(_values[0])
duration_str = _values[-1]
if "" in duration_str:
_values = duration_str.split("", 1)
hour = int(_values[0])
duration_str = _values[-1]
if "" in duration_str:
_values = duration_str.split("", 1)
minute = int(_values[0])
duration_str = _values[-1]
if "" in duration_str:
_values = duration_str.split("", 1)
second = int(duration_str.split("")[0])
return day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second
except Exception:
return None
def extra_script_data_after_call(resp):
"""解析内容部门返回的响应数据并拼接成字符串"""
resp_data = ''
try:
overall = resp.get('overall')
_ts = resp.get('data', [])
resp_data = f'状态码:{overall} 其他信息:'
if isinstance(_ts, list):
for _t in _ts:
resp_data += f'{_t}\n'
except Exception:
try:
resp_data = f'{resp}'
except:
pass
return resp_data
def time_diff_int(str_time1, str_time2):
"""计算差值,返回秒数"""
# TODO 修改类型
if str(str_time2) == '2038-01-01 00:00:00':
raise Exception()
timearray = time.strptime(str_time1, "%Y-%m-%d %H:%M:%S")
int_time1 = int(time.mktime(timearray))
timearray = time.strptime(str_time2, "%Y-%m-%d %H:%M:%S")
int_time2 = int(time.mktime(timearray))
return int_time1 - int_time2
def timed_lru_cache(seconds: int, maxsize: int = 128):
def wrapper_cache(func):
func = lru_cache(maxsize=maxsize)(func)
func.lifetime = datetime.timedelta(seconds=seconds)
func.expiration = datetime.datetime.now() + func.lifetime
@wraps(func)
def wrapped_func(*args, **kwargs):
if datetime.datetime.now() >= func.expiration:
func.cache_clear()
func.expiration = datetime.datetime.now() + func.lifetime
return func(*args, **kwargs)
return wrapped_func
return wrapper_cache
def is_illegal_name(name):
illegal_list = ['+', "-", "=", "@"]
for i in illegal_list:
if i in name:
return True
return False
def get_local_ip():
try:
# 获取本机主机名
hostname = socket.gethostname()
# 获取本机IP地址
ip_address = socket.gethostbyname(hostname)
return ip_address
except socket.error:
return "Unable to get local IP"

@ -0,0 +1,338 @@
"""
此文件是用来生成Code128条形码 并实现打印功能
"""
import os
import uuid
import unicodedata
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtPrintSupport import QPrinter
from PyQt5.QtWidgets import QApplication
from pystrich.code128 import Code128Encoder
from conf import setting
class CreateBarcode:
"""
Code128Encoder(options={}) options参数
* ttf_font用于呈现标签的truetype字体文件的绝对路径
* ttf_fontsize绘制标签的字体大小
* label_border条形码和标签之间的像素空间数
* bottom_border标签和底部边框之间的像素空间数
* height图像的高度以像素为单位
* show_label是否在条形码下面显示标签默认为True
"""
code_path = os.path.join(os.getcwd(), "img")
if not os.path.exists(code_path):
os.umask(0)
os.makedirs(code_path)
def __init__(self):
self.archive_name = ""
def create_Code128_img(self, barcode, name='', manufacturer=''):
show_barcode_only = False
if self.archive_name == "耗材" or (self.archive_name == "普通试剂" and setting.BARCODE_COMMON_SHOW_ONLY):
show_barcode_only = True
a = Code128Encoder(
barcode,
options={
'ttf_font': os.path.join(os.getcwd(), 'arial.ttf'),
'label_border': 0,
'height': 140,
'bottom_border': 14,
'ttf_fontsize': 46 if show_barcode_only else 20,
},
)
# bar_width 条码宽度尺寸
file_name = str(uuid.uuid4()) + '.png'
a.save(file_name, bar_width=3)
if show_barcode_only:
self.printer_only_code(file_name)
else:
self.printer_code(file_name, name, manufacturer)
def printer_code(self, file_name, name, manufacturer):
a = QApplication([])
document = QTextDocument()
document.setDocumentMargin(0)
fontId = QFontDatabase.addApplicationFont(os.path.join(os.getcwd(), 'simsun.ttf'))
if len(name) >= 6:
name = name[:6] + '..'
if manufacturer and len(manufacturer) >= 6:
manufacturer = manufacturer[:6] + '..'
table_html = """
<div style='display:flex;width:100%'>&nbsp;&nbsp;</div>
<img src="{0}"><br/>
&nbsp; 试剂名称:{1} <br/>
&nbsp; 生产厂家:{2} <br/>
&nbsp; 开封时间: <br/>
&nbsp; 开封人:
""".format(
file_name, name, manufacturer
)
html = """
<head>
<title>Report</title>
<style>
table {{
width: 100%;
border-collapse: collapse;
}}
td {{
border: 1px solid black;
padding: 2px;
text-align: center;
font-size: 18px;
}}
div,br,p,span {{
white-span: nowrap;
word-warp: break-word;
overflow:hidden;
text-overflow: ellipsis;
}}
img {{
display: block;
margin: 0 auto;
}}
</style>
</head>
<body style='font-family:WenQuanYi Micro Hei;font-size:28px;line-height:22px;font-weight: bold;'>
{}
</body>
""".format(
table_html
)
document.setHtml(html)
printer = QPrinter()
printer.setPageSize(QPagedPaintDevice.Custom)
printer.setPaperSize(QSizeF(45.0, 30.0), QPrinter.Millimeter)
# 设置纸张到条码的边距 左上下右
printer.setPageMargins(5, 3, 0, 0, QPrinter.Millimeter)
document.setPageSize(QSizeF(400.0, 320.0))
print(document.pageSize(), printer.resolution(), printer.pageRect())
print('正在打印中。。。。')
document.print_(printer)
print('打印完成。。')
os.remove(file_name)
def create_drug_lobel_code(self, **kwargs):
a = Code128Encoder(
# kwargs.get("code_number"),
"111",
options={
'ttf_font': os.path.join(os.getcwd(), 'arial.ttf'),
'label_border': 0,
'height': 15,
'bottom_border': 0,
'ttf_fontsize': 0,
},
)
# bar_width 条码宽度尺寸
file_name = os.path.join(self.code_path, str(uuid.uuid4()) + '.png')
print(file_name)
a.save(file_name, bar_width=1)
kwargs["file_path"] = file_name
self.printer_drug_label(**kwargs)
def zhsy_create_drug_label_code(self, **kwargs):
"""
珠海食药打印配液标签
:param kwargs:
:return:
"""
a = Code128Encoder(
kwargs.get("code_number"),
options={
'ttf_font': os.path.join(os.getcwd(), 'arial.ttf'),
'label_border': 0,
'height': 15,
'bottom_border': 0,
'ttf_fontsize': 0,
},
)
file_name = os.path.join(self.code_path, str(uuid.uuid4()) + '.png')
a.save(file_name, bar_width=1)
kwargs["file_path"] = file_name
self.zhsy_printer_drug_label(**kwargs)
def printer_drug_label(self, **kwargs):
a = QApplication([])
document = QTextDocument()
# <p style="font-size:12px;">
html = """
<head>
<title>Report</title>
<style></style>
</head>
<body>
<div style="display:flex; align-content: center;width:100%">
<p style="font-size:6px; width: 100%;">
<span style="font-size:8px;width: 100%;">华润三九制药</span> <br />
试剂名称: {} <br />
级别: {} <br />
批号: {} <br />
编号: {} <br />
货位号: {} {} <br />
<img src="{}">
</p>
</div>
</body>
""".format(
kwargs.get("name"),
kwargs.get("purity"),
kwargs.get("standard_code"),
kwargs.get("remark12"),
kwargs.get("client_name"),
kwargs.get("flow_position_code"),
kwargs.get("file_path"),
)
document.setHtml(html)
printer = QPrinter()
printer.setPageSize(QPagedPaintDevice.Custom)
# printer.setPaperSize(QSizeF(60.0,40.0),QPrinter.Millimeter)
printer.setPaperSize(QSizeF(30.0, 18.0), QPrinter.Millimeter)
# printer.setPaperSize(QSizeF(30.0,50.0),QPrinter.Millimeter)
# 设置纸张到条码的边距 左上下右
printer.setPageMargins(6, 3, 0, 0, QPrinter.Millimeter)
# printer.setPageMargins(20, 20, 0, 0, QPrinter.Millimeter)
document.setPageSize(QSizeF(printer.pageRect().size()))
# document.setPageSize(QSizeF(50.0,30.0))
print('正在打印中。。。。')
document.print_(printer)
print('打印完成。。')
os.remove(kwargs.get("file_path"))
def zhsy_printer_drug_label(self, **kwargs):
print(kwargs.get("file_path"), 55555555)
print('sss:', kwargs)
a = QApplication([])
document = QTextDocument()
html = """
<head>
<title>Report</title>
<style></style>
</head>
<body>
<div>
<p style="font-size:11px;">
<span style="font-size:13px;">珠海市食品药品检验所</span> <br />
名称: {} <br />
编号: {} <br />
浓度: {} &nbsp;&nbsp;溶剂:{}<br />
配置日期: {} <br />
{} <br />
{} <br />
</p>
<img src="{}">
</div>
</body>
""".format(
kwargs.get("name")[:12],
kwargs.get("code_number"),
kwargs.get("purity")[:10],
kwargs.get("solvent"),
kwargs.get("start_time")[:10],
kwargs.get("end_time")[:10],
kwargs.get("user_name"),
kwargs.get("file_path"),
)
print(html)
document.setHtml(html)
printer = QPrinter()
printer.setPageSize(QPagedPaintDevice.Custom)
printer.setPaperSize(QSizeF(60.0, 40.0), QPrinter.Millimeter)
# printer.setPaperSize(QSizeF(30.0,50.0),QPrinter.Millimeter)
# 设置纸张到条码的边距 左上下右
printer.setPageMargins(6, 2, 0, 0, QPrinter.Millimeter)
document.setPageSize(QSizeF(printer.pageRect().size()))
print('正在打印中。。。。')
document.print_(printer)
print('打印完成。。')
def printer_only_code(self, file_name):
a = QApplication([])
document = QTextDocument()
document.setDocumentMargin(0)
fontId = QFontDatabase.addApplicationFont(os.path.join(os.getcwd(), 'simsun.ttf'))
table_html = """
<div style='display:flex;width:100%'>&nbsp;&nbsp;</div>
<img src="{0}"><br/>
""".format(
file_name
)
html = """
<head>
<title>Report</title>
<style>
table {{
width: 100%;
border-collapse: collapse;
}}
td {{
border: 1px solid black;
padding: 2px;
text-align: center;
font-size: 18px;
}}
div,br,p,span {{
white-span: nowrap;
word-warp: break-word;
overflow:hidden;
text-overflow: ellipsis;
}}
img {{
display: block;
margin: 0 auto;
}}
</style>
</head>
<body style='font-family:WenQuanYi Micro Hei;font-size:35px;line-height:22px;font-weight: bold;'>
{}
</body>
""".format(
table_html
)
document.setHtml(html)
printer = QPrinter()
printer.setPageSize(QPagedPaintDevice.Custom)
printer.setPaperSize(QSizeF(30.0, 18.0), QPrinter.Millimeter)
# 设置纸张到条码的边距 左上下右
printer.setPageMargins(0, 5, 0, 0, QPrinter.Millimeter)
document.setPageSize(QSizeF(400.0, 230.0))
print(document.pageSize(), printer.resolution(), printer.pageRect())
print('正在打印中。。。。')
document.print_(printer)
print('打印完成。。')
os.remove(file_name)
if __name__ == '__main__':
kwS = {
"code_number": "200001",
"medicament_name": "1111111",
"purity": "12",
"solvent": "2313",
"start_time": "2022-10-15",
"end_time": "2022-10-16",
"user_name": "测试账户",
}
# CreateBarcode().create_drug_lobel_code(**kwS)
CreateBarcode().create_Code128_img('123456', '硫酸', '研一')
# CreateBarcode().create_Code128_img("200000001")

@ -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,187 @@
# -*- coding: utf-8 -*-
# @Time : 2023/9/26 10:58
# @Author : tx
# @File : base.py
# @Description :
import string
from io import BytesIO
import urllib.parse
from openpyxl.workbook import Workbook
from openpyxl.styles import Alignment, Side, Border, Font, PatternFill
from helper.utils import timezone_now
from helper import usb
from datetime import datetime, timezone
class ReportExport:
def __init__(self):
self.title = ""
self.stateDict = {}
# 新建一个workbook
self.wb = Workbook()
# 定义设置样式
self.a = 0
self.styleTrue = '\u2611' # 打勾
self.styleFalse = '\u25A1' # 空框
def set_style(self, title='sheet1', ty=1):
# 如果是第一次调用则删除默认创建的sheet表
if self.a == 0:
self.wb.remove(self.wb['Sheet'])
self.a += 1
# 创建第几个sheet
self.ws = self.wb.create_sheet()
# 设置sheet的标题
self.ws.title = title
# 设置1 2 3 行固定的高度
self.ws.row_dimensions[1].height = 35
self.ws.row_dimensions[2].height = 25
self.ws.row_dimensions[3].height = 28
# 水平对齐, 居中对齐
self.alignment_style2 = Alignment(
horizontal='left', vertical='top', wrapText=True)
self.alignment_style = Alignment(
horizontal='center', vertical='center', wrapText=True)
# 定义Border边框样式
left, right, top, bottom = [Side(style='thin', color='000000')] * 4
self.border_style = Border(
left=left, right=right, top=top, bottom=bottom)
# 设置列宽
# 生成前14个大写字母 ascii_uppercase生成所有大写字母
self.upper_string = string.ascii_uppercase[:15]
# 定义字体
self.font_size = Font(size=9)
width_list = [20, 15, 10, 15, 15, 22, 15, 22, 15, 15]
for col in self.upper_string:
try:
width = width_list[self.upper_string.index(col)]
except:
width = 20
self.ws.column_dimensions[col].width = width
def editor_state(self, col_):
"""
状态解析
:param col_:
:return:
"""
self.max_lines = self.ws.max_row
for row_ in range(2, self.max_lines + 1):
b = self.ws[col_ + str(row_)].value
if "状态" in str(b):
continue
self.ws[col_ + str(row_)].value = self.stateDict.get(b)
def create_row(self, data_list,title=''):
if title:
n=2
self.ws.merge_cells(start_row=1, end_row=1,
start_column=1, end_column=len(data_list))
# 写入值
self.ws.cell(row=1, column=1).value = title
self.ws['A1'].alignment = self.alignment_style
self.ws['A1'].font = Font(size=16, bold=True)
else:
n=1
for data in range(n, len(data_list) + n):
if n ==2:
data =data-1
# 第一行写入值
self.ws.cell(row=n, column=data).value = data_list[data - 1]
# 设置文本水平居中, 垂直居中
self.ws.cell(row=n, column=data).alignment = self.alignment_style
# 设置字体加粗
self.ws.cell(row=n, column=data).font = Font(bold=True, size=9)
# 边框样式
self.ws.cell(row=n, column=data).border = self.border_style
def create_multiple_rows(self, start_row, data_list, keys_list):
# 从第2行创建
for row_number in range(start_row, len(data_list) + start_row):
# 设置每一行的行高
self.ws.row_dimensions[row_number].height = 60
# 遍历每一个对象的长度
col_ = 1
for key_ in keys_list:
# 写入值
try:
if key_ =='ysr':
value ='李世光'
elif key_ =='info':
value=f'{self.styleTrue}标识清晰\r\n{self.styleTrue}外观完整、无破损\r\n{self.styleTrue}形状无明显改变\r\n{self.styleTrue}符合申购要求\r\n{self.styleFalse}其他'
elif key_ =='jg':
value=f'{self.styleTrue}合格\r\n{self.styleFalse}不合格'
else:
value = data_list[row_number - start_row][key_]
if isinstance(value, datetime) and value.tzinfo is not None:
# 如果是带有时区信息的datetime对象则移除时区信息
value = value.replace(tzinfo=None)
except:
value = "/"
self.ws.cell(row=row_number, column=col_).value = value
# 设置文本水平居中, 垂直居中
if key_ == "acceptace" or key_ == "info" or key_ == "jg":
self.ws.cell(row=row_number, column=col_).alignment = self.alignment_style2
else:
self.ws.cell(row=row_number, column=col_).alignment = self.alignment_style
# 设置边框样式
self.ws.cell(row=row_number, column=col_).border = self.border_style
# 设置字体大小
self.ws.cell(row=row_number, column=col_).font = Font(size=9)
col_ += 1
def build_file(self,title='', **kwargs):
self.set_style(title="Sheet")
# 数据条目
data_list = kwargs.pop("data_list")
# 英文列名
key_list = kwargs.pop("key_list")
# 中文列名
finds_name = kwargs.pop("finds_list")
# 创建标题
self.create_row(finds_name,title)
# 创建数据
if title:
n=3
else:
n=2
self.create_multiple_rows(n, data_list, key_list)
for i in key_list:
if "state" in i:
# 如果行超过25行异常处理
if key_list.index(i) <= 25:
self.editor_state(string.ascii_uppercase[key_list.index(i)])
else:
# AA AB AC AD...
divisor = key_list.index(i) // 25 - 1
string_divisor = string.ascii_uppercase[divisor]
remainder = key_list.index(i) % 25 - 1
string_remainder = string.ascii_uppercase[remainder]
self.editor_state(f"{string_divisor}{string_remainder}")
def export(self, data, download_type='usb'):
"""
导出
:param data:
:param download_type:
:return:
"""
binary = BytesIO()
self.wb.save(binary)
self.wb.close()
binary.seek(0)
filename = f"{timezone_now().strftime('%Y%m%d%H%M%S')}-{self.title}-报表.xlsx"
if download_type == 'usb':
try:
usb.put_in(filename, binary)
return 200, "导出成功"
except usb.DeviceNotFound:
return 404, "U盘未找到"
encoded_filename = urllib.parse.quote(filename)
return binary, encoded_filename

@ -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, {})

@ -0,0 +1,75 @@
from helper.SerialPort import *
from collections import Counter
import time
import threading
import binascii
# 移植来自RMS3.0的超高频SimpleRFIDReader方法中的超高频UHF部分
class OutSideUHFReader:
def __init__(self, com="/dev/ttyUSB0"):
self.seria = None
self.rfidData=''
self.taskKillFlag=True
self.noCount=0
self.bqz=''
try:
self.seria = SerialPort(com, 115200,0.1)
print('小型FID初始化成功')
except Exception as ex:
print('小型FID初始化失败' + str(ex))
# 读取
def read(self):
self.seria.Write(bytes.fromhex("A0 04 FF 89 01 D3"))
while True:
data = self.seria.Read()
if data == None:
continue
else:
break
val = str(binascii.b2a_hex(data).decode())
# print('valvalval:',val)
if len(val) == 66:
# 不同的超高频的标签的UID位置可能不一样注意
bq = val[14:38]
self.bqz=bq
self.noCount=0
else:
self.noCount+=1
if(self.noCount>=3):
self.bqz = ""
# print(bqz)
return self.bqz
def getData(self):
return self.rfidData
def readDataThread(self):
while not(self.taskKillFlag):
try:
dataStr=self.read()
if(dataStr):
self.rfidData=dataStr
else:
self.rfidData=''
time.sleep(0.1)
except Exception as e:
print(str(e))
# 启动服务
def start(self):
self.rfidData=''
self.taskKillFlag=False
if self.seria.ser:
p = threading.Thread(target=self.readDataThread)
p.start()
else:
print("打开串口设备失败!")
# 停止服务
def stop(self):
self.taskKillFlag=True
self.rfidData=''
rfid_reader = OutSideUHFReader()
rfid_reader.start()

@ -0,0 +1,330 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Date:2022/08/27 11:05:06
'''
import sys
sys.path.append('.')
import string
import os
import datetime
from openpyxl.workbook import Workbook
from openpyxl.styles import Alignment, Side, Border, Font, PatternFill
class ReportData:
def __init__(self):
# 新建一个workbook
self.wb = Workbook()
# 定义设置样式
self.a = 0
def set_style(self, title='sheet1'):
# 如果是第一次调用则删除默认创建的sheet表
if self.a == 0:
self.wb.remove(self.wb['Sheet'])
self.a += 1
# 创建第几个sheet
self.ws = self.wb.create_sheet()
# 设置sheet的标题
self.ws.title = title
# 设置1 2 3 行固定的高度
self.ws.row_dimensions[1].height = 35
self.ws.row_dimensions[2].height = 25
self.ws.row_dimensions[3].height = 28
# 水平对齐, 居中对齐
self.alignment_style = Alignment(
horizontal='center', vertical='center')
# 定义Border边框样式
left, right, top, bottom = [Side(style='thin', color='000000')] * 4
self.border_style = Border(
left=left, right=right, top=top, bottom=bottom)
# 设置列宽
# 生成前14个大写字母 ascii_uppercase生成所有大写字母
self.upper_string = string.ascii_uppercase[:15]
# 定义字体
self.font_size = Font(size=9)
for col in self.upper_string:
self.ws.column_dimensions[col].width = 20
# 创建第一行
def create_row1(self, value, length):
# 合并第1行1-14列单元格
self.ws.merge_cells(start_row=1, end_row=1,
start_column=1, end_column=length)
# 写入值
self.ws.cell(row=1, column=1).value = value
self.ws['A1'].alignment = self.alignment_style
self.ws['A1'].font = Font(size=16, bold=True)
self.create_row2(length)
def create_row2(self, length):
# 第2行写入值
now_time = datetime.datetime.now().strftime('%Y%m%d %H%M')
# 格式化时间为中文年月日
now_time = now_time[:4] + '' + now_time[4:6] + '' + \
now_time[6:8] + '' + now_time[8:11] + '' + now_time[11:13] + ''
self.ws.cell(row=2, column=1).value = '报表导出时间:{}'.format(now_time)
self.ws.cell(row=2, column=3).value = '终端系统版本:3.1'
self.ws.cell(row=2, column=5).value = ''
self.ws.cell(row=2, column=7).value = '报表导出位置:终端'
# 合并单元格
x = 1
while x < 6:
self.ws.merge_cells(start_row=2, end_row=2,
start_column=x, end_column=x + 1)
x += 2
self.ws.merge_cells(start_row=2, end_row=2,
start_column=7, end_column=length)
# 遍历取1, 3, 5, 7为第二行添加样式
for x in range(1, 8, 2):
self.ws.cell(row=2, column=x).font = Font(size=9)
# 水平对齐 垂直对齐
self.ws.cell(row=2, column=x).alignment = self.alignment_style
if x < 7:
# 边框样式
self.ws.cell(row=2, column=x).border = self.border_style
self.ws.cell(row=2, column=x + 1).border = self.border_style
else:
# 因为第2行第7-14列是合并的单元格 所以需要循环添加边框样式
while x < length + 1:
self.ws.cell(row=2, column=x).border = self.border_style
x += 1
# 创建第三行, 需要传入一个列表用来写入单元格的值
def create_row3(self, data_list):
for data in range(1, len(data_list) + 1):
# 第三行写入值
self.ws.cell(row=3, column=data).value = data_list[data - 1]
# 设置文本水平居中, 垂直居中
self.ws.cell(row=3, column=data).alignment = self.alignment_style
# 设置字体加粗
self.ws.cell(row=3, column=data).font = Font(bold=True, size=9)
# 背景颜色
self.ws.cell(row=3, column=data).fill = PatternFill(
fill_type='solid', fgColor='EE9A49')
# 边框样式
self.ws.cell(row=3, column=data).border = self.border_style
# 创建多行固定样式 start_row从第几行开始有规律 传入一个数据库获取的列表对象的值, 传入一个数据对象keys列表
def create_multiple_rows(self, start_row, data_list, keys_list):
# 从第4行创建
for row_number in range(start_row, len(data_list) + start_row):
# 设置每一行的行高
self.ws.row_dimensions[row_number].height = 18
# 遍历每一个对象的长度
col_ = 1
for key_ in keys_list:
# 写入值
try:
# 判断是否时datetime 类型,
if isinstance(dict(data_list[row_number - start_row]).get(key_), datetime.datetime):
value = dict(data_list[row_number - start_row]).get(key_).replace(tzinfo=None)
# 判断是否时state
elif key_=='state':
value = dict(data_list[row_number - start_row]).get(key_).value
else:
value = dict(data_list[row_number - start_row]).get(key_)
except :
value = "/"
self.ws.cell(row=row_number, column=col_).value = value
# 设置文本水平居中, 垂直居中
self.ws.cell(row=row_number,
column=col_).alignment = self.alignment_style
# 设置边框样式
self.ws.cell(row=row_number,
column=col_).border = self.border_style
# 设置字体大小
self.ws.cell(row=row_number, column=col_).font = Font(size=9)
col_ += 1
# 保存文件
def save(self, file_path):
self.wb.save('{}.xlsx'.format(file_path))
self.close()
# 关闭文件
def close(self):
self.wb.close()
# 替换空白
def replace_space(self, length):
self.max_lines = self.ws.max_row
upper_letter_str = string.ascii_uppercase[:length]
for upper_letter in upper_letter_str:
print(upper_letter)
for row_ in range(1, self.max_lines + 1):
b = self.ws[upper_letter + str(row_)].value
# print(b)
if not b:
self.ws[upper_letter + str(row_)].value = 'null'
# 编辑excel表格中列药剂状态1 2 3 替换为在库 出库
def editor_status(self, col_):
self.max_lines = self.ws.max_row
for row_ in range(4, self.max_lines + 1):
b = self.ws[col_ + str(row_)].value
if b == 1:
self.ws[col_ + str(row_)].value = '在库'
elif b == 0:
self.ws[col_ + str(row_)].value = '待入库'
elif b == 2:
self.ws[col_ + str(row_)].value = '出库'
elif b == 3:
self.ws[col_ + str(row_)].value = '空瓶'
elif b == 4:
self.ws[col_ + str(row_)].value = '销毁'
# 编辑 温度报警类型 01 替换为正常和异常
def eidtor_temperature_type(self,col_):
self.max_lines = self.ws.max_row
for row_ in range(4,self.max_lines +1):
b = self.ws[col_ + str(row_)].value
if b==0:
self.ws[col_ + str(row_)].value = "正常"
elif b ==1 :
self.ws[col_ + str(row_)].value = "异常"
# 编辑药剂重点监管列为1 0 替换为是 否
def editor_isSupervise(self, col_):
self.max_lines = self.ws.max_row
for row_ in range(4, self.max_lines + 1):
b = self.ws[col_ + str(row_)].value
if b == 1:
self.ws[col_ + str(row_)].value = ''
elif b == 0:
self.ws[col_ + str(row_)].value = ''
# 编辑操作类型 1 2 3 入库 领用 归还
def editor_RecordType(self, col_):
self.max_lines = self.ws.max_row
for row_ in range(4, self.max_lines + 1):
b = self.ws[col_ + str(row_)].value
if b == 1:
self.ws[col_ + str(row_)].value = '入库'
elif b == 2:
self.ws[col_ + str(row_)].value = '领用'
elif b == 3:
self.ws[col_ + str(row_)].value = '归还'
# 替换sqlAlchemy时间格式
def editor_time(self, keys_list, params):
self.max_lines = self.ws.max_row
for row_ in range(4, self.max_lines + 1):
col_ = string.ascii_uppercase[keys_list.index(params)]
b = self.ws[col_ + str(row_)].value
# print('sssssssssssssssssssssssssssssssssssssssssassssssssssssssss:::', b,type(b))
try:
if len(b) == 19:
self.ws[col_ + str(row_)].value = b.replace('T', ' ')
except Exception as e :
pass
# print(e)
# 编辑用户管理列为1 0 替换为正常 禁用
def editor_user_status(self, col_):
self.max_lines = self.ws.max_row
for row_ in range(4, self.max_lines + 1):
b = self.ws[col_ + str(row_)].value
if b == 1:
self.ws[col_ + str(row_)].value = '正常'
elif b == 0:
self.ws[col_ + str(row_)].value = '禁用'
# 构建文件内容
def build_file(self,**kwargs):
data_list = kwargs.pop("data_list")
key_list = kwargs.pop("key_list")
finds_name = kwargs.pop("finds_name")
self.set_style(title="Sheet")
file_name = kwargs.pop("file_name")
self.create_row1(file_name, len(finds_name))
self.create_row3(finds_name)
self.create_multiple_rows(4, data_list, key_list)
for i in key_list:
if "date" in i:
self.editor_time(key_list, i)
if "state" in i:
self.editor_status(string.ascii_uppercase[key_list.index(i)])
if "temperature_type" in i:
self.eidtor_temperature_type(string.ascii_uppercase[key_list.index(i)])
if "is_supervise" in i:
self.editor_isSupervise(string.ascii_uppercase[key_list.index(i)])
if "record_type" == i:
self.editor_RecordType(
string.ascii_uppercase[key_list.index(i)])
return self
# 下载文件
@staticmethod
def download_excel(filename, chunk_size=512):
try:
f = open(filename, 'rb')
except:
return
while True:
c = f.read(chunk_size)
if c:
yield c
else:
f.close()
os.remove(filename)
break
# # 导出用户数据报表接口
# @dataReport.route('/downloadUserReport', methods=["GET", "POST"])
# def downloadUserReport():
# try:
# uPath = Utils.getUDiskPath()
# if (uPath == ''):
# return jsonify(Utils.resultData(1, '未检测到U盘'))
# else:
# data_user_list = BllUser().findList().all()
# data_user_list = json.loads(
# Utils.resultAlchemyData(data_user_list))
# # 创建一个报表类实例
# a = ReportData()
# a.set_style(title='Sheet')
# data_list = ['工号', '角色', '姓名', '性别', 'QQ', '手机', '邮箱',
# '条码', '状态', '最后一次登录时间', ]
# a.create_row1('用户角色数据统计表', len(data_list))
# a.create_row3(data_list)
# keys_list = ['UserCode', 'RoleName', 'RealName', 'Sex', 'QQ', 'Mobile', 'Email',
# 'BarCode', 'IsEnabled', 'LastVisitDate', ]
# a.create_multiple_rows(4, data_user_list, keys_list)
# a.replace_space(len(data_list))
# a.editor_time(keys_list, 'LastVisitDate')
# # 判断用户角色
# for row_ in range(4, a.max_lines + 1):
# col_ = string.ascii_uppercase[keys_list.index('UserCode')]
# b = a.ws[col_ + str(row_)].value
# if b == 'admin':
# for col_value in range(1, len(keys_list) + 1):
# a.ws.cell(row=row_, column=col_value).fill = PatternFill(
# fill_type='solid', fgColor='FF0000')
# elif b == 'yanyi':
# for col_value in range(1, len(keys_list) + 1):
# a.ws.cell(row=row_, column=col_value).fill = PatternFill(
# fill_type='solid', fgColor='FFFF00')
# # 优化用户性别
# for row_ in range(4, a.max_lines + 1):
# col_ = string.ascii_uppercase[keys_list.index('Sex')]
# b = a.ws[col_ + str(row_)].value
# if b == 0:
# a.ws[col_ + str(row_)].value = '女'
# else:
# a.ws[col_ + str(row_)].value = '男'
# a.editor_user_status('I')
# file_name = '用户角色数据统计表{}'.format(Utils.UUID())
# a.save(uPath + '/' + file_name)
# returnData = Utils.resultData(0, '导出成功')
# return jsonify(returnData)
# except Exception as e:
# return jsonify(Utils.resultData(2, str(e)))

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,109 @@
# -*- coding:utf-8 -*-
"""
@Created on : 2023/7/24 10:25
@Author: hxl
@Des:
"""
import hashlib
import os
import platform
from collections import namedtuple
from datetime import datetime
import psutil
from pytz import timezone
from conf import setting
def ostruct(kv: dict):
return namedtuple('OpenStruct', ' '.join(kv.keys()))(**kv)
def isBlank(term):
return not (term and term.strip())
def timezone_now():
zone = timezone(setting.TIMEZONE)
return datetime.now(zone)
def rfid_reverse(rfid):
"""
读卡器反转
"""
if setting.RFID_REVERSE:
rfid = "".join(reversed([rfid[i:i + 2] for i in range(0, len(rfid), 2)]))
rfid = rfid.upper()
return rfid
def encrypt_md5(text):
# 创建一个 MD5 对象
md5 = hashlib.md5()
# 更新哈希对象的输入值,需要将字符串编码为字节型再进行更新
md5.update(text.encode('utf-8'))
# 计算并返回哈希值的十六进制表示
return md5.hexdigest()
# 判断当前系统是linux还是windows
system_name = platform.system()
# 获取当前插入U盘路径
def get_UDisk_path():
if system_name == 'Windows':
disk_list = psutil.disk_partitions()
# 获取U盘路径
u_path = [disk.device for disk in disk_list if disk.opts == 'rw,removable']
if u_path:
return u_path[0]
elif system_name == "Linux":
r = os.popen('ls -a /media/yanyi')
text = r.read()
r.close()
udisklist = text.splitlines()
if (len(udisklist) >= 3):
return '/media/yanyi/' + udisklist[2]
return ""
# 创建文件夹
def mkdir(path):
folder = os.path.exists(path)
if not folder:
os.makedirs(path)
# 获取配液按天自增编号
def get_seq_no(number):
"""
储备液编号 = 打印标签
格式如CB230728001
"""
# zone = timezone(setting.TIMEZONE) # 时区
# today = datetime.now(zone).strftime('%y%m%d')
today = datetime.now().strftime('%y%m%d')
return "CB%s%03d" % (today, number)
# 获取数字和单位
def number_and_unit(input_str):
"""
获取数量和单位
支持如下的常见格式12.23g/ml,12g,12.3ml等
"""
pattern = r"(\d+\.?\d+)([a-zA-Z/]+)" # 匹配数字和单位的正则表达式
match = re.match(pattern, input_str)
if match:
weight = match.group(1) # 获取数字部分
unit = match.group(2) # 获取单位部分
return weight, unit
else:
return None, None

@ -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小车状态"])

@ -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,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`);"""

@ -0,0 +1,2 @@
from .agv import Agv
from .path import Path

@ -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,245 @@
#!/usr/bin/env python
# -*- coding: utf_8 -*-
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
class TaskAddr:
# 起始地址
TASKTYPE_ADDR_BEGIN = 15000
# AGV路径点集合
PATH_ADDR_BEGIN = 15001
# PATH_ADDR_LENGTH = 42
#是否经过玻璃门
PASS_DOOR_ADDR = 15051
#货架第几层
LAYER_ADDR = 15052
#机械臂是否动作
ARM_ADDR = 15053
#任务数量
TASK_NUM = 15060
#任务取放开始地址 15061, 15062 -> 15079, 15080
T1_ADDR_GET_ADDR = 15061
T1_ADDR_RETURN_ADDR = 15062
#任务截至地址
T10_ADDR_GET_ADDR = 15079
T10_ADDR_RETURN_ADDR = 15080
#
######
# Read Registers
######
#任务是否完成
TASK_DONE_ADDR = 16000
#任务消息接收
TASK_RECV_ADDR = 16001
#AGV所在站点
AGV_STOP_ADDR = 16002
#AGV当前电量
AGV_POWER_ADDR = 16003
#AGV充电状态
AGV_CHARGE_ADDR = 16004
#设备忙
BUSY_ADDR = 16005
#设备异常
ABNORM_ADDR = 16006
#报警信息
ALARM_ADDR = 16007
#状态寄存器长度
# 连续读取8字节单字节长度为16位
STATUS_ADDR_LENGTH = 8
#
def cmd_type(cmdType: int):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_SINGLE_REGISTER,
starting_address=TaskAddr.TASKTYPE_ADDR_BEGIN,
output_value=cmdType
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
async def setSubPaths(start: str, stop: str):
path = await Path.filter(start = start, stop = stop, is_valid = True, is_active = True)
subPaths = []
if path and len(path) > 0:
subPaths = path.paths
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_MULTIPLE_REGISTERS,
starting_address=TaskAddr.TASKTYPE_ADDR_BEGIN,
output_value=subPaths
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
#
def pass_door(passDoor: int):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_SINGLE_REGISTER,
starting_address=TaskAddr.PASS_DOOR_ADDR,
output_value=passDoor
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
#
def setLayer(layer: int):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_SINGLE_REGISTER,
starting_address=TaskAddr.LAYER_ADDR,
output_value=layer
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
#
def moveArm(move: int):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_SINGLE_REGISTER,
starting_address=TaskAddr.ARM_ADDR,
output_value=move
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
#
def taskNum(num: int):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_SINGLE_REGISTER,
starting_address=TaskAddr.TASK_NUM,
output_value=num
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
def setTasks(tasks = []):
try:
with ModbusConnection(
# 试剂amr机器人
AGVPLC.HOST,
AGVPLC.PORT
) as master:
res = master.execute(
1,
cst.WRITE_MULTIPLE_REGISTERS,
starting_address=TaskAddr.T1_ADDR_GET_ADDR,
output_value=tasks
)
print('cmd_type: ',res)
if res:
task_status = False
except Exception as e:
print('cmd_type信息指令异常' + str(e))
time.sleep(2)
def readStatus():
with ModbusConnection(
AGVPLC.HOST,
AGVPLC.PORT
) as master:
status = master.execute(
1,
cst.READ_HOLDING_REGISTERS,
TaskAddr.TASK_DONE_ADDR,
TaskAddr.STATUS_ADDR_LENGTH
)
print('readStatus: ',status)
return status

@ -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,55 @@
import struct
def int_list_to_bytes(int_list):
'''
int列表转换为字节流
'''
byte_stream = b''
for num in int_list:
byte_stream += struct.pack('>H', num)
return byte_stream
def bytes_to_int_list(byte_stream):
'''
字节流转换为int列表
'''
int_list = []
for i in range(0, byte_stream.__len__(), 2):
int_list.append(struct.unpack('>H', byte_stream[i:i+2])[0])
return int_list
def get_bit(data: int, bit: int) -> int:
'''
获取某比特的值
'''
if data & (1 << bit):
return 1
else:
return 0
def get_bit_bool(data: int, bit: int) -> bool:
'''
获取某比特的值并转换为布尔值
'''
return bool(get_bit(data, bit))
def set_bit(number: int, pos: int, value: int):
'''
修改某比特的值并返回新值
'''
new_number = None
if value == 1:
new_number = number | (1 << pos)
elif value == 0:
new_number = number & ~(1 << pos)
return new_number

@ -0,0 +1,4 @@
[tool.aerich]
tortoise_orm = "settings.TORTOISE_ORM"
location = "./migrations"
src_folder = "./."

@ -0,0 +1,2 @@
#python11 ENV
python main.py --host 0.0.0.0 --reload

@ -0,0 +1,13 @@
TORTOISE_ORM = {
# "connections": {"default": "mysql://root:123456@127.0.0.1:3306/shceduler?charset=utf8mb4"}, # MySQL
# "connections": {"default": "sqlite://db.sqlite3"}, # sqlite
"connections": {"default": "mysql://root:123456@127.0.0.1:3306/agv?charset=utf8mb4"}, # MySQL
"apps": {
# 模型分组名字当需要使用此app下的模型的时候需使用 此名字.模型名称
"models": {
# 须添加"aerich.models", 此时会在数据库中生成一个名为aerich的表用于存模型信息以便以后做脚本迁移
"models": ["aerich.models", "models"], # 模型所在的py文件
"default_connection": "default"
}
}
}
Loading…
Cancel
Save