GST Modbus Communication Integration Guide
This document describes the Modbus protocol architecture, Controller interfaces, and integration patterns in the GST Framework. Applicable to Modbus communication projects such as ProcessVision, Jope-PLC-Monitor, and GST.Simulator.
1. Architecture Overview
┌────────────────────────────────────────────────────────┐
│ Application Layer │
│ PlcPollingService / DataCollection / UI │
├────────────────────────────────────────────────────────┤
│ Controller Layer (Device Abstraction) │
│ ITemperatureController / IPressureController │
│ TaieFcController (TAIE FC Series Implementation) │
├────────────────────────────────────────────────────────┤
│ Command Queue Layer (Priority Scheduling) │
│ IModbusCommandQueue — Priority scheduling, batch │
│ atomic operations │
├────────────────────────────────────────────────────────┤
│ Transport Layer (Protocol) │
│ IModbusClient → ModbusTcpClient / ModbusRtuClient │
│ ModbusHelper (CRC, PDU construction) │
└────────────────────────────────────────────────────────┘
2. Protocol Differences and Selection
| Modbus RTU | Modbus ASCII | Modbus TCP | |
|---|---|---|---|
| Transport Medium | RS-485/232 | RS-485/232 | Ethernet |
| Framing | Binary + CRC16 | ASCII Hex + LRC | MBAP Header |
| Efficiency | Highest | Lower (2x data volume) | High |
| Error Detection | CRC-16 | LRC | TCP itself |
| Use Case | Single device direct connection | Legacy device compatibility | Multi-device / Remote |
| GST Implementation | ModbusRtuClient | — | ModbusTcpClient |
Selection Criteria:
- Device has Ethernet → Modbus TCP
- Device only has Serial Port → Modbus RTU
- PLC multi-device monitoring → Modbus TCP (each PLC has its own IP)
3. Core Interfaces
3.1 IModbusClient — Transport Layer
public interface IModbusClient
{
string ConnectionId { get; } // "COM3" 或 "192.168.1.100:502"
bool IsConnected { get; }
// 連線管理
Task ConnectAsync(CancellationToken ct);
Task DisconnectAsync(CancellationToken ct);
// 讀取操作
Task<ushort[]> ReadHoldingRegistersAsync(byte unitId, ushort startAddress, ushort quantity, CancellationToken ct);
Task<ushort[]> ReadInputRegistersAsync(byte unitId, ushort startAddress, ushort quantity, CancellationToken ct);
Task<bool[]> ReadCoilsAsync(byte unitId, ushort startAddress, ushort quantity, CancellationToken ct);
Task<bool[]> ReadDiscreteInputsAsync(byte unitId, ushort startAddress, ushort quantity, CancellationToken ct);
// 寫入操作
Task WriteSingleRegisterAsync(byte unitId, ushort address, ushort value, CancellationToken ct);
Task WriteSingleCoilAsync(byte unitId, ushort address, bool value, CancellationToken ct);
Task WriteMultipleRegistersAsync(byte unitId, ushort startAddress, ushort[] values, CancellationToken ct);
Task WriteMultipleCoilsAsync(byte unitId, ushort startAddress, bool[] values, CancellationToken ct);
}
3.2 IModbusController — Controller Layer
public interface IModbusController
{
string Name { get; }
byte UnitId { get; }
bool IsConnected { get; }
string BusId { get; } // 同一 ConnectionId 表示共用匯流排
Task<IReadOnlyDictionary<string, object>> ReadAllAsync(CancellationToken ct);
Task ConnectAsync(CancellationToken ct);
Task DisconnectAsync(CancellationToken ct);
}
3.3 ITemperatureController / IPressureController
// 溫度控制器專用介面
Task<double> GetProcessValueAsync(CancellationToken ct); // 當前 PV
Task<double> GetSetValueAsync(CancellationToken ct); // 目標 SV
Task SetSetValueAsync(double value, CancellationToken ct); // 寫入 SV
Task<double> GetOutputPercentageAsync(CancellationToken ct); // 輸出 0-100%
Task<ControllerStatus> GetStatusAsync(CancellationToken ct); // 狀態位元
// 程式段控制(1-8 段)
Task SetProgramSegmentsAsync(ProgramSegment[] segments, CancellationToken ct);
Task StartProgramAsync(CancellationToken ct);
Task StopProgramAsync(CancellationToken ct);
Task<int> GetCurrentSegmentAsync(CancellationToken ct);
Task<int> GetRemainingTimeAsync(CancellationToken ct);
// PID 參數
Task<PidParameters> GetPidParametersAsync(CancellationToken ct);
Task SetPidParametersAsync(PidParameters pid, CancellationToken ct);
3.4 Data Types
record ProgramSegment(
double SetValue, // 目標值
int TimeSeconds, // 持續時間(秒)
double OutputLimit = 100.0 // 輸出限制 %
);
record PidParameters
{
double ProportionalBand; // P(線路上 x10)
int IntegralTime; // I(秒)
int DerivativeTime; // D(秒)
}
record ControllerStatus
{
bool SensorAbnormal;
bool AutoTuning;
bool Alarm1Active;
bool Alarm2Active;
bool IsRunning;
bool OutputOn;
ushort RawBits;
}
4. CommandQueue Priority Scheduling
4.1 Problem Scenario
A single Modbus bus (Serial or TCP) can only handle one request/response at a time. When multiple operations need to access the same device simultaneously, a scheduling mechanism is required.
4.2 Priority Definitions
enum ModbusCommandPriority
{
Low = 0, // Polling 讀取(可取消)
Normal = 50, // 一般操作(可取消)
High = 100, // 重要寫入(不可取消)
Critical = 200 // 批次操作(阻塞所有其他操作)
}
4.3 Usage Interface
public interface IModbusCommandQueue
{
Task<T> EnqueueReadAsync<T>(Func<CancellationToken, Task<T>> readFunc,
ModbusCommandPriority priority, CancellationToken ct);
Task EnqueueWriteAsync(Func<CancellationToken, Task> writeFunc,
ModbusCommandPriority priority, CancellationToken ct);
Task ExecuteBatchAsync(Func<CancellationToken, Task> batchFunc,
ModbusCommandPriority priority, CancellationToken ct);
Task<int> CancelPendingAsync(ModbusCommandPriority belowPriority);
}
4.4 Operating Mechanism
Background Queue Processor (Single Task)
↓ Dequeue highest priority command
SemaphoreSlim (Execution Lock)
↓ Ensures only one operation accesses the bus at a time
Execute Modbus Operation
↓
Return result to caller
Special Behaviors:
- When High/Critical commands enter the queue, Low/Normal commands are automatically cancelled
- Batch operations (Critical) block all other operations after acquiring the execution lock
- Prevents polling from interrupting user operations
5. TAIE FC Controller
5.1 Register Map
Address Purpose R/W Notes
0x0000 SetValue (SV) R/W Affected by DecimalPoint
0x008A ProcessValue (PV) R/O Affected by DecimalPoint
0x0087 OutputPercentage R/O 0-1000 = 0-100.0%
0x0088 OutputBits R/O Status bits
0x004B DecimalPoint R/O 0=xxxx, 1=xxx.x, 2=xx.xx, 3=x.xxx
0x0003 ProportionalBand R/W Actual value x10
0x0004 IntegralTime R/W Seconds
0x0005 DerivativeTime R/W Seconds
0x000D Alarm1 SetValue R/W
0x000E Alarm2 SetValue R/W
0x0006 ProgramPattern R/W 0-2 Select program group
0x0007 CurrentSegment R/O 1-8
0x0008 RemainingTime R/O Seconds
Program Segments (8 segments x 3 registers):
0x0009 Segment 1 SV R/W
0x000A Segment 1 Time R/W
0x000B Segment 1 Output R/W
0x000C Segment 2 SV R/W
...(each segment +3)
5.2 DecimalPoint Value Conversion
// Register 值 ↔ 實際值(依 DecimalPoint 設定)
DecimalPoint 0: register = value // 25 → 25
DecimalPoint 1: register = value * 10 // 25.3 → 253
DecimalPoint 2: register = value * 100 // 25.30 → 2530
DecimalPoint 3: register = value * 1000 // 2.530 → 2530
The DecimalPoint register (0x004B) is automatically read upon connection for correct value conversion.
5.3 Known Issues
| Issue | Description | Status |
|---|---|---|
| GST-94 | TAIE FC DecimalPoint register address correction | Done |
| GST-114 | AL1/AL2 + PID address swap correction | Done |
Lesson Learned: Register Map addresses must be verified one-by-one against the actual hardware manual; never rely on assumptions or documentation alone.
6. DI Registration
6.1 Basic Registration
services.AddModbus()
.AddModbusControllers();
6.2 TCP Client
services.AddModbusTcpClient("controller1", opts =>
{
opts.Host = "192.168.1.100";
opts.Port = 502;
opts.ConnectionTimeoutMs = 5000;
opts.ReadWriteTimeoutMs = 3000;
opts.RetryCount = 3;
opts.RetryDelayMs = 100;
opts.KeepAlive = true;
});
6.3 RTU Client
services.AddModbusRtuClient("device", opts =>
{
opts.PortName = "COM3";
opts.BaudRate = 9600;
});
6.4 TAIE FC Controller
// 溫度控制器
services.AddTaieFcTemperatureController(
name: "Chamber1",
modbusClientName: "controller1",
opts =>
{
opts.UnitId = 1;
opts.DecimalPoint = 1; // xxx.x
opts.TimeoutMs = 3000;
});
// 壓力控制器
services.AddTaieFcPressureController(
name: "Pressure1",
modbusClientName: "controller1",
opts =>
{
opts.UnitId = 2;
opts.DecimalPoint = 2; // xx.xx
});
7. Integration Patterns
7.1 Pattern A: Direct IModbusClient
The simplest approach, suitable for one-off read/write operations.
var client = factory.CreateTcpClient(options);
await client.ConnectAsync(ct);
var registers = await client.ReadHoldingRegistersAsync(
unitId: 1, startAddress: 0x0000, quantity: 10, ct);
await client.WriteSingleRegisterAsync(1, 0x0000, 5000, ct);
await client.DisconnectAsync(ct);
7.2 Pattern B: TAIE FC Controller
Device-level abstraction, suitable for temperature/pressure control devices.
await controller.ConnectAsync(ct);
var pv = await controller.GetProcessValueAsync(ct); // 25.3°C
await controller.SetSetValueAsync(30.5, ct);
// 載入程式段
var segments = new ProgramSegment[]
{
new(SetValue: 30.0, TimeSeconds: 60),
new(SetValue: 40.0, TimeSeconds: 120),
new(SetValue: 30.0, TimeSeconds: 60)
};
await controller.SetProgramSegmentsAsync(segments, ct);
await controller.StartProgramAsync(ct);
7.3 Pattern C: Polling Service (PLC Monitor)
Background service with continuous polling, suitable for real-time monitoring.
// DI 註冊
services.AddSingleton<IPollingDataCache, PlcPollingService>();
services.AddHostedService<PlcPollingService>();
// 內部流程:
// 1. 從 DB 載入 Device + Tag 配置
// 2. 建立各 Device 的 IModbusClient 連線
// 3. PeriodicTimer 定期輪詢(預設 1 秒)
// 4. 平行讀取所有 Device
// 5. Raw 值 → 工程單位(Scaling)
// 6. 批次寫入 TimescaleDB
// 7. 更新 In-Memory Cache
// 8. 觸發 DataUpdated 事件 → UI 更新
7.4 Pattern D: CommandQueue Scheduling
Shared bus with multiple Controllers, suitable for ProcessVision.
var queue = new ModbusCommandQueue(logger);
controller1.CommandQueue = queue;
controller2.CommandQueue = queue;
// Polling(Low)→ 可被使用者操作取消
var pv = await controller1.GetProcessValueAsync(ct);
// 使用者寫入(High)→ 取消排隊中的 Low 命令
await controller2.SetSetValueAsync(50.0, ct);
// 原子批次(Critical)→ 阻塞所有操作
await queue.ExecuteBatchAsync(async ct =>
{
await controller1.SetSetValueAsync(25.0, ct);
await controller2.SetSetValueAsync(30.0, ct);
}, ModbusCommandPriority.Critical, ct);
8. Simulator Integration
8.1 GST.Simulator Support
The Simulator provides Modbus device simulation, supporting:
- Holding / Input Registers, Coils / Discrete Inputs
- TCP Server mode (simulating remote devices)
- Fault injection (no response, CRC errors, delayed response, Modbus Exception)
8.2 Testing Workflow
1. Start GST.Simulator (TCP Server mode)
2. Application connects to Simulator's IP:Port
3. Simulator responds with default Register values
4. Faults can be injected to verify error handling
9. Frequently Asked Questions
9.1 Incorrect Register Address
Symptom: Read values are incorrect or meaningless. Solution: Verify each Register Map address against the hardware manual one-by-one, paying attention to address offsets (some devices are 0-based, others are 1-based).
9.2 Abnormal DecimalPoint Values
Symptom: Temperature displays 253 instead of 25.3. Solution: Confirm that the Controller's DecimalPoint setting is correct, or let the Controller auto-detect it (by reading register 0x004B on connection).
9.3 Shared Bus Conflicts with Multiple Devices
Symptom: Intermittent read/write failures, mixed-up responses. Solution: Use CommandQueue to serialize all operations, ensuring only one request is active at a time.
9.4 TCP Disconnect and Reconnect
Symptom: Communication interruption after prolonged operation.
Solution: Enable KeepAlive + RetryCount. The TCP Client has a built-in automatic reconnection mechanism.
Document Version: v1.0 Created: 2026-03-16 Last Updated: 2026-03-16 Linear Issue: GST-165