C# API 参考
C# SDK(程序集 DarraPnet,命名空间 DarraPnet.Pnet,netstandard2.0)是 6 语言 SDK 的基准实现,其他语言用法与命名对齐本页。P/Invoke 声明集中在 PnetDll.cs(仿 ETH DLL.cs 集中声明风格),序列导出自原生 DarraPnet.dll。
using DarraPnet.Pnet;
概览
SDK 为 ETH 式单一入口(仿 DarraEtherCAT.Slave 形态):一切操作从 DarraPnet 实例出发。SDK 不做配置(配置由 GUI 导出运行配置 XML → Service 加载),只负责【使用】——启停 + IO 读写。站点名是设备身份(与控制器工程中的设备名一致),由 GUI / Service 配置,SDK 构造不再传;网卡同理由 GUI / Service 配置选择(单网口从站,SDK 仅枚举供诊断)。
单一 PROFINET 网口契约(audit-2026-08-11,用户裁定 2026-08-10):设备对外只开放一个 PROFINET 网口(工业现场常态 —— 设备只有一个 PROFINET 口)。网卡选择在 GUI / Service 配置层完成,服务侧单一权威 =
NetworkInterfaceName(读取链:环境变量DARRA_PNET_NIC> 运行配置 XML<Network/InterfaceName>> appsettings 出厂默认;驱动绑定 / pnal 绑定目标 / MAC 解析同源一致,无双配置歧义)。SDKEnumerateInterfaces()仅作诊断/枚举用途,不改变服务侧绑定目标。
| 类型 | 职责 | 可用模式 |
|---|---|---|
DarraPnet | 单一入口:构造(native 直连)/ LoadConfig / Connect(服务模式)+ Start / Stop + 地址化 IO 读写 | 全部 |
PnetPdo(Pdo) | PDO 结构体映射(过程数据 ↔ 结构体) | 全部 |
PnetVariables(Variables) | 变量联想(从运行配置 XML 自动建表) | 全部 |
PnetSlave(Slave) | 从站控制(状态 / 插拔 / 应用就绪) | 仅 native 直连 |
PnetIo(Io) | 槽位过程数据 + 底层地址化读写 | 仅 native 直连 |
PnetAlarm(Alarm) | 报警发送 / ACK 应答 | 仅 native 直连 |
PnetConfig(Config) | 运行配置只读(诊断用) | 仅 native 直连 |
PnetDiag(Diag) | 诊断计数 / 诊断条目 | 仅 native 直连 |
连接服务模式无本地子对象:状态 / 报警 / 诊断由服务端单一权威维护,访问
Slave/Io/Alarm/Config/Diag抛NotSupportedException,请经服务 HTTP API 查询(GET /api/status/GET /api/alarms)。地址化 IO 读写两种模式均可用(服务模式经GET/POST /api/io转发)。
创建实例(三种方式,构造决定模式)
// 1) native 直连快速测试:仅传 IP,站点名/网卡/掩码/网关取默认(正式部署走 LoadConfig)
var pnet = new DarraPnet("192.168.1.100");
// 2) native 直连正式部署:加载 GUI 导出的运行配置 XML
// (站点名/网卡/IP/槽位布局全从 XML 读,用户不接触配置对象)
var pnet = DarraPnet.LoadConfig(@"C:\Darra\Profinet\device.xml");
// 3) 连接服务模式(推荐):经服务 HTTP API(端口 18840 固定)连接,由服务承载从站运行
// ★ 服务仅监听本地回环 127.0.0.1(audit-2026-08-10):未开放局域网监听,
// 仅本地回环可达;host 传非回环地址连接必失败
var pnet = DarraPnet.Connect("127.0.0.1"); // 本地服务(唯一可达地址)
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
new DarraPnet(string ipAddress) | DarraPnet | 设备 | 构造 | native 直连:仅传 IP,其余取默认(快速测试) |
LoadConfig(string xmlPath) | DarraPnet | 设备 | 静态 | native 直连:加载运行配置 XML(PNETConfig schema,GUI ConfigWriter 输出) |
Connect(string host, int port = DefaultServicePort) | DarraPnet | 设备 | 静态 | 连接服务模式:经服务 HTTP API 转发启停 / IO(SDK 与 Web API 同层,由服务运行) |
DefaultServicePort | int | 设备 | 只读 | 服务 HTTP API 固定端口 18840(编译期常量,不可配置) |
IsServiceMode | bool | 设备 | 只读 | 是否连接服务模式 |
ServiceHost / ServicePort | string / int | 设备 | 只读 | 服务主机 / 端口(连接服务模式) |
Start() | PnetErrorCode | 设备 | 写 | 启动从站:native 初始化栈 + 注册过程映像槽位 + 1 ms 周期驱动线程;服务模式 POST /api/start |
Stop() | void | 设备 | 写 | 停止(幂等;Stop 后本实例不可复用,需重新创建) |
Dispose() | void | 设备 | 写 | 等价 Stop + 释放(幂等) |
IsRunning | bool | 设备 | 只读 | 是否运行中 |
State | PnetState | 设备 | 只读 | 当前运行状态(服务模式映射 GET /api/status) |
Connected | bool | 设备 | 只读 | 是否已与 IO 控制器建立周期数据交换(服务模式,GET /api/status 的 connected 原值;与服务端状态同源透出,用户裁定 2026-08-10) |
ErrorCode | int | 设备 | 只读 | 最近一次错误码原值(服务模式,GET /api/status 的 errorCode 原值透传;0 = 无错误,非零 = pnet 栈错误码,语义见 PnetRuntimeErrorCode) |
ErrorCodeEnum | PnetRuntimeErrorCode | 设备 | 只读 | 最近一次错误码的枚举化映射(ErrorCode 原值映射;未识别值如实返回 Unknown,原始值仍经 ErrorCode 透传) |
Version | DarraPnetVersionInfo | 设备 | 只读 | SDK / native 栈版本信息(加载失败为 null) |
EnumerateInterfaces() | IReadOnlyList<string> | 设备 | 静态 | 枚举本机物理网卡(诊断用;正式部署网卡由 GUI / Service 配置选择) |
LastServiceError | string | 设备 | 只读 | 最近一次连接服务模式 HTTP 错误(成功调用时清空) |
生命周期:New → Start → [IO 读写] → Stop / Dispose。数据获取用属性(非 GetXxx),状态返回枚举(ETH SDK 规范)。
地址化读写(ETH 式地址,公共解析器 PnetAddress.Parse 单源)
西门子风格地址,统一由公共解析器 PnetAddress.Parse 解析(单源原则),两种运行模式语义一致:
| 区域 | 地址格式 | 方向语义 |
|---|---|---|
| I 区(输入面) | I0.0 / IB0 / IW2 / ID4 | 设备 → 控制器;Write* 发送,Read* 读回本地影子值 |
| Q 区(输出面) | Q0.0 / QB0 / QW2 / QD4 | 控制器 → 设备;Read* 读取,Write* 不支持(返回 NotSupported) |
| M 区(内部存储) | M0.0 / MB0 / MW0 / MD0 | 映射输入面,仅本机可见(仿 PLC M 区) |
| DB 区(槽位数据块) | DB1.DBX0.0 / DB1.DBB2 / DB1.DBW0 / DB1.DBD4 | 槽位 1 数据块;写 = 输入面,读 = 输出面(DB 编号 = 槽位号) |
- 字 / 双字偏移即字节偏移,不强制 2 / 4 对齐(HSL 习惯:IW3 / DB1.DBW3 合法,与
Pdo.GetFieldAddress生成的未对齐字段地址自洽)。 - 字 / 双字均为大端序(PROFINET 网络序)。
- 连接服务模式为服务端单一线性过程区(无槽位概念),DB 仅槽位 1 有效(槽位 > 1 返回
NotFound)。 - 地址化读写返回
PnetErrorCode(SDK 调用返回错误码的契约,不抛出)。
// 写输入位 I0.0(设备 → 控制器)
pnet.WriteBool("I0.0", true);
// 写输入字 IW2(设备 → 控制器,大端序)
pnet.WriteInt16("IW2", counter);
// 读输出字 QW0(控制器 → 设备)
pnet.ReadInt16("QW0", out short output);
// 读输出位 Q0.0(控制器 → 设备)
pnet.ReadBool("Q0.0", out bool startCmd);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
ReadBool(string address, out bool value) | PnetErrorCode | IO | 只读 | 读位(I / Q / M / DB 位地址) |
WriteBool(string address, bool value) | PnetErrorCode | IO | 写 | 写位(I / M / DB;Q 区只读返回 NotSupported) |
ReadInt16(string address, out short value) | PnetErrorCode | IO | 只读 | 读字(IW2 / QW2 / MW0 / DB1.DBW0,大端) |
WriteInt16(string address, short value) | PnetErrorCode | IO | 写 | 写字(I / M / DB;Q 区只读返回 NotSupported) |
ReadInt32(string address, out int value) | PnetErrorCode | IO | 只读 | 读双字(ID4 / QD4 / MD0 / DB1.DBD0,大端) |
WriteInt32(string address, int value) | PnetErrorCode | IO | 写 | 写双字(I / M / DB;Q 区只读返回 NotSupported) |
PDO 结构体映射(ETH 式 PDO 子对象)
Pdo 把 C# 结构体按 [StructLayout(LayoutKind.Sequential, Pack = 1)] 偏移映射到过程映像字节区,与地址化读写同源同区:
Pdo.Read(ref T):结构体 → 输入过程映像(I 区,设备 → 控制器);Pdo.Write(ref T):输出过程映像(Q 区,控制器 → 设备)→ 结构体;Pdo.GetFieldAddress<T>(name):字段名 → I/Q 地址(供地址化读写按字段访问)。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PdoData
{
public byte A; // 偏移 0
public short B; // 偏移 1(大端)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] D; // 偏移 3
}
// 发送:结构体 → 输入过程映像(设备 → 控制器)
PdoData tx = new PdoData { A = 1, B = -2, D = new byte[] { 9, 8, 7, 6 } };
pnet.Pdo.Read(ref tx); // 或 pnet.ReadStruct(ref tx)
// 接收:输出过程映像(控制器 → 设备)→ 结构体
PdoData rx = new PdoData();
pnet.Pdo.Write(ref rx); // 或 pnet.WriteStruct(ref rx)
// 字段 → 地址(供地址化读写)
string addr = pnet.Pdo.GetFieldAddress<PdoData>("B"); // "IW1"
pnet.ReadInt16(addr, out short b);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
Pdo.Read<T>(ref T data) | PnetErrorCode | PDO | 写 | 结构体 → 输入过程映像(设备 → 控制器) |
Pdo.Write<T>(ref T data) | PnetErrorCode | PDO | 只读 | 输出过程映像 → 结构体(控制器 → 设备) |
Pdo.GetFieldAddress<T>(string fieldName) | string | PDO | 只读 | 字段名 → I 区地址(默认 Input) |
Pdo.GetFieldAddress<T>(string fieldName, PnetAddressArea area) | string | PDO | 只读 | 字段名 → I / Q 区地址(显式方向) |
ReadStruct<T>(ref T data) | PnetErrorCode | PDO | 写 | Pdo.Read 便捷转发 |
WriteStruct<T>(ref T data) | PnetErrorCode | PDO | 只读 | Pdo.Write 便捷转发 |
支持字段类型:数值(sbyte / byte / short / ushort / int / uint / long / ulong / float / double)/ bool(1 字节)/ 枚举 / 定长 byte[](MarshalAs(UnmanagedType.ByValArray, SizeConst=N))/ 嵌套结构体(递归)。字 / 双字 / 8 字节均为大端序。结构体跨槽位边界时按槽位分段读写。
变量联想(仿 PLC SymbolTree 层级联想)
Variables 懒加载解析 LoadConfig 已加载的运行配置 XML(SlotLayout 的模块 / 子模块 / IoItem 名称 → 变量表;未走 LoadConfig 或 XML 无 SlotLayout 时为空表)。
// 层级联想:空前缀 = 全部;输 "X" 只匹配当前层,不命中深层(仿 SymbolTree)
foreach (PnetVariable v in pnet.SuggestVariables("数字量模块."))
{
Console.WriteLine($"{v.Name} = {v.Address} ({v.Direction})");
}
// 精确查找(完整层级名,大小写不敏感)→ 含地址,供 Read*/Write* 直接使用
PnetVariable v = pnet.GetVariable("输入输出模块 1.数字量 IO.数字输出 0");
pnet.WriteBool(v.BitAddress, true);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
Variables | PnetVariables | 变量 | 只读 | 变量表子对象(懒加载) |
SuggestVariables(string prefix) | IReadOnlyList<PnetVariable> | 变量 | 只读 | 前缀层级联想("模块.子模块." 下钻) |
GetVariable(string name) | PnetVariable | 变量 | 只读 | 精确查找(完整层级名),未找到返回 null |
PnetVariable
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
Name | string | 变量 | 只读 | 完整层级名(模块.子模块.IoItem,. 为层级分隔符) |
Address | string | 变量 | 只读 | 规范地址(IB/IW/ID 或 QB/QW/QD,按长度与对齐推导) |
BitAddress | string | 变量 | 只读 | 位地址(字节第 0 位),供 ReadBool / WriteBool 直接使用 |
DataType | PnetAddressDataType | 变量 | 只读 | 数据类型(字节 / 字 / 双字) |
Direction | PnetIoDirection | 变量 | 只读 | 方向(输入 = 设备 → 控制器;输出 = 控制器 → 设备) |
Slot / Subslot / Offset / Length | ushort / ushort / int / int | 变量 | 只读 | 槽位 / 子槽位 / 子槽内偏移 / 数据长度 |
子对象(ETH 式,仅 native 直连模式)
连接服务模式无本地子对象(状态 / 报警 / 诊断由服务端单一权威维护),访问抛
NotSupportedException。
Slave — 从站控制
pnet.Slave.PlugSubmodule(1, 1, 1, 1, PnetIoDirection.InOut, 8, 8);
pnet.Slave.ApplicationReady();
var state = pnet.Slave.State;
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
State | PnetState | 从站 | 只读 | 运行态:None / Idle / Connecting / Parameterized / ApplicationReady / DataExchange / Aborted |
LastNativeResult | int | 从站 | 只读 | 最近一次原生调用结果 |
Arep | uint | 从站 | 只读 | 当前 AR 标识(未连接为 uint.MaxValue) |
ApplicationReady() | PnetErrorCode | 从站 | 写 | 应用就绪(APPLRDY,参数交换完成) |
Abort() | PnetErrorCode | 从站 | 写 | 中止连接(释放 AR) |
FactoryReset() | PnetErrorCode | 从站 | 写 | 恢复出厂设置 |
PlugModule(ushort slot, uint moduleIdent, uint api = 0) | PnetErrorCode | 从站 | 写 | 插入模块 |
PlugSubmodule(slot, subslot, moduleIdent, submoduleIdent, direction, lengthInput, lengthOutput) | PnetErrorCode | 从站 | 写 | 插入子模块(注册过程映像) |
PullModule / PullSubmodule | PnetErrorCode | 从站 | 写 | 拔出模块 / 子模块 |
GetArErrorCodes(out errorClass, out errorCode) | PnetErrorCode | 从站 | 只读 | 读取 AR 错误码(诊断) |
Io — 过程数据(槽位级)
pnet.Io.RegisterSubslot(0, 1, 1, PnetIoDirection.InOut, 8, 8);
pnet.Io.WriteInput(1, 1, data); // 设备 → 控制器
pnet.Io.ReadOutput(1, 1, buf, out iops, out newData); // 控制器 → 设备
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
RegisterSubslot(api, slot, subslot, direction, inputLength, outputLength) | void | IO | 写 | 注册槽位并建过程映像缓冲(线性化映射按注册顺序拼接) |
RegisteredSlotCount | int | IO | 只读 | 已注册槽位数 |
WriteInput(slot, subslot, data, iops) | PnetErrorCode | IO | 写 | 写输入区(设备 → 控制器),周期发出 |
ReadOutput(slot, subslot, data, out iops, out newData) | PnetErrorCode | IO | 只读 | 读输出区(控制器 → 设备),含质量与新增标志 |
ReadInputIocs(slot, subslot, out iocs) | PnetErrorCode | IO | 只读 | 读输入区质量状态(IOPS) |
WriteOutputIocs(slot, subslot, iocs) | PnetErrorCode | IO | 写 | 主动设置输出区 IOCS(默认驱动按接收结果维护) |
Alarm — 报警
pnet.Alarm.SendProcessAlarm(new PnetLocation(0, 1, 1, 0), 0x0001, payload);
pnet.Alarm.SendAck(new PnetLocation(0, 1, 1, 0), PnetAlarmType.Process, sequenceNumber);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
SendProcessAlarm(PnetLocation location, ushort payloadUsi = 0, byte[] payload = null) | PnetErrorCode | 报警 | 写 | 发送过程报警(USI + 负载;在途未确认时返回 Busy) |
SendAck(PnetLocation location, PnetAlarmType alarmType, ushort sequenceNumber) | PnetErrorCode | 报警 | 写 | 应答控制器报警(超时未应答控制器会重发) |
IsSendInFlight | bool | 报警 | 只读 | 是否有报警发送在途(等待 ACK) |
Config — 运行配置(只读诊断)
Console.WriteLine(pnet.Config.StationName);
Console.WriteLine(pnet.Config.IpAddress);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
StationName | string | 配置 | 只读 | 站点名(PROFINET 设备名,单源 = GUI → 运行配置 XML) |
ProductName | string | 配置 | 只读 | 产品名 |
VendorId / DeviceId | ushort / ushort | 配置 | 只读 | 厂商 / 设备标识(与 GSDML 一致) |
MainNetifName | string | 配置 | 只读 | 绑卡网卡名 |
IpAddress / IpMask / IpGateway | IPAddress | 配置 | 只读 | 网络配置 |
TickUs | uint | 配置 | 只读 | 周期(µs,默认 1000 = 1 ms) |
MinDeviceInterval | ushort | 配置 | 只读 | 最小数据交换周期(31.25 µs 单位,RT Class 1 最小 32 = 1 ms) |
用户不配置(配置由 GUI 导出 XML 经
LoadConfig加载),仅诊断用。
Diag — 诊断
Console.WriteLine(pnet.Diag.AlarmSendCount);
pnet.Diag.AddStandardDiag(...);
| 成员 | 类型 | 类别 | 读写 | 说明 |
|---|---|---|---|---|
AlarmSendCount / AlarmSendFailedCount | ulong | 诊断 | 只读 | 报警发送计数 / 失败数 |
CycleCount | ulong | 诊断 | 只读 | 周期驱动计数 |
ActiveDiagCount | int | 诊断 | 只读 | 存活诊断条目数 |
AddStandardDiag / UpdateStandardDiag / RemoveStandardDiag | PnetErrorCode | 诊断 | 写 | 标准格式诊断条目管理 |
AddUsiDiag(PnetLocation location, ushort usi, byte[] manufacturerData) | PnetErrorCode | 诊断 | 写 | 厂商特定(USI)诊断 |
ResetCounters() | void | 诊断 | 写 | 复位计数 |
枚举速查
| 枚举 | 值 | 说明 |
|---|---|---|
PnetState | None / Idle / Connecting / Parameterized / ApplicationReady / DataExchange / Aborted / Resetting / Unknown | 从站运行态 |
PnetErrorCode | Success / InvalidArgument / InvalidHandle / NotConnected / NativeError / LengthMismatch / NotFound / Busy / NotSupported / NativeDllNotFound / PlatformNotSupported | SDK 调用错误码 |
PnetRuntimeErrorCode | Ok / ErrInit / ErrCfg / ErrThread / ErrRunning / ErrPdi / Unknown | 服务端运行错误码(ErrorCode 枚举化,值对齐服务端 pnet_stack_err_t:Ok=0 / ErrInit=-1 / ErrCfg=-2 / ErrThread=-3 / ErrRunning=-4 / ErrPdi=-5 / Unknown=-10000) |
PnetAddressArea | Input / Output / Memory / Db | 地址区域 |
PnetAddressDataType | Bit / Byte / Word / DWord | 地址数据类型 |
PnetIoDirection | None / Input / Output / InOut | IO 数据方向 |
PnetIoxs | Bad / Good | 过程数据质量状态(线上固定不可改) |
PnetAlarmType | Diagnosis / Process / Pull / Plug / Status / Update / Redundancy / ControllerProcess / Manufacturer | 报警类型 |
完整示例(服务模式)
using DarraPnet.Pnet;
// 1. 连接服务模式(经服务 HTTP 18840 转发,由服务承载从站运行)
// ★ 服务仅监听本地回环 127.0.0.1(audit-2026-08-10):未开放局域网监听,
// 仅本地回环可达;host 传非回环地址连接必失败
using (DarraPnet.Pnet.DarraPnet pnet = DarraPnet.Pnet.DarraPnet.Connect("127.0.0.1"))
{
// 2. 启动从站(POST /api/start)
if (pnet.Start() != PnetErrorCode.Success)
{
Console.WriteLine("启动失败: " + pnet.LastServiceError);
return;
}
// 3. 周期地址化读写(设备 → 控制器 输入面 / 控制器 → 设备 输出面)
short counter = 0;
while (pnet.State == PnetState.Idle || pnet.State == PnetState.DataExchange)
{
pnet.WriteBool("I0.0", counter % 2 == 0); // 写输入位
pnet.WriteInt16("IW2", counter); // 写输入字(大端)
pnet.ReadInt16("QW0", out short output); // 读输出字(控制器 → 设备)
counter++;
Thread.Sleep(10);
}
}
// 4. Dispose 自动 Stop(POST /api/stop,幂等)
native 直连示例(LoadConfig)
using DarraPnet.Pnet;
using (DarraPnet.Pnet.DarraPnet pnet = DarraPnet.Pnet.DarraPnet.LoadConfig(@"C:\Darra\Profinet\device.xml"))
{
if (pnet.Start() != PnetErrorCode.Success) return;
// 变量联想(SlotLayout 自动建表,地址供 Read*/Write* 直接使用)
PnetVariable v = pnet.GetVariable("输入输出模块 1.数字量 IO.数字输出 0");
pnet.WriteBool(v.BitAddress, true);
// PDO 结构体映射(结构体 → 输入过程映像)
PdoData tx = new PdoData { A = 1, B = -2 };
pnet.Pdo.Read(ref tx);
}