304 lines
11 KiB
Python
304 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# coding: utf-8
|
||
import os
|
||
import cv2
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
import ctypes
|
||
from ctypes import *
|
||
import glob
|
||
import sys
|
||
|
||
# ============================================================
|
||
# SDK Load
|
||
# ============================================================
|
||
|
||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
MAIN_SO_NAME = "libbx_sdkDual.so"
|
||
MAIN_SO = os.path.join(CURRENT_DIR, MAIN_SO_NAME)
|
||
|
||
def preload_shared_objects(so_dir):
|
||
print(f"自动加载 so 路径:{so_dir}")
|
||
if not os.path.isdir(so_dir):
|
||
print(f"错误:目录不存在: {so_dir}")
|
||
return None
|
||
|
||
so_list = glob.glob(os.path.join(so_dir, "*.so*"))
|
||
iconv_files = [s for s in so_list if "libiconv" in os.path.basename(s)]
|
||
loaded = set()
|
||
|
||
for f in iconv_files:
|
||
try:
|
||
ctypes.CDLL(f, mode=ctypes.RTLD_GLOBAL)
|
||
print(f"已加载 libiconv: {f}")
|
||
loaded.add(f)
|
||
except Exception as e:
|
||
print(f"加载失败 {f}: {e}")
|
||
|
||
for f in so_list:
|
||
if os.path.basename(f) == MAIN_SO_NAME or f in loaded:
|
||
continue
|
||
try:
|
||
ctypes.CDLL(f, mode=ctypes.RTLD_GLOBAL)
|
||
print(f"已加载依赖库: {f}")
|
||
except Exception as e:
|
||
print(f"跳过无法加载的库 {f}: {e}")
|
||
|
||
if os.path.exists(MAIN_SO):
|
||
try:
|
||
lib = ctypes.CDLL(MAIN_SO, mode=ctypes.RTLD_GLOBAL)
|
||
print(f"成功加载主库: {MAIN_SO}")
|
||
return lib
|
||
except Exception as e:
|
||
print(f"主库加载失败: {MAIN_SO} -> {e}")
|
||
return None
|
||
else:
|
||
print(f"主库不存在: {MAIN_SO}")
|
||
return None
|
||
|
||
os.environ["LD_LIBRARY_PATH"] = CURRENT_DIR + ":" + os.environ.get("LD_LIBRARY_PATH", "")
|
||
os.environ["PATH"] = CURRENT_DIR + ":" + os.environ.get("PATH", "")
|
||
|
||
lib = preload_shared_objects(CURRENT_DIR)
|
||
if lib is None:
|
||
print("无法加载主库,程序退出")
|
||
sys.exit(1)
|
||
|
||
# ====================== 生成 LED 表格 ======================
|
||
def generate_led_table(data, output_path="led_send.png", font_path="msyh.ttc"):
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
|
||
try:
|
||
font_title = ImageFont.truetype(font_path, 24)
|
||
font_data = ImageFont.truetype(font_path, 20)
|
||
font_data_big = ImageFont.truetype(font_path, 22)
|
||
font_small = ImageFont.truetype(font_path, 16)
|
||
header_font = ImageFont.truetype(font_path, 26)
|
||
except IOError:
|
||
print("字体未找到,使用默认字体")
|
||
font_title = font_data = font_data_big = font_small = ImageFont.load_default()
|
||
header_font = ImageFont.load_default()
|
||
|
||
total_width, total_height = 630, 430
|
||
img = Image.new("RGB", (total_width, total_height), (0, 0, 0))
|
||
draw = ImageDraw.Draw(img)
|
||
|
||
col_count = 4
|
||
row_count = 8
|
||
row_heights = [int(total_height * 0.095)] * 6 + [int(total_height * 0.15), int(total_height * 0.15)]
|
||
y_positions = [0]
|
||
for h in row_heights:
|
||
y_positions.append(y_positions[-1] + h)
|
||
col_width = total_width // col_count
|
||
|
||
# 表头
|
||
header_text = "浇筑工序信息屏测试"
|
||
bbox = draw.textbbox((0, 0), header_text, font=header_font)
|
||
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||
draw.text(((total_width - tw) // 2, 7), header_text, fill="Yellow", font=header_font)
|
||
|
||
# safe float
|
||
try:
|
||
task_quantity = float(data.get("TotMete", 0.0))
|
||
fixed_value = float(data.get("BetonVolumeAlready", 0.0))
|
||
except Exception:
|
||
task_quantity = 0.0
|
||
fixed_value = 0.0
|
||
task_quantity_str = f"{task_quantity}"
|
||
fixed_value_str = f"/{fixed_value}"
|
||
|
||
table_data = [
|
||
["本盘方量", "当前模具", "高斗称值", "低斗称值"],
|
||
[str(data.get("PlateVolume", "")), str(data.get("MouldCode", "")),
|
||
str(data.get("UpperWeight", "")), str(data.get("LowerWeight", ""))],
|
||
["投料时间", "当前管片", "砼出料温度", "振捣频率"],
|
||
[str(data.get("ProduceStartTime", "")), str(data.get("ArtifactID", "")),
|
||
str(data.get("Temper", "")), str(data.get("VibrationFrequency", ""))],
|
||
["累计盘次", "隐蔽验收", "车间环温", "任务方量"],
|
||
[str(data.get("PlateIDSerial", "")), str(data.get("CheckResult", "")),
|
||
str(data.get("WorkshopTemperature", "")), ""],
|
||
["配方比例", "", "", ""],
|
||
["拆模强度", "", "", ""]
|
||
]
|
||
|
||
# =======================
|
||
# 画表格线(只用 line)
|
||
# =======================
|
||
line_color = (255, 255, 255)
|
||
line_width = 1
|
||
|
||
# 横线
|
||
for r in range(row_count + 1):
|
||
y = y_positions[r] + 40 if r < row_count else y_positions[-1] + 40
|
||
draw.line([(0, y), (total_width, y)], fill=line_color, width=line_width)
|
||
|
||
# 竖线
|
||
for c in range(col_count + 1):
|
||
x = c * col_width
|
||
# 前6行所有竖线
|
||
for r in range(6):
|
||
y1 = y_positions[r] + 40
|
||
y2 = y_positions[r + 1] + 40
|
||
draw.line([(x, y1), (x, y2)], fill=line_color, width=line_width)
|
||
|
||
# 最后两行
|
||
y1 = y_positions[6] + 40
|
||
y2 = y_positions[8] + 40
|
||
if c == 0 or c == col_count: # 左右边框
|
||
draw.line([(x, y1), (x, y2)], fill=line_color, width=line_width)
|
||
elif c == 1: # 第二列竖线(分隔跨列内容)
|
||
draw.line([(x, y1), (x, y2)], fill=line_color, width=line_width)
|
||
# 第三列和第四列竖线不画,保持跨列显示
|
||
|
||
# =======================
|
||
# 绘制文本
|
||
# =======================
|
||
for r in range(row_count):
|
||
y1 = y_positions[r] + 40
|
||
h = row_heights[r]
|
||
for c in range(col_count):
|
||
x1 = c * col_width
|
||
content = table_data[r][c]
|
||
|
||
if not content.strip():
|
||
if r == 5 and c == 3:
|
||
bbox_task = draw.textbbox((0, 0), task_quantity_str, font=font_data)
|
||
tw_task = bbox_task[2] - bbox_task[0]
|
||
th_task = bbox_task[3] - bbox_task[1]
|
||
draw.text((x1 + (col_width - 1.8 * tw_task) // 2, y1 + (h - th_task) // 2),
|
||
task_quantity_str, fill="red", font=font_data)
|
||
bbox_fixed = draw.textbbox((0, 0), fixed_value_str, font=font_data)
|
||
tw_fixed = bbox_fixed[2] - bbox_fixed[0]
|
||
draw.text((x1 + (col_width - tw_fixed) // 2 + 0.78 * tw_task,
|
||
y1 + (h - th_task) // 2),
|
||
fixed_value_str, fill=(0, 255, 0), font=font_data)
|
||
continue
|
||
|
||
is_header = r in (0, 2, 4, 6, 7)
|
||
color = (0, 255, 0) if is_header else "red"
|
||
if color == "red" and r < 3:
|
||
font = font_data_big
|
||
elif color == "red" and r >= 6:
|
||
font = font_small
|
||
else:
|
||
font = font_title if is_header else font_data
|
||
|
||
bbox = draw.textbbox((0, 0), content, font=font)
|
||
tw = bbox[2] - bbox[0]
|
||
th = bbox[3] - bbox[1]
|
||
draw.text((x1 + (col_width - tw) // 2, y1 + (h - th) // 2), content, fill=color, font=font)
|
||
|
||
# 多行文本居中
|
||
def draw_multiline_text_center(draw_obj, x, y, width, height, text, font_obj, fill="red"):
|
||
lines = text.split('\n')
|
||
bboxs = [draw_obj.textbbox((0, 0), line, font=font_obj) for line in lines]
|
||
total_h = sum(b[3] - b[1] for b in bboxs)
|
||
cy = y + (height - total_h) // 2
|
||
for line, b in zip(lines, bboxs):
|
||
w = b[2] - b[0]
|
||
h = b[3] - b[1]
|
||
draw_obj.text((x + (width - w) // 2, cy), line, fill=fill, font=font_obj)
|
||
cy += h
|
||
|
||
draw_multiline_text_center(draw, col_width * 1, y_positions[6] + 40, col_width * 3, row_heights[6],
|
||
str(data.get("FormulaProportion", "")).replace("\r", ""), font_small)
|
||
draw_multiline_text_center(draw, col_width * 1, y_positions[7] + 40, col_width * 3, row_heights[7],
|
||
f"{data.get('DayStrengthValue', '')}\n{data.get('NihtStrengthValue', '')}",
|
||
font_small)
|
||
|
||
img.save(output_path)
|
||
print(f"已生成参数化表格:{output_path}")
|
||
|
||
|
||
|
||
# ====================== 动态区结构体 ======================
|
||
class EQpageHeader_G6(Structure):
|
||
_fields_ = [
|
||
("PageStyle", c_uint8), ("DisplayMode", c_uint8), ("ClearMode", c_uint8),
|
||
("Speed", c_uint8), ("StayTime", c_uint16), ("RepeatTime", c_uint8),
|
||
("ValidLen", c_uint8), ("CartoonFrameRate", c_uint8), ("BackNotValidFlag", c_uint8),
|
||
("arrMode", c_uint8), ("fontSize", c_uint8), ("color", c_uint8),
|
||
("fontBold", c_uint8), ("fontItalic", c_uint8), ("tdirection", c_uint8),
|
||
("txtSpace", c_uint8), ("Valign", c_uint8), ("Halign", c_uint8)
|
||
]
|
||
|
||
lib.bxDual_dynamicArea_AddAreaPic_6G.argtypes = [
|
||
c_char_p, c_uint32, c_uint8, c_uint8, c_uint16, c_uint16,
|
||
c_uint16, c_uint16, POINTER(EQpageHeader_G6), c_char_p
|
||
]
|
||
lib.bxDual_dynamicArea_AddAreaPic_6G.restype = c_int
|
||
|
||
# ====================== 发送动态区帧(丝滑覆盖) ======================
|
||
def send_dynamic_frame(ip="10.6.242.2", port=5005, frame=None, filename="led_send.png"):
|
||
if frame is None:
|
||
print("frame 为空!")
|
||
return
|
||
|
||
target_w, target_h = 630, 435
|
||
resized = cv2.resize(frame, (target_w, target_h))
|
||
save_path = os.path.join(CURRENT_DIR, filename)
|
||
cv2.imwrite(save_path, resized)
|
||
|
||
page = EQpageHeader_G6()
|
||
page.PageStyle = 0
|
||
page.DisplayMode = 2
|
||
page.ClearMode = 1
|
||
page.Speed = 10
|
||
page.StayTime = 1000
|
||
page.RepeatTime = 1
|
||
page.ValidLen = 64
|
||
page.CartoonFrameRate = 0
|
||
page.BackNotValidFlag = 0
|
||
page.arrMode = 1
|
||
page.fontSize = 16
|
||
page.color = 1
|
||
page.fontBold = 0
|
||
page.fontItalic = 0
|
||
page.tdirection = 0
|
||
page.txtSpace = 0
|
||
page.Valign = 2
|
||
page.Halign = 1
|
||
|
||
try:
|
||
ret = lib.bxDual_dynamicArea_AddAreaPic_6G(
|
||
ip.encode("ascii"), port, 2, 0, 0, 0, target_w, target_h,
|
||
byref(page), save_path.encode("gb2312")
|
||
)
|
||
if ret == 0:
|
||
print("Frame 覆盖成功!")
|
||
else:
|
||
print("Frame 发送失败,返回码:", ret)
|
||
except Exception as e:
|
||
print("调用 AddAreaPic 失败:", e)
|
||
|
||
def send_led_data(data: dict):
|
||
img_path = os.path.join(CURRENT_DIR, "led_send.png")
|
||
generate_led_table(data, output_path=img_path)
|
||
frame = cv2.imread(img_path)
|
||
send_dynamic_frame(frame=frame, filename="led_send.png")
|
||
|
||
# ============================================================
|
||
# 主程序示例
|
||
# ============================================================
|
||
|
||
if __name__ == "__main__":
|
||
data = {
|
||
"PlateVolume": "2.00",
|
||
"MouldCode": "SHR2B1-3",
|
||
"ProduceStartTime": "15:06",
|
||
"ArtifactID": "QR2B13099115D",
|
||
"Temper": "18.6℃",
|
||
"PlateIDSerial": "85",
|
||
"CheckResult": "合格",
|
||
"TotMete": "353.2",
|
||
"LowBucketWeighingValue": "75",
|
||
"HighBucketWeighingValue": "115",
|
||
"WorkshopTemperature": "12.4℃",
|
||
"VibrationFrequency": "10min/220HZ",
|
||
"FormulaProportion": "水泥:砂:石:粉煤灰:矿粉:外加剂:水\r\n0.70:1.56:2.78:0.15:0.15:0.006:0.33",
|
||
"DayStrengthValue": "白班:2024/11/27 22:00抗压 龄期:15h 强度25.9",
|
||
"NihtStrengthValue": "晚班:2024/11/26 07:55抗压 龄期:12h 强度25.2"
|
||
}
|
||
|
||
send_led_data(data)
|
||
|