文档信息
| 项目 | 内容 |
|---|---|
| 文档版本 | V1.0 |
| 编写日期 | 2025-12-05 |
| 适用范围 | MES 系统开发、西门子 PLC 通信软件开发 |
| 开发语言 | MES:Java(Spring Boot);PLC 通信软件:C#(.NET Framework) |
| 通信协议 | HTTP(RESTful API) |
| PLC 通信方式 | S7.NET(西门子 S7 系列 PLC 专用通信库) |
一、总体架构设计
1.1 系统整体流程
- MES 系统根据业务需求,向 PLC 通信软件发送包含 "PLC 地址、数据点位、采集指令" 的 HTTP 请求;
- PLC 通信软件接收请求后,解析指令并通过S7.NET库连接西门子 PLC,抓取指定点位的数据;
- PLC 通信软件将采集到的数据封装为 JSON 格式的 HTTP 响应,返回至 MES 系统;
- MES 系统接收响应后,解析数据并进行存储、展示、业务逻辑处理。
1.2 系统架构图
plaintext
┌─────────────┐ HTTP请求 ┌─────────────────────┐ S7协议 ┌─────────────┐
│ MES系统 │ ────采集指令/参数─────→ │ PLC通信软件(上位机)│ ────数据采集请求────→ │ 西门子PLC │
│ (Spring Boot)│ ←────采集结果/数据────── │ (.NET Framework) │ ←────PLC实时数据──── │ (S7-1200/1500)│
└─────────────┘ HTTP响应 └─────────────────────┘ └─────────────┘
二、MES 系统技术开发文档
2.1 功能需求
| 功能模块 | 详细需求 |
|---|---|
| 指令发送模块 | 1. 支持配置 PLC 通信软件的 IP / 端口;2. 支持自定义采集指令(PLC 型号、IP、数据点位、采集频率);3. 发送 HTTP POST 请求至 PLC 通信软件;4. 记录请求日志(请求时间、指令内容、状态)。 |
| 响应接收模块 | 1. 接收 PLC 通信软件的 JSON 格式响应;2. 解析响应数据(点位名称、数值、采集时间、PLC 状态);3. 校验数据完整性,异常数据标记并告警;4. 存储解析后的数据至 MES 数据库。 |
| 数据管理模块 | 1. 支持 PLC 采集数据的查询(按时间、PLC 点位、产线);2. 数据可视化展示(实时监控面板、历史趋势图);3. 异常数据告警(阈值配置、短信 / 邮件通知)。 |
| 系统配置模块 | 1. PLC 通信软件地址配置;2. PLC 点位映射配置(业务字段与 PLC 点位对应);3. 采集频率、超时时间配置;4. 权限管理(不同角色操作权限)。 |
2.2 技术选型
| 模块 | 技术栈 | 说明 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 快速开发 RESTful API,集成 Spring MVC、Spring Data JPA |
| 通信方式 | HTTP/HTTPS | 与 PLC 通信软件的标准化通信,支持 JSON 数据传输 |
| 数据库 | MySQL 8.0 | 存储 MES 业务数据、PLC 采集数据、系统配置、日志 |
| 缓存 | Redis 6.x | 缓存高频访问的 PLC 点位配置、实时数据 |
| 前端 | Vue 3 + Element Plus | 数据展示、指令配置、系统监控界面 |
| 日志 | Logback + ELK | 系统日志采集、分析、检索 |
| 部署 | Docker + Kubernetes | 容器化部署,支持集群扩展 |
2.3 核心接口设计
2.3.1 采集指令发送接口
-
接口地址:
/api/plc/collect -
请求方式:POST
-
请求头:
Content-Type: application/json -
请求参数:
参数名 类型 必填 说明 plcId String 是 PLC 唯一标识(自定义) plcIp String 是 PLC 的 IP 地址 plcModel String 是 PLC 型号(如 S7-1200、S7-1500) dataPoints Array 是 待采集的点位列表 dataPoints[].pointName String 是 点位名称(如 DB1.DBW0) dataPoints[].dataType String 是 数据类型(int、float、bool、string) timeout Integer 否 采集超时时间(默认 5000ms) -
请求示例:
json
{
"plcId": "PLC_001",
"plcIp": "192.168.1.100",
"plcModel": "S7-1500",
"dataPoints": [
{
"pointName": "DB1.DBW0",
"dataType": "int"
},
{
"pointName": "DB1.DBD2",
"dataType": "float"
},
{
"pointName": "DB1.DBX5.0",
"dataType": "bool"
}
],
"timeout": 5000
}
-
响应参数:
参数名 类型 说明 code Integer 响应码(200 = 成功,500 = 失败) msg String 响应信息 requestId String 请求唯一标识 data Object 采集结果(异步采集时为 null,同步返回时为数据) -
响应示例(同步):
json
{
"code": 200,
"msg": "采集成功",
"requestId": "REQ_20251205100001",
"data": {
"plcId": "PLC_001",
"collectTime": "2025-12-05 10:00:05",
"dataPoints": [
{
"pointName": "DB1.DBW0",
"dataType": "int",
"value": 100,
"status": "success"
},
{
"pointName": "DB1.DBD2",
"dataType": "float",
"value": 25.5,
"status": "success"
},
{
"pointName": "DB1.DBX5.0",
"dataType": "bool",
"value": true,
"status": "success"
}
],
"plcStatus": "online"
}
}
2.3.2 采集结果查询接口(异步场景)
- 接口地址:
/api/plc/collect/result/{requestId} - 请求方式:GET
- 响应参数:同上述同步响应示例
2.4 数据库设计(核心表)
2.4.1 PLC 配置表(plc_config)
| 字段名 | 类型 | 主键 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 自增 ID |
| plc_id | VARCHAR(50) | 否 | PLC 唯一标识 |
| plc_ip | VARCHAR(20) | 否 | PLC IP 地址 |
| plc_model | VARCHAR(20) | 否 | PLC 型号 |
| communication_software_ip | VARCHAR(20) | 否 | PLC 通信软件 IP |
| communication_software_port | INT | 否 | PLC 通信软件端口 |
| create_time | DATETIME | 否 | 创建时间 |
| update_time | DATETIME | 否 | 更新时间 |
2.4.2 PLC 采集数据表(plc_collect_data)
| 字段名 | 类型 | 主键 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 自增 ID |
| request_id | VARCHAR(50) | 否 | 请求唯一标识 |
| plc_id | VARCHAR(50) | 否 | PLC 唯一标识 |
| point_name | VARCHAR(50) | 否 | 点位名称 |
| data_type | VARCHAR(20) | 否 | 数据类型 |
| value | VARCHAR(100) | 否 | 采集值(统一存储为字符串) |
| collect_time | DATETIME | 否 | 采集时间 |
| status | VARCHAR(20) | 否 | 采集状态(success/fail) |
| error_msg | VARCHAR(200) | 否 | 错误信息(失败时填充) |
2.4.3 请求日志表(plc_request_log)
| 字段名 | 类型 | 主键 | 说明 |
|---|---|---|---|
| id | BIGINT | 是 | 自增 ID |
| request_id | VARCHAR(50) | 否 | 请求唯一标识 |
| request_content | TEXT | 否 | 请求内容(JSON) |
| response_content | TEXT | 否 | 响应内容(JSON) |
| request_time | DATETIME | 否 | 请求时间 |
| response_time | DATETIME | 否 | 响应时间 |
| status | VARCHAR(20) | 否 | 请求状态(success/fail) |
2.5 核心代码示例(Java)
2.5.1 采集指令发送服务
java
运行
@Service
@Slf4j
public class PlcCollectService {
@Autowired
private RestTemplate restTemplate;
@Value("${plc.communication.software.url}")
private String plcSoftwareUrl;
@Autowired
private PlcRequestLogMapper requestLogMapper;
/**
* 发送采集指令至PLC通信软件
* @param collectRequest 采集请求参数
* @return 采集响应结果
*/
public PlcCollectResponse sendCollectRequest(PlcCollectRequest collectRequest) {
// 生成请求ID
String requestId = "REQ_" + System.currentTimeMillis();
collectRequest.setRequestId(requestId);
PlcCollectResponse response = null;
try {
// 记录请求日志(请求中)
PlcRequestLog log = new PlcRequestLog();
log.setRequestId(requestId);
log.setRequestContent(JSON.toJSONString(collectRequest));
log.setRequestTime(new Date());
log.setStatus("processing");
requestLogMapper.insert(log);
// 发送HTTP请求
ResponseEntity<PlcCollectResponse> responseEntity = restTemplate.postForEntity(
plcSoftwareUrl + "/api/plc/collect",
collectRequest,
PlcCollectResponse.class
);
// 处理响应
if (responseEntity.getStatusCode().is2xxSuccessful()) {
response = responseEntity.getBody();
log.setResponseContent(JSON.toJSONString(response));
log.setResponseTime(new Date());
log.setStatus("success");
} else {
log.setStatus("fail");
log.setResponseTime(new Date());
log.setErrorMsg("PLC通信软件返回非200状态码:" + responseEntity.getStatusCode());
throw new BusinessException("采集请求失败,PLC通信软件返回异常状态码");
}
} catch (Exception e) {
log.error("发送采集指令失败,requestId={}", requestId, e);
response = new PlcCollectResponse();
response.setCode(500);
response.setMsg("采集请求失败:" + e.getMessage());
response.setRequestId(requestId);
// 更新日志
PlcRequestLog log = requestLogMapper.selectByRequestId(requestId);
if (log != null) {
log.setStatus("fail");
log.setResponseTime(new Date());
log.setErrorMsg(e.getMessage());
requestLogMapper.updateById(log);
}
}
return response;
}
}
2.5.2 控制器层
java
运行
@RestController
@RequestMapping("/api/plc")
@RequiredArgsConstructor
public class PlcCollectController {
private final PlcCollectService plcCollectService;
@PostMapping("/collect")
public Result<PlcCollectResponse> collect(@RequestBody @Valid PlcCollectRequest request) {
PlcCollectResponse response = plcCollectService.sendCollectRequest(request);
return Result.success(response);
}
@GetMapping("/collect/result/{requestId}")
public Result<PlcCollectResponse> getCollectResult(@PathVariable String requestId) {
PlcCollectResponse response = plcCollectService.getCollectResult(requestId);
return Result.success(response);
}
}
2.6 部署与运维
- 环境要求:JDK 11+、MySQL 8.0+、Redis 6.x+;
- 配置文件:application.yml 中配置 PLC 通信软件地址、数据库连接、Redis 连接;
- 容器化:编写 Dockerfile,打包为镜像,通过 K8s 部署;
- 监控:集成 Prometheus + Grafana,监控接口调用量、响应时间、异常率;
- 备份:MySQL 数据每日定时备份,日志保留 30 天。
三、上位机西门子 PLC 通信软件技术开发文档
3.1 功能需求
| 功能模块 | 详细需求 |
|---|---|
| HTTP 接口接收模块 | 1. 监听指定端口的 HTTP POST 请求;2. 解析 MES 发送的采集指令(PLC IP、点位、数据类型);3. 校验请求参数合法性,非法请求返回错误响应;4. 记录请求日志(请求时间、指令内容、MES IP)。 |
| PLC 数据采集模块 | 1. 通过S7.NET库连接西门子 PLC;2. 根据指令抓取指定点位的数据,支持 int/float/bool/string 类型;3. 处理 PLC 连接异常(超时、离线),返回错误信息;4. 采集完成后断开 PLC 连接,释放资源。 |
| 响应封装模块 | 1. 将采集到的数据封装为 JSON 格式的 HTTP 响应;2. 响应包含采集值、采集时间、PLC 状态、点位状态;3. 异常场景返回错误码和错误描述。 |
| 日志管理模块 | 1. 记录 PLC 连接日志、数据采集日志、HTTP 请求日志;2. 日志支持按时间、PLC ID、请求 ID 检索;3. 日志文件自动分割(按天),保留 30 天。 |
| 系统配置模块 | 1. 配置 HTTP 监听端口、PLC 连接超时时间;2. 配置日志存储路径、日志级别;3. 支持开机自启动。 |
3.2 技术选型
| 模块 | 技术栈 | 说明 |
|---|---|---|
| 开发框架 | .NET Framework 4.8 | 兼容 Windows 系统,成熟稳定,支持S7.NET库 |
| HTTP 服务 | ASP.NET Web API | 轻量级 HTTP 接口开发框架,支持 RESTful API |
| PLC 通信 | S7.NET(NuGet 包) | 西门子 S7 系列 PLC 专用通信库,支持 S7-1200/1500/300/400 |
| 日志 | log4net | .NET 平台主流日志框架,支持文件、控制台输出 |
| 配置管理 | App.config | 系统配置存储,支持动态加载 |
| 部署 | Windows 服务 | 以 Windows 服务形式运行,后台常驻,开机自启 |
| 调试工具 | Visual Studio 2022 | 开发、调试、打包一体化 |
3.3 核心接口设计
3.3.1 采集指令接收接口(对外)
- 接口地址:
/api/plc/collect - 请求方式:POST
- 请求参数:同 MES 系统的采集指令发送接口请求参数;
- 响应参数:同 MES 系统的采集指令发送接口响应参数。
3.4 核心代码示例(C#)
3.4.1 PLC 数据采集服务
csharp
运行
using System;
using System.Collections.Generic;
using System.Net;
using S7.Net;
namespace PlcCommunicationSoftware.Service
{
public class PlcDataCollectService
{
private static readonly ILog log = LogManager.GetLogger(typeof(PlcDataCollectService));
/// <summary>
/// 采集PLC数据
/// </summary>
/// <param name="plcIp">PLC IP地址</param>
/// <param name="plcModel">PLC型号</param>
/// <param name="dataPoints">采集点位列表</param>
/// <param name="timeout">超时时间(ms)</param>
/// <returns>采集结果</returns>
public PlcCollectResult CollectPlcData(string plcIp, string plcModel, List<DataPoint> dataPoints, int timeout)
{
PlcCollectResult result = new PlcCollectResult();
result.PlcId = Guid.NewGuid().ToString("N");
result.CollectTime = DateTime.Now;
result.DataPoints = new List<DataPointResult>();
Plc plc = null;
try
{
// 初始化PLC连接
plc = CreatePlcInstance(plcModel, plcIp);
if (plc == null)
{
result.PlcStatus = "offline";
throw new Exception("不支持的PLC型号:" + plcModel);
}
// 设置超时时间
plc.ConnectTimeout = timeout;
// 连接PLC
var connectResult = plc.Open();
if (!connectResult)
{
result.PlcStatus = "offline";
throw new Exception("PLC连接失败,IP:" + plcIp);
}
result.PlcStatus = "online";
// 遍历采集点位
foreach (var point in dataPoints)
{
DataPointResult pointResult = new DataPointResult();
pointResult.PointName = point.PointName;
pointResult.DataType = point.DataType;
try
{
// 读取点位数据
object value = ReadPlcData(plc, point.PointName, point.DataType);
pointResult.Value = value;
pointResult.Status = "success";
log.Info($"采集点位{point.PointName}成功,值:{value}");
}
catch (Exception ex)
{
pointResult.Status = "fail";
pointResult.ErrorMsg = ex.Message;
log.Error($"采集点位{point.PointName}失败", ex);
}
result.DataPoints.Add(pointResult);
}
}
catch (Exception ex)
{
result.Code = 500;
result.Msg = ex.Message;
log.Error("PLC数据采集失败", ex);
}
finally
{
// 关闭PLC连接
plc?.Close();
plc?.Dispose();
}
return result;
}
/// <summary>
/// 创建PLC实例
/// </summary>
private Plc CreatePlcInstance(string plcModel, string plcIp)
{
PlcType plcType;
switch (plcModel.ToUpper())
{
case "S7-1200":
plcType = PlcType.S71200;
break;
case "S7-1500":
plcType = PlcType.S71500;
break;
case "S7-300":
plcType = PlcType.S7300;
break;
case "S7-400":
plcType = PlcType.S7400;
break;
default:
return null;
}
return new Plc(plcType, plcIp, 0, 1);
}
/// <summary>
/// 读取PLC点位数据
/// </summary>
private object ReadPlcData(Plc plc, string pointName, string dataType)
{
switch (dataType.ToLower())
{
case "int":
return plc.ReadInt16(pointName);
case "float":
return plc.ReadFloat(pointName);
case "bool":
return plc.ReadBit(pointName);
case "string":
return plc.ReadString(pointName, 256);
default:
throw new Exception("不支持的数据类型:" + dataType);
}
}
}
}
3.4.2 HTTP 接口控制器
csharp
运行
using System.Web.Http;
namespace PlcCommunicationSoftware.Controllers
{
[RoutePrefix("api/plc")]
public class PlcController : ApiController
{
private readonly PlcDataCollectService _collectService = new PlcDataCollectService();
private static readonly ILog log = LogManager.GetLogger(typeof(PlcController));
[HttpPost]
[Route("collect")]
public IHttpActionResult Collect([FromBody] PlcCollectRequest request)
{
// 参数校验
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
log.Info($"接收采集请求,requestId={request.RequestId},plcIp={request.PlcIp}");
// 采集PLC数据
var result = _collectService.CollectPlcData(
request.PlcIp,
request.PlcModel,
request.DataPoints,
request.Timeout ?? 5000
);
// 封装响应
var response = new PlcCollectResponse
{
Code = result.Code ?? 200,
Msg = result.Msg ?? "采集成功",
RequestId = request.RequestId,
Data = result
};
return Ok(response);
}
}
}
3.5 部署与运维
- 环境要求:Windows Server 2016+、.NET Framework 4.8 运行时;
- 部署方式:
- 将编译后的程序打包为 Windows 服务;
- 通过 InstallUtil 工具安装服务,设置为开机自启;
- 配置文件:App.config 中配置 HTTP 监听端口、PLC 连接超时时间、日志路径;
- 日志管理:日志文件默认存储在
C:\PlcCommunicationSoftware\Logs,按天分割; - 监控:通过 Windows 事件查看器监控服务运行状态,异常时触发邮件告警;
- 调试:通过 Visual Studio 远程调试,或查看日志文件定位问题。
四、通信交互规范
4.1 数据格式
- 所有 HTTP 请求 / 响应均采用 JSON 格式;
- 字符编码:UTF-8;
- 时间格式:yyyy-MM-dd HH:mm:ss(东八区)。
4.2 错误码定义
| 错误码 | 说明 | 归属系统 |
|---|---|---|
| 200 | 操作成功 | MES/PLC 通信软件 |
| 400 | 请求参数错误 | PLC 通信软件 |
| 404 | PLC 点位不存在 | PLC 通信软件 |
| 500 | 系统内部错误 | MES/PLC 通信软件 |
| 501 | PLC 连接失败 | PLC 通信软件 |
| 502 | PLC 通信软件无响应 | MES |
| 503 | 数据采集超时 | PLC 通信软件 |
4.3 安全规范
- HTTP 通信建议使用 HTTPS 加密;
- PLC 通信软件仅开放指定 MES 服务器的 IP 访问权限;
- 接口请求添加签名验证(如 MD5 + 时间戳),防止非法请求;
- PLC 点位配置权限严格管控,仅授权人员可修改。
五、测试方案
5.1 MES 系统测试
- 功能测试:指令发送、响应接收、数据存储、查询展示;
- 性能测试:并发发送 100 个采集请求,测试响应时间、成功率;
- 异常测试:PLC 通信软件离线、参数错误、采集超时等场景;
- 兼容性测试:不同浏览器、不同分辨率下前端界面展示。
5.2 PLC 通信软件测试
- 功能测试:HTTP 接口接收、PLC 连接、数据采集、响应封装;
- 兼容性测试:支持 S7-1200/1500/300/400 等型号 PLC;
- 稳定性测试:7*24 小时持续运行,模拟高频采集请求;
- 异常测试:PLC 断电、网络中断、点位不存在等场景。
5.3 集成测试
- 端到端测试:MES 发送指令→PLC 通信软件采集→MES 接收数据→存储展示;
- 数据一致性测试:对比 PLC 实际值与 MES 存储值;
- 压力测试:模拟 100 个 MES 并发请求,测试整体响应效率。
六、附录
6.1 西门子 PLC 点位格式说明
| 数据类型 | 点位格式示例 | 说明 |
|---|---|---|
| 整数(int) | DB1.DBW0 | 数据块 1,字地址 0 |
| 浮点数(float) | DB1.DBD2 | 数据块 1,双字地址 2 |
| 布尔值(bool) | DB1.DBX5.0 | 数据块 1,字节 5,位 0 |
| 字符串(string) | DB1.DBB10 | 数据块 1,字节 10 开始的字符串 |
6.2 依赖库版本
| 库名称 | 版本 | 用途 |
|---|---|---|
| S7.NET | 0.15.0 | PLC 通信 |
| log4net | 2.0.15 | 日志记录 |
| Newtonsoft.Json | 13.0.3 | JSON 序列化 / 反序列化 |
| Spring Boot | 2.7.10 | MES 后端框架 |
| MyBatis-Plus | 3.5.3.1 | 数据库操作 |