跳到主要内容

自定义 FB 开发 / SDK 扩展

概述

DarraRT 内置 200+ 标准功能块 (FB), 已覆盖绝大多数通用场景。当业务出现特殊工艺算法第三方协议历史遗留库时, 需要把这些代码以功能块形式纳入 IEC 61131-3 工程中调用。DarraRT 提供三种扩展方式, 按安全性 / 性能 / 便利性三维取舍。

三种扩展方式

方式安全性性能开发成本适用
SCL 源码库 (.darralib)最高 (IDE 沙箱)纯算法 / 流程
C/C++ 动态库 (DLL/so)低 (原生, 崩溃拖垮 PLC)最高FFT / 图像 / 数学
.NET 程序集 (DLL, 托管)中 (AppDomain 隔离)通讯 / 数据库 / 复杂协议

选型原则:

  • 能用 SCL 写 → 用 SCL (可跨平台, 可热更)
  • 必须高性能 → C/C++
  • 要调库多 (HSL / Dapper / AMQP) → .NET

方式 1: SCL 源码库

1.1 创建库项目

IDE: 文件 → 新建 → 库项目 (.darralib)

MyLib.darralib
├── Manifest.yaml # 库元数据
├── FB/
│ ├── FB_MyControl.st
│ └── FB_MyFilter.st
├── UDT/
│ └── MyTypes.st
└── Functions/
└── Utils.st

Manifest.yaml:

library:
id: com.acme.mylib
name: Acme 自定义库
version: 1.2.0
vendor: Acme Corp
requires:
- darrart.stdlib >= 2.4
provides:
function_blocks:
- FB_MyControl
- FB_MyFilter
types:
- MyMotorCmd
functions:
- CrcXmodem
signature: SHA256:... # 构建时自动

1.2 示例 SCL 库函数

(* 工艺专用 PID, 带死区 + 积分分离 *)
FUNCTION_BLOCK FB_MyControl
VAR_INPUT
sp, pv : REAL;
kp,ki,kd : REAL;
deadband : REAL := 0.0;
END_VAR
VAR_OUTPUT
mv : REAL;
END_VAR
VAR
err_prev, integ : REAL;
first : BOOL := TRUE;
END_VAR

VAR err, derr : REAL; END_VAR
err := sp - pv;
IF ABS(err) < deadband THEN err := 0.0; END_IF;

// 积分分离: 误差大时停止积分
IF ABS(err) < 10.0 THEN
integ := integ + ki * err;
IF integ > 100.0 THEN integ := 100.0; END_IF;
IF integ < -100.0 THEN integ := -100.0; END_IF;
END_IF;

IF first THEN derr := 0.0; first := FALSE;
ELSE derr := err - err_prev;
END_IF;
err_prev := err;

mv := kp * err + integ + kd * derr;

1.3 发布与引用

IDE: 库 → 编译 → 签名 → 发布到企业库 (本地 %AppData%/DarraLib 或 HTTP 仓库 https://lib.acme.com)

主工程引用: 工程 → 依赖 → 添加 com.acme.mylib:1.2.0 → 直接用 FB_MyControl 即可。

方式 2: C/C++ 动态库

2.1 FB 模板 (C)

DarraRT FB C ABI 固定三个入口:

// mylib_fb.h
#include <stdint.h>

typedef struct DarraFbContext_ DarraFbContext;

typedef struct {
const char* fb_name; // "FB_MyProcess"
uint32_t api_version; // 1
uint32_t cycle_ns_hint; // 建议周期, 0=不关心
size_t state_size; // FB 内部状态字节数
int (*init) (DarraFbContext* ctx, const void* params, size_t n);
int (*cycle) (DarraFbContext* ctx);
int (*cleanup)(DarraFbContext* ctx);
} DarraFbDesc;

typedef struct {
// IO 绑定: 指向 PLC 变量区的指针
void* (*var_read) (DarraFbContext* c, const char* path);
void (*var_write)(DarraFbContext* c, const char* path, const void* val, size_t n);
uint64_t (*ns_clock) (void);
void (*log) (int level, const char* fmt, ...);
} DarraHostApi;

实现示例:

// mylib_fb.c
#include "mylib_fb.h"

typedef struct {
double accumulator;
uint64_t cycles;
} FbState;

static const DarraHostApi* H = NULL;

int my_init(DarraFbContext* ctx, const void* p, size_t n) {
FbState* s = (FbState*)fb_state(ctx);
s->accumulator = 0.0;
s->cycles = 0;
return 0;
}

int my_cycle(DarraFbContext* ctx) {
FbState* s = (FbState*)fb_state(ctx);
double* pv = (double*)H->var_read(ctx, "Input.pv");
s->accumulator += *pv;
s->cycles++;
double avg = s->accumulator / (double)s->cycles;
H->var_write(ctx, "Output.avg", &avg, sizeof(avg));
return 0;
}

int my_cleanup(DarraFbContext* ctx) { return 0; }

// 注册导出
FB_EXPORT DarraFbDesc FB_MyProcess = {
.fb_name = "FB_MyProcess",
.api_version = 1,
.cycle_ns_hint= 1000000, // 1 ms
.state_size = sizeof(FbState),
.init = my_init,
.cycle = my_cycle,
.cleanup = my_cleanup
};

FB_EXPORT void fb_set_host(const DarraHostApi* api) { H = api; }

2.2 编译打包

# Windows (MSVC)
cl /LD /O2 /D "FB_EXPORT=__declspec(dllexport)" mylib_fb.c
# 产出 mylib_fb.dll

# Linux
gcc -shared -fPIC -O2 -o mylib_fb.so mylib_fb.c

打包成 .darrafb:

# manifest.yaml
fb_package:
id: com.acme.fft
version: 1.0.0
abi: c-v1
binaries:
windows-x64: mylib_fb.dll
linux-x64: mylib_fb.so
function_blocks:
- FB_MyProcess
required_host_api: 1.x
signature_file: sig.bin
darra-pkg pack . -o mylib-1.0.0.darrafb
darra-pkg sign mylib-1.0.0.darrafb --cert ./acme.pfx

2.3 安全警告

C FB 运行在 Service 同进程空间, 崩溃 / 缓冲区溢出会直接导致 PLC 停止。建议:

  • 代码签名强制, 未签名 FB Service 拒绝加载
  • 使用 AddressSanitizer (-fsanitize=address) 开发
  • 生产前过至少 10^7 个周期的压力测试
  • 对输入参数做严格校验, 不信任来自 PLC 的指针
  • 不得在 cycle 内分配堆内存 (会破坏实时性)

方式 3: .NET 程序集

3.1 接口

// Darra.PLC.Extensions
public interface IFunctionBlock
{
string Name { get; }
void Init(IFbContext ctx, IDictionary<string, object>? args);
void Execute(IFbContext ctx);
void Dispose();
}

public interface IFbContext
{
T GetInput<T>(string name);
void SetOutput<T>(string name, T value);
long NsClock { get; }
ILogger Log { get; }
}

[FunctionBlock(Name = "FB_HttpFetch", CycleHint = CycleHint.Slow)]
public sealed class FB_HttpFetch : IFunctionBlock
{
public string Name => "FB_HttpFetch";

private HttpClient? _http;
public void Init(IFbContext ctx, IDictionary<string,object>? args)
{
_http = new HttpClient { Timeout = TimeSpan.FromSeconds(3) };
}

public void Execute(IFbContext ctx)
{
var trigger = ctx.GetInput<bool>("trigger");
if (!trigger) return;
var url = ctx.GetInput<string>("url");
try
{
var body = _http!.GetStringAsync(url).Result; // 简化
ctx.SetOutput("ok", true);
ctx.SetOutput("response", body);
}
catch (Exception e)
{
ctx.Log.Error(e, "HTTP fetch 失败");
ctx.SetOutput("ok", false);
ctx.SetOutput("error", e.Message);
}
}

public void Dispose() => _http?.Dispose();
}

3.2 线程模型

  • Fast FB (CycleHint.Fast): 运行在 PLC 扫描线程, 必须 <100μs, 不得阻塞 IO
  • Slow FB (CycleHint.Slow): 运行在 Service 工作线程池, 可做 HTTP / SQL / 磁盘, 结果异步回写 PLC 变量

标注了 CycleHint.Slow 的 FB, Execute 在后台线程, PLC 调用方不等待返回, 只检查结果标志位。

3.3 打包与签名

<!-- MyFb.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>acme.snk</AssemblyOriginatorKeyFile>
<DarraFbPackageId>com.acme.netfb</DarraFbPackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Darra.PLC.Extensions" Version="2.4.0" />
</ItemGroup>
</Project>
dotnet build -c Release
darra-pkg pack-net ./bin/Release/net8.0 -o netfb-1.0.0.darrafb
darra-pkg sign netfb-1.0.0.darrafb --cert ./acme.pfx

错误传播

所有三种 FB 统一用FB 状态字 (ENO / Error / ErrorCode) 表达异常, 不抛不处理的异常会被 Host 拦截并转成 ENO=FALSE, ErrorCode=0xFB0000xx, 日志记录完整堆栈。

FB_MyProcess(pv := DB.pv);
IF NOT FB_MyProcess.ENO THEN
// 处理异常: 日志 / 停止 / 回退
StartAlarm(ALARM_FB_FAIL, FB_MyProcess.ErrorCode);
END_IF;

发布到企业库

企业级部署推荐独立 FB 仓库:

https://fblib.acme.com/
├── api/v1/search?q=...
├── api/v1/fetch/com.acme.mylib/1.2.0
└── api/v1/publish (需 API Key)

IDE 配置:

# ~/.darra/config.yaml
lib_sources:
- https://darra-lib.official.com/
- https://fblib.acme.com/
- file:///D:/darra-lib-local/

版本与兼容

主版本ABI 兼容含义
1.x.y1.x.z兼容接口 FB 签名不变
1.x2.x破坏强制重编译, 需迁移
api_versionHost 检查不匹配拒加载

排错

现象原因措施
FB 加载失败签名无效 / ABI 不匹配检查 api_version 与签名证书
FB 内存泄漏C 代码未释放Valgrind / DrMemory 跑压力测试
FB 扫描拖慢Fast FB 里做了 IO改 Slow FB
.NET FB 崩溃未捕获异常Host 自动 Dispose + 降级

高级技巧

  1. 双实现策略: SCL 作基准版, C/.NET 作加速版, 根据平台自动选
  2. 配置即参数: FB Init 接收 YAML 字典, 改行为不改代码
  3. 单元测试: Darra.PLC.TestHost 本地跑 FB, 不需要真 PLC
  4. 热更新: Slow FB 可无停机替换 Assembly, Fast FB 必须重启 PLC
  5. 沙箱限额: .NET FB 限制 CPU / 内存 / 文件访问

相关文档