通过 SDK 读写 DB 块
本文介绍如何使用 Darra SDK 远程读写 PLC 数据块 (DB),适用于上位机监控、MES 数据采集和第三方系统集成。
建立连接
所有 DB 操作前需通过 DarraLink 协议建立连接。
连接参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| IP 地址 | PLC 运行时 IP | — |
| 端口 | DarraLink 端口 | 18821 |
| 超时 | 连接超时时间 | 5000ms |
using Darra.PLC.SDK;
var connection = new PlcConnection("192.168.1.100", 18821)
{
Timeout = TimeSpan.FromSeconds(5)
};
await connection.ConnectAsync();
读取 DB 块数据
读取基本类型
// 读取 BOOL 类型
bool bValue = await connection.ReadDbAsync<bool>("DB_Status", "bMotorRunning");
// 读取数值类型
float fValue = await connection.ReadDbAsync<float>("DB_ProcessParam", "rSetTemp");
short iValue = await connection.ReadDbAsync<short>("DB_ProcessParam", "iErrorCode");
int diValue = await connection.ReadDbAsync<int>("DB_Counter", "diTotalCount");
// 读取字符串
string sValue = await connection.ReadDbAsync<string>("DB_ProcessParam", "sRecipeName");
读取结构体
// 定义结构体 (与 PLC 端一致)
public class AxisConfig
{
public float MaxSpeed { get; set; }
public float AccelTime { get; set; }
public float DecelTime { get; set; }
public bool EnablePositive { get; set; }
public bool EnableNegative { get; set; }
}
// 读取整个结构体
var axisConfig = await connection.ReadDbAsync<AxisConfig>("DB_Machine", "stAxes[0]");
Console.WriteLine($"轴 0 最大速度: {axisConfig.MaxSpeed}");
// 读取结构体中的单个字段
float maxSpeed = await connection.ReadDbAsync<float>("DB_Machine", "stAxes[0].rMaxSpeed");
读取数组
// 读取整个数组
float[] temperatures = await connection.ReadDbAsync<float[]>("DB_ProcessParam", "arHistory");
Console.WriteLine($"历史数据点: {temperatures.Length}");
// 读取数组中的单个元素
float temp = await connection.ReadDbAsync<float>("DB_ProcessParam", "arHistory[5]");
// 读取数组切片 (部分读取)
float[] slice = await connection.ReadDbAsync<float[]>("DB_ProcessParam", "arHistory[0..9]");
批量读取
// 批量读取多个变量
var results = await connection.ReadBatchAsync(
("DB_ProcessParam.rSetTemp", typeof(float)),
("DB_ProcessParam.rActTemp", typeof(float)),
("DB_ProcessParam.sRecipeName", typeof(string)),
("DB_Status.bMotorRunning", typeof(bool))
);
foreach (var (name, value) in results)
{
Console.WriteLine($"{name} = {value}");
}
写入 DB 块数据
写入基本类型
// 写入 BOOL
await connection.WriteDbAsync("DB_Control", "bStartMotor", true);
// 写入数值
await connection.WriteDbAsync("DB_ProcessParam", "rSetTemp", 150.0f);
await connection.WriteDbAsync("DB_ProcessParam", "iErrorCode", 0);
// 写入字符串
await connection.WriteDbAsync("DB_ProcessParam", "sRecipeName", "标准配方");
写入结构体
// 写入整个结构体
var config = new AxisConfig
{
MaxSpeed = 3000.0f,
AccelTime = 0.5f,
DecelTime = 0.3f,
EnablePositive = true,
EnableNegative = true
};
await connection.WriteDbAsync("DB_Machine", "stAxes[0]", config);
// 写入结构体中的单个字段
await connection.WriteDbAsync("DB_Machine", "stAxes[0].rMaxSpeed", 2500.0f);
批量写入
// 批量写入 (原子操作)
await connection.WriteBatchAsync(
("DB_Control.bStartMotor", true),
("DB_ProcessParam.rSetTemp", 120.0f),
("DB_ProcessParam.sRecipeName", "高速配方")
);
订阅与通知
订阅变量变化
// 订阅单个变量
connection.Subscribe("DB_ProcessParam.rActTemp", (name, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"温度已更新: {temp:F1}°C");
});
// 订阅多个变量
connection.Subscribe("DB_ProcessParam.rSetTemp", OnSetpointChanged);
connection.Subscribe("DB_ProcessParam.rActTemp", OnFeedbackChanged);
connection.Subscribe("DB_Alarm.bActiveAlarm", OnAlarmChanged);
订阅过滤
// 设置变化阈值 (仅当变化超过阈值时通知)
connection.Subscribe("DB_ProcessParam.rActTemp", OnTempChanged,
changeThreshold: 0.5f); // 温度变化超过 0.5°C 才通知
// 设置最小通知间隔
connection.Subscribe("DB_Status.bMotorRunning", OnStatusChanged,
minIntervalMs: 100); // 100ms 内最多通知一次
订阅 DB 变量变化通知
SDK 支持对 DB 块中的变量进行订阅,当 PLC 侧变量值变化时,SDK 实时推送通知:
// 订阅单个 DB 变量
connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
(dbName, varName, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] DB {dbName}.{varName} = {temp:F1}");
}
);
// 订阅整个 DB 块的所有变量变化
connection.SubscribeDbBlock("DB_ProcessParam", (dbName, changes) =>
{
foreach (var (varName, value) in changes)
{
Console.WriteLine($"{dbName}.{varName} → {value}");
}
});
// 订阅带过滤条件的 DB 变量
connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
OnTemperatureChanged,
new SubscriptionFilter
{
ChangeThreshold = 0.5f, // 变化超过 0.5 才通知
MinIntervalMs = 200, // 200ms 内最多通知一次
DeadbandPercent = 1.0f // 1% 死区
}
);
取消订阅
// 取消单个订阅
connection.Unsubscribe("DB_ProcessParam.rActTemp");
// 取消整个 DB 块的订阅
connection.UnsubscribeDbBlock("DB_ProcessParam");
// 取消所有订阅
connection.UnsubscribeAll();
数据类型映射
SDK 与 PLC 之间的数据类型映射关系:
| PLC 类型 | C# 类型 | Python 类型 | Java 类型 | Rust 类型 | 字节数 |
|---|---|---|---|---|---|
| BOOL | bool | bool | boolean | bool | 1 |
| BYTE | byte | int | byte | u8 | 1 |
| WORD | ushort | int | int | u16 | 2 |
| DWORD | uint | int | long | u32 | 4 |
| LWORD | ulong | int | long | u64 | 8 |
| SINT | sbyte | int | byte | i8 | 1 |
| INT | short | int | short | i16 | 2 |
| DINT | int | int | int | i32 | 4 |
| LINT | long | int | long | i64 | 8 |
| REAL | float | float | float | f32 | 4 |
| LREAL | double | float | double | f64 | 8 |
| STRING[n] | string | str | String | String | n+2 |
| TIME | TimeSpan | int (ms) | Duration | Duration | 4 |
| DATE | DateTime | int | Date | NaiveDate | 4 |
| TOD | TimeSpan | int | LocalTime | Duration | 4 |
| ARRAY | T[] | list | T[] | Vec<T> | n × 元素大小 |
| STRUCT | class/struct | dict | class | struct | 各字段和 |
类型转换注意事项
// 1. 读取 INT 赋值给 C# short
short value = await connection.ReadDbAsync<short>("DB_ProcessParam", "iErrorCode");
// 2. 读取 REAL 赋值给 C# float
float temp = await connection.ReadDbAsync<float>("DB_ProcessParam", "rActTemp");
// 3. 读取 DWORD 赋值给 C# uint
uint flags = await connection.ReadDbAsync<uint>("DB_Status", "diFlags");
// 4. 读取 LREAL 赋值给 C# double
double precise = await connection.ReadDbAsync<double>("DB_ProcessParam", "rPreciseValue");
// 5. 类型不匹配时抛出 PlcTypeMismatchException
try
{
// PLC 端是 REAL,但尝试读取为 INT
int wrong = await connection.ReadDbAsync<int>("DB_ProcessParam", "rActTemp");
}
catch (PlcTypeMismatchException ex)
{
Console.WriteLine($"类型不匹配: 期望 {ex.ExpectedType}, 实际 {ex.ActualType}");
}
错误处理
连接错误
try
{
await connection.ConnectAsync();
}
catch (PlcConnectionException ex)
{
Console.WriteLine($"无法连接到 PLC: {ex.Message}");
Console.WriteLine($"目标: {ex.Endpoint}");
// 实施重试策略
await RetryConnectionAsync();
}
catch (PlcAuthenticationException ex)
{
Console.WriteLine($"认证失败: {ex.Message}");
// 检查凭据
}
读写错误
try
{
await connection.WriteDbAsync("DB_ProcessParam", "rSetTemp", value);
}
catch (PlcVariableNotFoundException ex)
{
Console.WriteLine($"变量不存在: {ex.VariableName}");
// 检查变量路径
}
catch (PlcTypeMismatchException ex)
{
Console.WriteLine($"类型不匹配: 期望 {ex.ExpectedType}, 实际 {ex.ActualType}");
}
catch (PlcTimeoutException ex)
{
Console.WriteLine($"操作超时: {ex.Message}");
// 重试
}
catch (PlcWriteProtectedException ex)
{
Console.WriteLine($"变量为只读: {ex.VariableName}");
// 检查 PLC 侧变量权限
}
finally
{
await connection.DisconnectAsync();
}
重试策略
// 带指数退避的重试策略
public async Task<T> ReadWithRetryAsync<T>(string dbName, string varName, int maxRetries = 3)
{
int retryCount = 0;
int baseDelayMs = 1000;
while (true)
{
try
{
return await connection.ReadDbAsync<T>(dbName, varName);
}
catch (PlcTimeoutException) when (retryCount < maxRetries)
{
retryCount++;
int delayMs = baseDelayMs * (int)Math.Pow(2, retryCount - 1);
Console.WriteLine($"读取超时,第 {retryCount} 次重试,等待 {delayMs}ms...");
await Task.Delay(delayMs);
}
catch (PlcConnectionException) when (retryCount < maxRetries)
{
retryCount++;
Console.WriteLine($"连接中断,尝试重新连接...");
await connection.ConnectAsync();
}
}
}
// 使用重试读取
float temp = await ReadWithRetryAsync<float>("DB_ProcessParam", "rActTemp");
完整示例: 配方管理
using Darra.PLC.SDK;
class RecipeManager
{
private readonly PlcConnection _connection;
public RecipeManager(string plcIp)
{
_connection = new PlcConnection(plcIp, 18821);
}
public async Task LoadRecipeAsync(string recipeName)
{
await _connection.ConnectAsync();
try
{
// 1. 停止当前工艺
await _connection.WriteDbAsync("DB_Control", "bStopProcess", true);
await Task.Delay(500);
// 2. 从本地数据库查询配方参数
var recipe = await QueryRecipeFromDatabase(recipeName);
// 3. 批量写入 PLC 配方参数
await _connection.WriteBatchAsync(
("DB_ProcessParam.rSetTemp", recipe.SetTemperature),
("DB_ProcessParam.rSetPressure", recipe.SetPressure),
("DB_ProcessParam.rSpeedLimit", recipe.SpeedLimit),
("DB_ProcessParam.sRecipeName", recipeName),
("DB_ProcessParam.iRecipeVersion", recipe.Version)
);
// 4. 读取写入确认
var verifyTemp = await _connection.ReadDbAsync<float>(
"DB_ProcessParam", "rSetTemp");
Console.WriteLine($"配方已加载: {recipeName}");
Console.WriteLine($"验证温度: {verifyTemp}°C");
// 5. 启动工艺
await _connection.WriteDbAsync("DB_Control", "bStartProcess", true);
}
catch (Exception ex)
{
Console.WriteLine($"配方加载失败: {ex.Message}");
throw;
}
finally
{
await _connection.DisconnectAsync();
}
}
private async Task<RecipeData> QueryRecipeFromDatabase(string name)
{
// 模拟数据库查询
return new RecipeData
{
SetTemperature = 150.0f,
SetPressure = 2.5f,
SpeedLimit = 3000.0f,
Version = 3
};
}
}
class RecipeData
{
public float SetTemperature { get; set; }
public float SetPressure { get; set; }
public float SpeedLimit { get; set; }
public int Version { get; set; }
}
完整 C# 示例:数据采集与监控
using Darra.PLC.SDK;
class DataCollector
{
private readonly PlcConnection _connection;
private readonly string _plcIp;
private readonly int _plcPort;
private CancellationTokenSource? _cts;
public DataCollector(string plcIp, int plcPort = 18821)
{
_plcIp = plcIp;
_plcPort = plcPort;
_connection = new PlcConnection(_plcIp, _plcPort)
{
AutoReconnect = true,
ReconnectIntervalMs = 5000,
MaxReconnectAttempts = 0
};
}
public async Task StartAsync(CancellationToken ct = default)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
await _connection.ConnectAsync();
Console.WriteLine("数据采集已启动");
// 订阅关键变量
SubscribeVariables();
// 启动采集循环
await CollectLoopAsync(_cts.Token);
}
private void SubscribeVariables()
{
// 订阅温度变化
_connection.SubscribeDbVariable(
"DB_ProcessParam", "rActTemp",
(db, varName, value) =>
{
float temp = Convert.ToSingle(value);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 温度: {temp:F1}°C");
if (temp > 150.0f)
{
Console.WriteLine("⚠ 温度超限报警!");
}
},
new SubscriptionFilter { ChangeThreshold = 0.5f }
);
// 订阅报警状态
_connection.SubscribeDbVariable(
"DB_Alarm", "bActiveAlarm",
(db, varName, value) =>
{
bool isActive = Convert.ToBoolean(value);
if (isActive)
{
string msg = _connection.ReadDbAsync<string>(
"DB_Alarm", "sAlarmMessage").Result;
Console.WriteLine($"🚨 报警: {msg}");
}
}
);
}
private async Task CollectLoopAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
// 批量读取
var data = await _connection.ReadBatchAsync(
("DB_ProcessParam.rActTemp", typeof(float)),
("DB_ProcessParam.rActPressure", typeof(float)),
("DB_Status.bMotorRunning", typeof(bool)),
("DB_Counter.diTotalCount", typeof(int))
);
// 写入数据库或文件
await SaveToDatabase(data);
await Task.Delay(1000, ct);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Console.WriteLine($"采集异常: {ex.Message}");
await Task.Delay(5000, ct);
}
}
}
private async Task SaveToDatabase(
IEnumerable<(string name, object value)> data)
{
// 写入时序数据库或本地文件
await Task.CompletedTask;
}
public void Stop()
{
_cts?.Cancel();
_connection?.Dispose();
}
}
性能优化
连接复用
// 长时间运行的应用程序应复用连接
public class PlcService : IDisposable
{
private readonly PlcConnection _connection;
public PlcService(string ip)
{
_connection = new PlcConnection(ip, 18821);
}
public async Task StartAsync()
{
await _connection.ConnectAsync();
// 持续使用同一连接
}
public void Dispose()
{
_connection?.Dispose();
}
}
批量操作
尽量使用批量读写替代单次操作,减少网络往返:
| 方式 | 100 个变量耗时 | 说明 |
|---|---|---|
| 单次读取 × 100 | ~2s | 100 次网络往返 |
| 批量读取 × 1 | ~20ms | 1 次网络往返 |
排错表
| 问题 | 原因 | 解决 |
|---|---|---|
| 连接超时 | PLC 不可达或端口错误 | 检查 IP 和端口 (18821) |
| 变量未找到 | 变量路径拼写错误 | 确认 DB 名称和变量名 |
| 类型转换异常 | SDK 类型与 PLC 不匹配 | 确认 PLC 变量数据类型 |
| 写入被拒绝 | 变量被强制或只读 | 在 IDE 中取消强制 |
| 订阅无响应 | 网络防火墙阻断 | 检查 18821 端口通信 |
| 批量写入部分失败 | 其中一个变量异常 | 检查批量写入的错误列表 |
相关文档
- SDK 集成概述 — SDK 快速入门
- HTTP REST API — HTTP 方式访问 PLC
- OPC UA — OPC UA 集成
- DB 块访问 — PLC DB 块概念详解