WinM.S7 3.2.0

WinM.S7

| 3.0.x版本目前与Connection.S7保持一致,只是单独提出仓库,优化部分代码,并未做破坏性改动

用于西门子S7系列PLC通讯的.NET库,提供简单易用的API接口,支持数据读写、状态监控和自动重连等功能。

功能特性

  • 简单易用的API:提供直观的接口进行PLC连接和数据交互
  • 自动重连:在网络中断时自动尝试重新连接,支持自定义重连策略
  • 状态监控:实时监控PLC连接和运行状态,提供丰富的状态事件
  • 数据块管理:高效管理和缓存数据块实例,支持并发访问
  • 类型安全:支持强类型数据读写,自动类型转换和验证
  • 异步操作:全面支持异步通信方法,提升应用程序响应性
  • 事件驱动:提供丰富的事件通知机制,包括写入成功/失败事件
  • 线程安全:内部实现保证多线程环境下的安全性
  • 节点管理:支持S7PlcNode节点模式,简化数据点管理
  • 结构体支持:支持S7PlcNodeStruct,实现批量高效读取
  • 配置管理:支持JSON配置文件,便于部署和维护
  • 缓存机制:内置PLC实例和配置缓存,提升性能

安装

NuGet包

dotnet add package WinM.S7

项目引用

<PackageReference Include="WinM.S7" Version="2.1.1" />

快速开始

基本连接

using WinM.S7;
using S7.Net;

// 创建PLC实例
var plc = new S7Plc(CpuType.S71500, "192.168.0.1", rack: 0, slot: 1);

// 启用自动重连
plc.UseAutoReconnection(enable: true, reconnectionDelay: TimeSpan.FromSeconds(2));

// 连接PLC
await plc.ConnectAsync();

// 检查连接状态
if (plc.IsConnected)
{
    Console.WriteLine("PLC已连接");
}

// 使用完毕后释放资源
plc.Dispose();

读写数据

// 读取数据
var value = plc.DB(1).ReadInt("0");  // 从DB1.DBW0读取一个Int值
var realValue = plc.DB(1).ReadFloat("4");  // 从DB1.DBD4读取一个Real值
var stringValue = plc.DB(1).ReadString(8, 20);  // 从DB1.DBB8读取一个长度为20的字符串

// 写入数据
plc.DB(1).WriteInt("0", 100);  // 向DB1.DBW0写入Int值100
plc.DB(1).WriteFloat("4", 123.45f);  // 向DB1.DBD4写入Real值123.45
plc.DB(1).WriteString(8, "Hello S7");  // 向DB1.DBB8写入字符串"Hello S7"

地址字符串读写 (KepServer 风格)

支持类似 KepServer 的地址格式,可以直接使用地址字符串进行读写操作。

支持的地址格式

地址格式 说明 数据类型
DB1.DBX0.0 DB区域位地址 bool
DB1.DBB0 DB区域字节 byte
DB1.DBW0 DB区域字 (16位) short/ushort
DB1.DBD0 DB区域双字 (32位) int/uint/float
DB1.STRING0.50 S7字符串 (最大长度50) string
DB1.STRING0 S7字符串 (默认长度254) string
DB1.WSTRING0.50 S7宽字符串 string
DB1.CHAR0.50 字符数组 string
DB1.DTL0 日期时间 DateTime
I0.0 输入区位地址 bool
IB0 输入区字节 byte
IW0 输入区字 ushort
ID0 输入区双字 uint
Q0.0 输出区位地址 bool
M0.0 内存区位地址 bool
MB0 内存区字节 byte

读取示例

// 基本读取 (返回 object)
object value = plc.Read("DB1.DBW0");

// 泛型读取 - 指定返回类型
bool bitValue = plc.Read<bool>("DB1.DBX0.0");           // 读取位
short intValue = plc.Read<short>("DB1.DBW0");           // 读取 INT
ushort uintValue = plc.Read<ushort>("DB1.DBW0");        // 读取 UINT
int dintValue = plc.Read<int>("DB1.DBD0");              // 读取 DINT
uint udintValue = plc.Read<uint>("DB1.DBD0");           // 读取 UDINT
float realValue = plc.Read<float>("DB1.DBD0");          // 读取 REAL
string strValue = plc.Read<string>("DB1.STRING0.50");   // 读取字符串

// 便捷方法
bool b = plc.ReadBool("DB1.DBX0.0");
short s = plc.ReadShort("DB1.DBW0");
int i = plc.ReadInt("DB1.DBD0");
float f = plc.ReadFloat("DB1.DBD0");
string str = plc.ReadString("DB1.STRING0.50");

// 异步读取
float asyncValue = await plc.ReadAsync<float>("DB1.DBD0");

// I/Q/M 区域读取
bool inputBit = plc.Read<bool>("I0.0");       // 输入区位
byte inputByte = plc.Read<byte>("IB0");       // 输入区字节
ushort inputWord = plc.Read<ushort>("IW0");   // 输入区字
bool outputBit = plc.Read<bool>("Q0.0");      // 输出区位
bool memBit = plc.Read<bool>("M0.0");         // 内存区位

写入示例

// 基本写入
plc.Write("DB1.DBX0.0", true);              // 写入位
plc.Write("DB1.DBW0", (short)100);          // 写入 INT
plc.Write("DB1.DBW0", (ushort)100);         // 写入 UINT
plc.Write("DB1.DBD0", 123456);              // 写入 DINT
plc.Write("DB1.DBD0", 3.14f);               // 写入 REAL
plc.Write("DB1.STRING0.50", "Hello");       // 写入字符串

// 异步写入
await plc.WriteAsync("DB1.DBD0", 3.14f);

// I/Q/M 区域写入
plc.Write("Q0.0", true);    // 写入输出区位
plc.Write("M0.0", false);   // 写入内存区位

类型说明

  • DBW (字): 16位,支持 short (INT) 和 ushort (UINT)
  • DBD (双字): 32位,支持 int (DINT)、uint (UDINT) 和 float (REAL)
  • 读取时使用泛型指定目标类型,如 Read<float>("DB1.DBD0") 读取 REAL 值

使用节点

// 创建节点
var node = new S7PlcNode
{
    Title = "温度传感器",
    Type = "real",
    Db = 1,
    Offset = "10",
    BitLength = 4
};

// 读取节点值
float temperature = plc.ReadNode<float>(node);

// 写入节点值
node.Value = 25.5f;
bool success = plc.WriteNode(node);

// 或者直接写入
success = plc.WriteNode(node, 25.5f);

// 监听节点写入事件
node.WriteSucceeded += (node, value) =>
{
    Console.WriteLine($"节点 {node.Title} 写入成功,值: {value}");
};

node.WriteFailed += (node, attemptedValue, exception) =>
{
    Console.WriteLine($"节点 {node.Title} 写入失败: {exception.Message}");
};

监视数据变化

// 创建节点
var node = new S7PlcNode
{
    Title = "生产计数器",
    Type = "int",
    Db = 1,
    Offset = "20"
};

// 设置监视
plc.Watch(node, value =>
{
    Console.WriteLine($"计数器值变化: {value}");
});

事件处理

// 连接事件
plc.OnConnectedHandler += (sender, e) => Console.WriteLine("PLC已连接");
plc.OnDisconnectedHandler += (sender, e) => Console.WriteLine("PLC已断开");

// 状态事件
plc.OnRunningHandler += (sender, e) => Console.WriteLine("PLC运行中");
plc.OnStopedHandler += (sender, e) => Console.WriteLine("PLC已停止");

// 错误事件
plc.OnErrorHandler += (ex) => Console.WriteLine($"发生错误: {ex.Message}");

心跳和就绪状态

// 设置心跳节点
var heartbeatNode = new S7PlcNode
{
    Title = "心跳",
    Type = "bool",
    Db = 1,
    Offset = "0.0"
};

// 启用心跳
plc.UseHeartbeat(heartbeatNode, status => 
{
    Console.WriteLine($"心跳状态: {status}");
});

// 设置就绪状态节点
var readyNode = new S7PlcNode
{
    Title = "就绪",
    Type = "bool",
    Db = 1,
    Offset = "0.1"
};

// 设置就绪状态
plc.UseReadyStatus(readyNode, true);

读取节点结构体 (S7PlcNodeStruct)

S7PlcNodeStruct 类允许您定义一个包含多个独立 S7PlcNode 对象的有序逻辑集合(结构体)。这些节点可以有不同的数据类型和地址。 通过 S7Plc.ReadStruct() 方法,您可以一次性读取结构体中定义的所有节点。该方法足够智能:

  • 它会检测结构体内连续的、同DB、同类型的节点序列。
  • 对于这样的序列,它会使用一次优化的批量读取操作。
  • 不符合连续条件的节点会被单独读取。 为了利用批量读取优化,请确保在将节点添加到S7PlcNodeStruct时,连续的节点是按其在PLC内存中的顺序添加的。
// 1. 创建 S7PlcNodeStruct 实例
var complexDeviceData = new S7PlcNodeStruct("ComplexDeviceMetrics");

// 2. 向结构体中添加 S7PlcNode 定义 (注意顺序以优化读取)
// 一组连续的Reals
complexDeviceData.AddNode(new S7PlcNode { Title = "Pressure", Db = 5, Offset = "0", Type = "Real" });
complexDeviceData.AddNode(new S7PlcNode { Title = "Temperature", Db = 5, Offset = "4", Type = "Real" });
complexDeviceData.AddNode(new S7PlcNode { Title = "FlowRate", Db = 5, Offset = "8", Type = "Real" });

// 一个单独的Bool
complexDeviceData.AddNode(new S7PlcNode { Title = "SystemEnabled", Db = 5, Offset = "12.0", Type = "Bool" });

// 另一个单独的DInt
complexDeviceData.AddNode(new S7PlcNode { Title = "CycleCounter", Db = 5, Offset = "14", Type = "DInt" });

// 3. 使用 S7Plc 实例的 ReadStruct 方法读取数据
Dictionary<string, object> allValues = plc.ReadStruct(complexDeviceData);

// 4. 访问数据
if (allValues.TryGetValue("Temperature", out object tempObj) && tempObj is float temp)
{
    Console.WriteLine($"温度: {temp}");
}
if (allValues.TryGetValue("SystemEnabled", out object enabledObj) && enabledObj is bool enabled)
{
    Console.WriteLine($"系统启用: {enabled}");
}
// ... 同样的方式访问其他节点

详细用法请参见 README.S7PlcNodeStruct.md

高级用法

批量写入节点

// 创建多个节点
var nodes = new List<S7PlcNode>
{
    new S7PlcNode { Title = "节点1", Type = "int", Db = 1, Offset = "0", Value = 100 },
    new S7PlcNode { Title = "节点2", Type = "real", Db = 1, Offset = "2", Value = 123.45f },
    new S7PlcNode { Title = "节点3", Type = "bool", Db = 1, Offset = "6.0", Value = true }
};

// 批量写入
int successCount = plc.WriteNodes(nodes);
Console.WriteLine($"成功写入 {successCount}/{nodes.Count} 个节点");

异步操作

// 异步连接
await plc.ConnectAsync(timeoutSeconds: 5);

// 异步写入
await plc.WriteNodeAsync(node, 100);

// 异步批量写入
await plc.WriteNodesAsync(nodes);

使用配置文件

// 加载配置
var config = new S7PlcConfig();
config.Load();

// 使用配置创建PLC实例
var plc = config.GetPlc();

// 连接
await plc.ConnectAsync();

// 使用配置中的节点
var node = config.GetNode("温度传感器");
if (node != null)
{
    float value = plc.ReadNode<float>(node);
    Console.WriteLine($"温度: {value}");
}

S7PlcNode 详细使用指南

创建不同类型的节点

// 布尔类型节点
var runningStatus = new S7PlcNode
{
    Title = "设备运行状态",
    Type = "bool",
    Db = 1,
    Offset = "0.0",  // DB1.DBX0.0
    Description = "设备是否正在运行"
};

// 整数类型节点
var counter = new S7PlcNode
{
    Title = "生产计数器",
    Type = "dint",
    Db = 1,
    Offset = "4",  // DB1.DBD4
    Description = "当前生产数量"
};

// 浮点数类型节点
var temperature = new S7PlcNode
{
    Title = "炉温",
    Type = "real",
    Db = 1,
    Offset = "8",  // DB1.DBD8
    Description = "炉子温度,单位:摄氏度"
};

// 字符串类型节点
var deviceName = new S7PlcNode
{
    Title = "设备名称",
    Type = "string",
    Db = 1,
    Offset = "20",  // DB1.DBB20
    BitLength = 50,  // 最大50个字符
    Description = "设备标识名称"
};

节点事件处理

var setpointNode = new S7PlcNode
{
    Title = "温度设定值",
    Type = "real",
    Db = 1,
    Offset = "100",
    NeedFeedback = true
};

// 写入成功事件
setpointNode.WriteSucceeded += (node, value) =>
{
    Console.WriteLine($"✓ {node.Title} 设置成功: {value}");
    // 可以更新UI状态
    UpdateUI($"{node.Title} 已更新");
};

// 写入失败事件
setpointNode.WriteFailed += (node, attemptedValue, exception) =>
{
    Console.WriteLine($"✗ {node.Title} 设置失败: {exception.Message}");
    Console.WriteLine($"尝试设置的值: {attemptedValue}");

    // 可以实现重试逻辑
    if (exception is TimeoutException)
    {
        Console.WriteLine("网络超时,将在3秒后重试...");
        Task.Delay(3000).ContinueWith(_ =>
        {
            plc.WriteNode(node, attemptedValue);
        });
    }
};

// 执行写入操作
await plc.WriteNodeAsync(setpointNode, 75.5f);

批量节点操作

// 创建一组相关的节点
var processNodes = new List<S7PlcNode>
{
    new S7PlcNode { Title = "进料温度", Type = "real", Db = 10, Offset = "0" },
    new S7PlcNode { Title = "出料温度", Type = "real", Db = 10, Offset = "4" },
    new S7PlcNode { Title = "压力值", Type = "real", Db = 10, Offset = "8" },
    new S7PlcNode { Title = "流量值", Type = "real", Db = 10, Offset = "12" },
    new S7PlcNode { Title = "设备状态", Type = "int", Db = 10, Offset = "16" }
};

// 为所有节点设置统一的事件处理
foreach (var node in processNodes)
{
    node.WriteSucceeded += (n, v) =>
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {n.Title}: {v}");

    node.WriteFailed += (n, v, e) =>
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {n.Title} 失败: {e.Message}");
}

// 批量读取
var results = new Dictionary<string, object>();
foreach (var node in processNodes)
{
    try
    {
        var value = plc.ReadNode<object>(node);
        results[node.Title] = value;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"读取 {node.Title} 失败: {ex.Message}");
    }
}

// 批量写入(设置新的目标值)
var targetValues = new Dictionary<string, object>
{
    ["进料温度"] = 80.0f,
    ["出料温度"] = 75.0f,
    ["压力值"] = 2.5f,
    ["流量值"] = 150.0f,
    ["设备状态"] = 1
};

foreach (var node in processNodes)
{
    if (targetValues.ContainsKey(node.Title))
    {
        node.Value = targetValues[node.Title];
    }
}

int successCount = await plc.WriteNodesAsync(processNodes);
Console.WriteLine($"成功写入 {successCount}/{processNodes.Count} 个节点");

API参考

S7Plc类

主要的PLC连接和操作类。

属性

  • IsConnected - 获取当前PLC连接状态
  • IsRunning - 获取PLC是否处于运行状态
  • Name - 获取PLC连接名称

方法

  • Connect() - 同步连接PLC
  • ConnectAsync(int timeoutSeconds = 3) - 异步连接PLC
  • Disconnect() - 同步断开PLC连接
  • DisconnectAsync() - 异步断开PLC连接
  • UseAutoReconnection(bool enable = true, TimeSpan? reconnectionDelay = null) - 配置自动重连
  • DB(int id) - 获取指定编号的数据块实例
  • UseBlock(int db) - DB方法的别名
  • ReadNode<T>(S7PlcNode node) - 从指定节点读取值
  • ReadStruct(S7PlcNodeStruct plcStruct) - 读取S7PlcNodeStruct中定义的所有节点的值
  • WriteNode(S7PlcNode node) - 向指定节点写入值
  • WriteNode(S7PlcNode node, object value) - 向指定节点写入指定值
  • WriteNodeAsync(S7PlcNode node) - 异步向指定节点写入值
  • WriteNodeAsync(S7PlcNode node, object value) - 异步向指定节点写入指定值
  • WriteNodes(IEnumerable<S7PlcNode> nodes) - 批量写入多个节点
  • WriteNodesAsync(IEnumerable<S7PlcNode> nodes) - 异步批量写入多个节点
  • Watch(S7PlcNode node, WatchModel.WatchCallbackDelegate<object> cb) - 设置节点值监视
  • UseHeartbeat(S7PlcNode heartbeatNode, HeartbeatCallback? cb = null) - 使用心跳功能
  • UseReadyStatus(S7PlcNode readyNode, bool ready = true) - 使用就绪状态功能
  • SetReadyStatus(bool ready = true) - 设置就绪状态
  • SetReadyStatus(S7PlcNode readyNode) - 设置指定节点的就绪状态
  • GetReadyStatus(S7PlcNode readyNode) - 获取就绪状态
  • UnWatch(WatchModel model) - 移除数据监视

S7PlcDb类

数据块操作类,提供对特定DB块的读写功能。

方法

  • ReadBool(string offset) - 读取布尔值
  • ReadByte(string offset) - 读取字节值
  • ReadInt(string offset) - 读取Int值
  • ReadUInt(string offset) - 读取UInt值
  • ReadDInt(string offset) - 读取DInt值
  • ReadUDInt(string offset) - 读取UDInt值
  • ReadFloat(string offset) - 读取Float值
  • ReadDouble(string offset) - 读取Double值
  • ReadString(int offset, int length = 254) - 读取字符串
  • ReadDateTime(string offset) - 读取日期时间
  • WriteBool(string offset, bool value) - 写入布尔值
  • WriteInt(string offset, short value) - 写入Int值
  • WriteUInt(string offset, ushort value) - 写入UInt值
  • WriteDInt(string offset, int value) - 写入DInt值
  • WriteUDInt(string offset, uint value) - 写入UDInt值
  • WriteFloat(string offset, float value) - 写入Float值
  • WriteDouble(string offset, double value) - 写入Double值
  • WriteString(int offset, string value, int length = 254) - 写入字符串
  • WriteDateTime(string offset, DateTime value) - 写入日期时间
  • Watch(WatchModel model) - 添加数据监视
  • Watch(S7PlcNode node, WatchModel.WatchCallbackDelegate<object> callback) - 添加节点数据监视
  • UnWatch(WatchModel model) - 移除数据监视

S7PlcNode类

表示PLC中的一个数据节点,是进行PLC数据交互的基础单元。详细文档请参阅 README.S7PlcNode.md

核心属性

  • Title - 节点名称,用作唯一标识符
  • Type - 数据类型(bool, int, uint, dint, udint, real, double, string, datetime)
  • Db - DB块编号
  • Offset - 偏移量(支持位偏移,如"10.0")
  • BitLength - 位长度(主要用于字符串类型,默认255)
  • Value - 节点的当前值(不参与序列化)
  • Description - 节点描述
  • Enabled - 节点是否启用(默认true)

反馈机制属性

  • NeedFeedback - 是否需要写入反馈确认
  • FeedbackNodeKey - 反馈节点的键(自动生成为".feedback")

地址范围属性

  • StartAddress - 起始字节地址(用于批量读取优化)
  • EndAddress - 结束字节地址(用于批量读取优化)
  • Items - 节点位置对照列表(用于枚举类型)

事件

  • WriteSucceeded - 写入成功事件
  • WriteFailed - 写入失败事件

方法

  • GetNodeDataType() - 获取节点对应的C#数据类型
  • Clone() - 创建节点的浅拷贝
  • Validate() - 验证节点基本有效性
  • Equals(S7PlcNode other) - 比较两个节点是否相等

静态方法

  • Create(string title, string type, int db, string offset) - 创建节点的工厂方法

接口实现

  • INotifyPropertyChanged - 属性更改通知
  • ICloneable - 支持克隆操作
  • IEquatable<S7PlcNode> - 支持相等性比较

S7PlcNodeStruct 类

用于定义一个包含多个独立 S7PlcNode 对象的逻辑集合(结构体),以便通过 S7Plc.ReadStruct() 方法一次性读取它们。详细信息请参阅 README.S7PlcNodeStruct.md

文档结构

本库提供了完整的文档体系,包括:

  • README.md - 主文档,包含快速开始和基本使用指南
  • README.S7Plc.md - S7Plc类的详细使用说明和API参考
  • README.S7PlcNode.md - S7PlcNode类的完整文档,包含所有属性、方法和使用示例
  • README.S7PlcNodeStruct.md - S7PlcNodeStruct类的使用指南,用于批量读取优化
  • README.WriteNode.md - WriteNode方法的详细使用指南
  • README.S7PlcCache.md - S7PlcCache缓存管理的使用说明

建议按照以下顺序阅读文档:

  1. 首先阅读本文档了解基本概念和快速开始
  2. 深入学习 README.S7PlcNode.md 了解节点的详细使用
  3. 根据需要查阅其他专门文档

版本历史

v2.1.1 (当前版本)

  • 增强了S7PlcNode的事件处理机制
  • 改进了批量读写性能
  • 完善了文档体系
  • 增加了更多使用示例

主要特性演进

  • v1.x - 基础PLC连接和数据读写功能
  • v2.0 - 引入S7PlcNode节点模式,支持事件驱动
  • v2.1 - 增加S7PlcNodeStruct批量读取,完善缓存机制

许可证

Copyright © WinM 2023-2025

此项目基于MIT许可证发布。

贡献

欢迎提交问题和贡献代码。请确保遵循项目的代码风格和贡献指南。

贡献指南

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

联系方式

沈阳维美智能工业科技有限公司

常见问题 (FAQ)

Q: 支持哪些西门子PLC型号?

A: 支持所有兼容S7通信协议的PLC,包括S7-1200、S7-1500、S7-300、S7-400等系列。

Q: 如何处理网络连接不稳定的情况?

A: 使用自动重连功能:

plc.UseAutoReconnection(enable: true, reconnectionDelay: TimeSpan.FromSeconds(2));

Q: 可以同时连接多个PLC吗?

A: 可以,每个PLC使用独立的S7Plc实例:

var plc1 = new S7Plc(CpuType.S71500, "192.168.0.1");
var plc2 = new S7Plc(CpuType.S71200, "192.168.0.2");

Q: 如何优化大量数据点的读取性能?

A: 使用S7PlcNodeStruct进行批量读取,将连续的同类型数据点组织成结构体。

Q: 支持哪些数据类型?

A: 支持bool、int、uint、dint、udint、real、double、string、datetime等常用数据类型。

Showing the top 20 packages that depend on WinM.S7.

Packages Downloads
WinM.S7.Wpf
用于WPF程序的S7控件展示
29
WinM.S7.Wpf
用于WPF程序的S7控件展示
14
WinM.S7.Wpf
用于WPF程序的S7控件展示
13
WinM.S7.Wpf
用于WPF程序的S7控件展示
12
WinM.S7.Wpf
用于WPF程序的S7控件展示
6
WinM.WpfModule.Module.Imlight
Imlight涂胶检测采集模块
6
WinM.WpfModule.Shell
MainApp通用壳
6
WinM.S7.Wpf
用于SiemensPLC通信
5
WinM.WpfModule.Module.Imlight
Imlight涂胶检测采集模块
5
WinM.WpfModule.Module.Biz
WPF模块开发 - 业务核心
5
WinM.WpfModule.Module.Bos6000
用于BOS6000模块的WPF控件,支持便捷开发与部署。
5
WinM.WpfModule.Shell
MainApp通用壳
5
WinM.WpfModule.Shell
MainApp通用壳
4
WinM.WpfModule.Module.Bos6000
用于BOS6000模块的WPF控件,支持便捷开发与部署。
4

.NET 8.0

Version Downloads Last updated
3.2.0 2 03/18/2026
3.1.0 14 12/06/2025
3.0.2 30 11/26/2025
3.0.1 14 11/20/2025
3.0.0 18 11/13/2025