SCADA/MES 面试题
SCADA/MES 面试题
简介
SCADA(数据采集与监视控制系统)和 MES(制造执行系统)是工业信息化的核心组成部分。本篇涵盖 SCADA 架构设计、MES 功能模块、工业通信协议、数据采集与处理等面试话题,帮助工业软件开发者系统性地准备技术面试。
特点
面试题目
1. 什么是 SCADA 系统?它的核心架构是什么?
答: SCADA(Supervisory Control And Data Acquisition)是用于监视和控制工业过程的系统,广泛应用于电力、水务、石油天然气等行业。
SCADA 系统的核心架构层次:
| 层级 | 组件 | 功能 |
|---|---|---|
| 现场层 | 传感器、执行器 | 数据采集和设备控制 |
| 控制层 | PLC、RTU、IED | 本地控制和数据处理 |
| 网络层 | 工业以太网、串口通信 | 数据传输 |
| SCADA 层 | SCADA 服务器、HMI | 集中监视和控制 |
| 管理层 | 历史数据库、报表系统 | 数据存储和分析 |
// SCADA 系统中的数据点模型
public class TagPoint
{
public string TagName { get; set; } // 点位名称(如 "TI_001")
public string Description { get; set; } // 描述
public TagDataType DataType { get; set; } // 数据类型
public double? Value { get; set; } // 当前值
public DateTime Timestamp { get; set; } // 时间戳
public QualityFlag Quality { get; set; } // 数据质量
public AlarmState AlarmState { get; set; } // 报警状态
public double? HighLimit { get; set; } // 高限
public double? LowLimit { get; set; } // 低限
public string Unit { get; set; } // 工程单位
public int ScanRate { get; set; } // 扫描周期(ms)
}
public enum TagDataType { Boolean, Integer, Float, Double, String }
public enum QualityFlag { Good, Bad, Uncertain, Unknown }
public enum AlarmState { Normal, High, Low, HighHigh, LowLow }
// 数据采集服务
public class DataAcquisitionService
{
private readonly Dictionary<string, TagPoint> _tags = new();
private readonly List<IAlarmHandler> _alarmHandlers = new();
private CancellationTokenSource? _cts;
public void AddTag(TagPoint tag) => _tags[tag.TagName] = tag;
public void StartAcquisition()
{
_cts = new CancellationTokenSource();
foreach (var group in _tags.Values.GroupBy(t => t.ScanRate))
{
_ = ScanGroupAsync(group.ToList(), group.Key, _cts.Token);
}
}
private async Task ScanGroupAsync(List<TagPoint> tags, int scanRate, CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(scanRate));
while (await timer.WaitForNextTickAsync(ct))
{
foreach (var tag in tags)
{
var oldValue = tag.Value;
// 从设备读取数据(模拟)
tag.Value = SimulateRead(tag);
tag.Timestamp = DateTime.UtcNow;
tag.Quality = QualityFlag.Good;
// 检查报警
CheckAlarm(tag, oldValue);
}
}
}
private double SimulateRead(TagPoint tag)
{
var random = new Random();
return tag.Value.HasValue
? tag.Value.Value + (random.NextDouble() - 0.5) * 2
: random.NextDouble() * 100;
}
private void CheckAlarm(TagPoint tag, double? oldValue)
{
if (!tag.Value.HasValue) return;
var val = tag.Value.Value;
var oldState = tag.AlarmState;
if (tag.HighLimit.HasValue && val > tag.HighLimit.Value)
tag.AlarmState = AlarmState.High;
else if (tag.LowLimit.HasValue && val < tag.LowLimit.Value)
tag.AlarmState = AlarmState.Low;
else
tag.AlarmState = AlarmState.Normal;
// 报警状态变化时触发通知
if (tag.AlarmState != oldState)
{
foreach (var handler in _alarmHandlers)
handler.OnAlarmChanged(tag, oldState);
}
}
}2. MES 系统的核心功能模块有哪些?
答: MES(Manufacturing Execution System)是连接 ERP 和底层控制系统的桥梁,核心功能遵循 ISA-95 标准。
// MES 核心功能模块
// 1. 生产调度管理
public class ProductionScheduler
{
public async Task<ScheduleResult> CreateScheduleAsync(ProductionOrder order)
{
// 分配产线、人员、物料
var line = await AllocateLineAsync(order.ProductId);
var workers = await AllocateWorkersAsync(line.Id, order.PlannedQuantity);
var materials = await AllocateMaterialsAsync(order.BOM);
return new ScheduleResult(
OrderId: order.Id,
LineId: line.Id,
StartTime: DateTime.UtcNow,
Workers: workers,
Materials: materials);
}
}
// 2. 生产过程监控
public class ProcessMonitor
{
private readonly Dictionary<string, ProcessParameter> _parameters = new();
public void RecordParameter(string stationId, string paramName, double value)
{
var param = new ProcessParameter
{
StationId = stationId,
ParameterName = paramName,
Value = value,
Timestamp = DateTime.UtcNow,
};
_parameters[$"{stationId}_{paramName}"] = param;
// SPC 统计过程控制检查
CheckSPC(param);
}
private void CheckSPC(ProcessParameter param)
{
// 检查是否超出控制限(UCL/LCL)
var history = GetRecentValues(param.StationId, param.ParameterName, 25);
var mean = history.Average();
var stdDev = CalculateStdDev(history);
var ucl = mean + 3 * stdDev; // 上控制限
var lcl = mean - 3 * stdDev; // 下控制限
if (param.Value > ucl || param.Value < lcl)
{
RaiseSPCAlert(param, ucl, lcl);
}
}
}
// 3. 质量管理
public class QualityManager
{
public async Task<InspectionResult> InspectAsync(InspectionRequest request)
{
var results = new List<InspectionItem>();
foreach (var item in request.Items)
{
var result = new InspectionItem
{
ParameterId = item.ParameterId,
MeasuredValue = item.Value,
Result = item.Value >= item.LowerLimit && item.Value <= item.UpperLimit
? "PASS" : "FAIL",
InspectedAt = DateTime.UtcNow,
Inspector = request.Inspector,
};
results.Add(result);
}
return new InspectionResult(
BatchId: request.BatchId,
TotalItems: results.Count,
PassCount: results.Count(r => r.Result == "PASS"),
FailCount: results.Count(r => r.Result == "FAIL"),
OverallResult: results.All(r => r.Result == "PASS") ? "PASS" : "FAIL",
Items: results);
}
}
// 4. 物料追溯
public class TraceabilityService
{
// 正向追溯:从原料到成品
public async Task<TraceResult> ForwardTraceAsync(string materialBatchId)
{
var material = await GetMaterialAsync(materialBatchId);
var productions = await GetProductionsByMaterialAsync(materialBatchId);
var products = await GetProductsByProductionsAsync(productions);
return new TraceResult(
Direction: "Forward",
Source: material,
Productions: productions,
EndProducts: products);
}
// 反向追溯:从成品到原料
public async Task<TraceResult> BackwardTraceAsync(string productSerialNo)
{
var product = await GetProductAsync(productSerialNo);
var productions = await GetProductionsByProductAsync(productSerialNo);
var materials = await GetMaterialsByProductionsAsync(productions);
return new TraceResult(
Direction: "Backward",
Source: product,
Productions: productions,
RawMaterials: materials);
}
}
public record ScheduleResult(int OrderId, int LineId, DateTime StartTime, List<string> Workers, List<string> Materials);
public record ProcessParameter(string StationId, string ParameterName, double Value, DateTime Timestamp);
public record InspectionResult(string BatchId, int TotalItems, int PassCount, int FailCount, string OverallResult, List<InspectionItem> Items);
public record TraceResult(string Direction, object Source, List<object> Productions, List<object>? EndProducts = null, List<object>? RawMaterials = null);3. 工业通信协议有哪些?如何选择?
答: 常见的工业通信协议及其特点:
| 协议 | 层级 | 特点 | 适用场景 |
|---|---|---|---|
| Modbus RTU/TCP | 现场层/控制层 | 简单、通用、开放 | PLC 通信、仪表读取 |
| OPC UA | 控制层/SCADA层 | 安全、跨平台、信息模型 | 标准化数据接入 |
| MQTT | 网络层 | 轻量、发布/订阅 | IoT 数据传输 |
| Profibus/Profinet | 现场层 | 高速、确定性强 | 实时控制 |
| Ethernet/IP | 控制层 | 基于以太网 | 罗克韦尔生态 |
// Modbus TCP 通信示例(使用 NModbus 库)
// dotnet add package NModbus
public class ModbusService
{
private readonly IModbusMaster _master;
private readonly string _slaveAddress;
private readonly byte _slaveId;
public ModbusService(string ipAddress, int port, byte slaveId)
{
var client = new TcpClient(ipAddress, port);
var factory = new ModbusFactory();
_master = factory.CreateMaster(client);
_slaveId = slaveId;
}
// 读取保持寄存器
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
{
return await _master.ReadHoldingRegistersAsync(_slaveId, startAddress, count);
}
// 读取输入寄存器
public async Task<ushort[]> ReadInputRegistersAsync(ushort startAddress, ushort count)
{
return await _master.ReadInputRegistersAsync(_slaveId, startAddress, count);
}
// 写入单个寄存器
public async Task WriteSingleRegisterAsync(ushort address, ushort value)
{
await _master.WriteSingleRegisterAsync(_slaveId, address, value);
}
// 读取线圈状态
public async Task<bool[]> ReadCoilsAsync(ushort startAddress, ushort count)
{
return await _master.ReadCoilsAsync(_slaveId, startAddress, count);
}
// 将寄存器值转换为工程值
public static double ConvertToEngineeringValue(ushort[] registers, double factor, double offset)
{
// 假设是 32 位浮点数(两个 16 位寄存器)
if (registers.Length >= 2)
{
var combined = (registers[0] << 16) | registers[1];
var floatValue = BitConverter.Int64BitsToDouble(combined);
return floatValue * factor + offset;
}
return registers[0] * factor + offset;
}
}
// OPC UA 通信示例(使用 OPC Foundation 库)
// dotnet add package OPCFoundation.NetStandard.Opc.Ua
public class OpcUaService : IDisposable
{
private Session? _session;
public async Task ConnectAsync(string endpointUrl)
{
var endpoint = CoreClientUtils.SelectEndpoint(endpointUrl, useSecurity: true);
var config = new ApplicationConfiguration
{
ApplicationName = "MES Client",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier(),
AutoAcceptUntrustedCertificates = true
}
};
_session = await Session.Create(
config,
new ConfiguredEndpoint(null, endpoint, EndpointConfiguration.Create(config)),
updateBeforeConnect: true,
sessionName: "MES Session",
sessionTimeout: 60000,
identity: null,
preferredLocales: null);
}
// 读取节点值
public async Task<DataValue> ReadNodeAsync(string nodeId)
{
var node = new NodeId(nodeId);
var values = await _session.ReadValuesAsync(new[] { node });
return values[0];
}
// 订阅数据变化
public void Subscribe(string nodeId, double interval, Action<DataValue> onChanged)
{
var subscription = new Subscription(_session.DefaultSubscription)
{
PublishingInterval = (int)interval,
};
var item = new MonitoredItem
{
StartNodeId = new NodeId(nodeId),
SamplingInterval = (int)interval,
AttributeId = Attributes.Value,
};
item.Notification += (monitoredItem, args) =>
{
var notification = (MonitoredItemNotification)args.NotificationValue;
onChanged(notification.Value);
};
subscription.AddItem(item);
_session.AddSubscription(subscription);
}
public void Dispose() => _session?.Close();
}4. 如何处理工业大数据的实时采集和存储?
答: 工业数据具有高频率、大体量、时间序列的特点,需要特殊的存储和处理策略。
// 时序数据存储服务
public class TimeSeriesService
{
private readonly Channel<TagData> _channel;
private readonly ILogger<TimeSeriesService> _logger;
public TimeSeriesService(ILogger<TimeSeriesService> logger)
{
_channel = Channel.CreateBounded<TagData>(new BoundedChannelOptions(100000)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
});
_logger = logger;
}
// 高性能写入入口
public bool Write(TagData data) => _channel.Writer.TryWrite(data);
// 批量写入后台任务
public async Task ProcessAsync(CancellationToken ct)
{
var batch = new List<TagData>(1000);
var period = TimeSpan.FromSeconds(1);
await foreach (var data in _channel.Reader.ReadAllAsync(ct))
{
batch.Add(data);
if (batch.Count >= 1000 || batch[0].Timestamp - batch[batch.Count - 1].Timestamp > period)
{
await FlushBatchAsync(batch);
batch.Clear();
}
}
}
private async Task FlushBatchAsync(List<TagData> batch)
{
_logger.LogInformation("批量写入 {Count} 条时序数据", batch.Count);
// 写入时序数据库(如 InfluxDB、TimescaleDB、TDengine)
await Task.CompletedTask;
}
// 数据降采样策略
public List<AggregatedData> Downsample(List<TagData> data, TimeSpan interval)
{
return data
.GroupBy(d => new DateTime(d.Timestamp.Ticks / interval.Ticks * interval.Ticks))
.Select(g => new AggregatedData
{
Timestamp = g.Key,
Avg = g.Average(d => d.Value),
Min = g.Min(d => d.Value),
Max = g.Max(d => d.Value),
Count = g.Count(),
})
.ToList();
}
}
public record TagData(string TagName, double Value, DateTime Timestamp, string Quality);
public record AggregatedData { public DateTime Timestamp; public double Avg; public double Min; public double Max; public int Count; }5-15. 更多 SCADA/MES 面试题简答
5. 什么是 OPC UA?与传统 OPC 有什么区别? OPC UA(Unified Architecture)是独立于平台的工业通信标准,相比传统 OPC(基于 COM/DCOM),OPC UA 支持跨平台、内建安全机制、提供信息模型和发现服务。
6. 如何保证 SCADA 系统的可靠性? 采用双机热备、冗余网络、看门狗机制、心跳检测等策略。关键数据本地缓存,通信中断后自动重连和数据补传。
7. 什么是数字孪生?在 SCADA 中如何应用? 数字孪生是物理实体的虚拟映射,通过实时数据驱动模型。在 SCADA 中用于设备状态模拟、故障预测、工艺优化等。
8. PLC 和 RTU 的区别是什么? PLC(可编程逻辑控制器)适合逻辑控制和顺序控制;RTU(远程终端单元)适合远程数据采集和简单控制,通常在恶劣环境下使用。
9. 如何实现工业数据的报警管理? 建立报警分级(紧急、高、中、低)、报警抑制(关联报警合并)、报警死区(避免抖动)、报警确认机制和报警统计分析。
10. MES 如何与 ERP 系统集成? 通过标准接口(如 ISA-95 B2MML)或自定义 API 进行数据交换。ERP 下发生产订单和物料信息,MES 上报生产进度、质量报告和库存变化。
11. 工业 4.0 对 SCADA/MES 有什么影响? 推动边缘计算、AI 质检、预测性维护、柔性制造等。SCADA 向云端扩展,MES 更加智能化和自适应。
12. 如何实现设备的 OEE 计算? OEE = 可用率 x 性能率 x 质量率。可用率 = 实际运行时间 / 计划运行时间;性能率 = 实际产量 / 理论产量;质量率 = 合格品数 / 总产量。
13. 边缘计算在工业场景中的作用? 边缘计算在数据源头进行实时处理,减少云端传输延迟和带宽压力。适用于实时质检、设备预测性维护、工艺参数优化等场景。
14. 如何实现工业网络安全? 纵深防御策略:物理隔离、防火墙、DMZ 区域划分、VPN 远程访问、网络准入控制、工业 IDS/IPS。
15. MQTT 在工业 IoT 中如何应用? MQTT 作为轻量级发布/订阅协议,适合带宽有限、网络不稳定的环境。Sparkplug 规范定义了工业数据的 MQTT 主题命名和数据格式标准。
优点
缺点
总结
SCADA/MES 面试需要展示对工业自动化体系的全面理解,包括控制层设备(PLC/RTU)、通信协议(Modbus/OPC UA)、数据处理(时序数据库/实时分析)和应用层功能(MES 模块/报警管理)。建议结合实际项目经验,阐述在数据采集效率、系统可靠性、协议适配等方面的技术方案和优化实践。
这组题真正考什么
- 上位机题常常在考你是否真正接触过现场,而不是只会界面开发。
- 面试官很关注设备异常、线程模型、实时性和故障恢复。
- 能把协议、界面刷新和设备状态管理串起来,答案会很有说服力。
60 秒答题模板
- 先交代架构和线程模型。
- 再说协议、数据流或设备交互过程。
- 最后补异常处理、重连和现场排障。
容易失分的点
- 只讲 happy path,不讲异常和恢复。
- 忽略 UI 线程和资源释放。
- 无法说明协议时序和超时策略。
刷题建议
- 把客户端架构、通信协议、设备交互和现场排障分块复习。
- 回答上位机题时尽量结合现场问题,比如掉线重连、实时刷新和异常恢复。
- 对协议类题目要能说清数据流、时序和超时策略。
高频追问
- 如果现场设备不稳定或网络抖动,你的补救策略是什么?
- 这个方案如何保证 UI 流畅、数据一致和异常可追踪?
- 如果协议变更或设备数量增加,架构上最先要改什么?
复习重点
- 把每道题的关键词整理成自己的知识树,而不是只背原句。
- 对容易混淆的概念要做横向比较,例如机制差异、适用边界和性能代价。
- 复习时优先补“为什么”,其次才是“怎么用”和“记住什么术语”。
面试作答提醒
- 先说明架构和线程模型,再讲通信细节。
- 遇到设备题不要只讲 Happy Path,要主动补异常与恢复。
- 如果回答涉及 UI,记得说明线程切换和资源释放。
