#!/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="simsun.ttc"): """ 根据接口返回的 Data 生成 LED 显示表格,适配 640x448 LED 屏 """ 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, 30) except IOError: print("字体未找到,使用默认字体") font_title = font_data = font_data_big = font_small = ImageFont.load_default() header_font = ImageFont.load_default() total_width, total_height = 640, 448 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[:-1]: 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 parse try: task_quantity = float(data.get("TotMete", 0)) except Exception: task_quantity = 0.0 fixed_value = 214.1 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("HighBucketWeighingValue", "")), str(data.get("LowBucketWeighingValue", ""))], ["投料时间", "当前管片", "砼出料温度", "振捣频率"], [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", "")), ""], ["配方比例", "", "", ""], ["拆模强度", "", "", ""] ] # 画表格框 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 if r >= 6 and c == 1: draw.rectangle([x1, y1, total_width - 1, y1 + h - 1], outline="white", width=1) break elif r >= 6 and c > 1: continue else: draw.rectangle([x1, y1, x1 + col_width - 1, y1 + h - 1], outline="white", width=1) # 绘制文本 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="green", font=font_data) continue is_header = r in (0, 2, 4, 6, 7) color = "green" 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) y_start = y + (height - total_h) // 2 curr_y = y_start for line, b in zip(lines, bboxs): w = b[2] - b[0] h = b[3] - b[1] draw_obj.text((x + (width - w) // 2, curr_y), line, fill=fill, font=font_obj) curr_y += 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_DelArea_6G.argtypes = [c_char_p, c_uint32, 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_DelArea_6G.restype = c_int 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 为空!") #因为相机SDK接口需要的是待发送图片的地址,所以加上确认需要发送图片是否存在。 return target_w, target_h = 640, 448 resized = cv2.resize(frame, (target_w, target_h)) current_dir = os.path.dirname(os.path.abspath(__file__)) save_path = os.path.join(current_dir, filename) # 使用 cv2.imwrite 保存确保文件编码一致 cv2.imwrite(save_path, resized) # 这些参数都可以设置,我备注一下参数名称和调节的信息 page = EQpageHeader_G6() page.PageStyle = 0 #数据页类型,默认为0 page.DisplayMode = 2 #显示方式: 0x00 :随机显示 0x01 :静止显示 0x02 :快速打出 0x03 :向左移动 0x04 :向左连移 0x05 :向上移动 0x06 :向上连移 0x07 :闪烁 ...... page.ClearMode = 1 #退出方式/清屏方式 page.Speed = 10 #速度等级/背景速度等级 page.StayTime = 1000 #停留时间, 单位为 10ms page.RepeatTime = 1 #重复次数/背景拼接步长(左右拼接下为宽度, 上下拼接为高度) page.ValidLen = 64 #用法比较复杂请参考协议,默认不动 page.CartoonFrameRate = 0 #特技为动画方式时,该值代表其帧率 page.BackNotValidFlag = 0 #背景无效标志 #字体信息 page.arrMode = 1 #排列方式--单行多行 page.fontSize = 16 #字体大小 page.color = 1 #字体颜色 E_Color_G56此通过此枚举值可以直接配置七彩色,如果大于枚举范围使用RGB888模式 page.fontBold = 0 #是否为粗体 page.fontItalic = 0 #是否为斜体 page.tdirection = 0 #文字方向 page.txtSpace = 0 #文字间隔 page.Valign = 2 #纵向对齐方式(0系统自适应、1上对齐、2居中、3下对齐) page.Halign = 1 #横向对齐方式(0系统自适应、1左对齐、2居中、3右对齐) print("删除旧动态区 ...") try: ret_del = lib.bxDual_dynamicArea_DelArea_6G(ip.encode(), port, 0xFF) print("删除返回码:", ret_del) except Exception as e: print("调用 DelArea 失败:", e) 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) #这里读取图片是为了保证生成图片函数已经在改文件夹下生成了图片,因为相机SDK接口需要的是待发送图片的地址,所以加上确认。 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)