Skip to main content

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 RTUModbus ASCIIModbus TCP
Transport MediumRS-485/232RS-485/232Ethernet
FramingBinary + CRC16ASCII Hex + LRCMBAP Header
EfficiencyHighestLower (2x data volume)High
Error DetectionCRC-16LRCTCP itself
Use CaseSingle device direct connectionLegacy device compatibilityMulti-device / Remote
GST ImplementationModbusRtuClientModbusTcpClient

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

IssueDescriptionStatus
GST-94TAIE FC DecimalPoint register address correctionDone
GST-114AL1/AL2 + PID address swap correctionDone

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