Initial commit

This commit is contained in:
琉璃月光
2025-11-18 17:16:08 +08:00
commit caeb0457f4
26 changed files with 1141 additions and 0 deletions

12
LED_send/.idea/LED_send.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
LED_send/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
</project>

8
LED_send/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/LED_send.iml" filepath="$PROJECT_DIR$/.idea/LED_send.iml" />
</modules>
</component>
</project>

45
LED_send/.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="722ee187-ac61-49c4-9cc8-e2dde3e3cecf" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 6
}]]></component>
<component name="ProjectId" id="35dGBMBjEMzZi0YJZqDn3K3Mo9D" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "/home/hx/开发/LED_send"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-python-sdk-7e47963ff851-f0eec537fc84-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.23892.515" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="722ee187-ac61-49c4-9cc8-e2dde3e3cecf" name="更改" comment="" />
<created>1763432398540</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1763432398540</updated>
</task>
<servers />
</component>
</project>

75
LED_send/README.md Normal file
View File

@ -0,0 +1,75 @@
# LED 信息屏动态显示 Python 调用C/C++ SDK
本项目提供一个完整的 Python 示例,用于生成 LED 信息屏显示内容并通过 SDK 发送动态区域帧到 LED 屏。
它支持:
自动加载依赖的 .so 库(包括 libiconv和主 SDK 库 libbx_sdkDual.so
生成参数化 LED 表格图片
使用 SDK 的动态区接口发送帧到 LED 屏
## 目录结构
LED_send/
├── led_send.py # 主程序脚本
├── libbx_sdkDual.so # SDK 动态库
├── libiconv.so.2 # SDK 依赖库
├── simsun.ttc # 字体文件,用于生成 LED 表格
└── README.md # 说明文档
## 配置
### 配置库路径
脚本为方便调用默认加载当前目录下的 libbx_sdkDual.so如需修改
```bash
MAIN_SO_NAME = "libbx_sdkDual.so"
CURRENT_DIR = "/your/path/to/so"
```
### 数据对齐
#### data数据
```bash
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"
}
```
#### LED 屏表格数据对应关系
| LED 区域 | 示例图片位置 | 对应数据字段 | 示例值 |
|----------- |-----------------|-----------------------------------------|-------------------------------|
| 表头 | 屏幕最上方 | 固定文字 | 浇筑工序信息屏测试 |
| 本盘方量 | 第一行第1列 | PlateVolume | 2.00 |
| 当前模具 | 第一行第2列 | MouldCode | SHR2B1-3 |
| 高斗称值 | 第一行第3列 | HighBucketWeighingValue | 115 |
| 低斗称值 | 第一行第4列 | LowBucketWeighingValue | 75 |
| 投料时间 | 第二行第1列 | ProduceStartTime | 15:06 |
| 当前管片 | 第二行第2列 | ArtifactID | QR2B13099115D |
| 砼出料温度 | 第二行第3列 | Temper | 18.6℃ |
| 振捣频率 | 第二行第4列 | VibrationFrequency | 10min/220HZ |
| 累计盘次 | 第三行第1列 | PlateIDSerial | 85 |
| 隐蔽验收 | 第三行第2列 | CheckResult | 合格 |
| 车间环温 | 第三行第3列 | WorkshopTemperature | 12.4℃ |
| 任务方量 | 第三行第4列 | TotMete / 固定值 | 353.2 / 214.1 |
| 配方比例 | 第四行跨3列 | FormulaProportion | 水泥:砂:石:粉煤灰:矿粉:外加剂:水... |
| 拆模强度 | 第五行跨3列 | DayStrengthValue / NihtStrengthValue | 白班:2024/11/27 22:00抗压 龄期:15h 强度25.9 / 晚班:2024/11/26 07:55抗压 龄期:12h 强度25.2 |
## 函数调用
```bash
from led_send import send_led_data
send_led_data(data)
```

292
LED_send/led_send.py Normal file
View File

@ -0,0 +1,292 @@
#!/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)

BIN
LED_send/libbx_sdkDual.so Normal file

Binary file not shown.

BIN
LED_send/libiconv.so.2 Normal file

Binary file not shown.

BIN
LED_send/simsun.ttc Normal file

Binary file not shown.