Skip to main content

ControlForge IEC 60870-5-104 Protocol Guide

James M. Belcher Founder, JMB Technical Services LLC April 2026 | ControlForge v1.0.533


1. Architecture Overview

ControlForge implements a complete IEC 60870-5-104 stack — both controlling station (client/master) and controlled station (server/slave) — callable directly from IEC 61131-3 Structured Text. No external libraries, no XML configuration files, no code generation. You create clients and servers, read and write data objects, and manage connections with plain function calls in your ST programs.

RoleFunctionsUse Case
Client (Controlling Station)IEC104_CLIENT_CREATE / IEC104_CLIENT_READ_* / IEC104_CLIENT_WRITE_*Poll remote RTUs, protection relays, bay controllers, IEDs
Server (Controlled Station)IEC104_SERVER_CREATE / IEC104_SERVER_SET_* / IEC104_SERVER_GET_*Expose ControlForge data to SCADA masters, control centers, energy management systems

Both roles can run simultaneously. A single ControlForge instance can poll substation IEDs as a client while serving aggregated data to a utility control center as a server — all from the same ST program.

System Diagram

Why IEC 60870-5-104?

IEC 60870-5-104 (commonly "IEC 104") is the dominant telecontrol protocol for power system SCADA in Europe, Asia, Africa, and South America — the international counterpart to DNP3. It runs over TCP/IP and is the standard protocol for:

  • Transmission SCADA — communication between control centers and substations
  • Distribution automation — feeder monitoring, recloser control, capacitor bank switching
  • Generation dispatch — turbine telemetry and setpoint control from EMS
  • Interconnection metering — real-time power flow data between grid operators
  • Renewable integration — wind farm and solar plant monitoring and curtailment

Key protocol features:

  • Application layer (IEC 60870-5-101) over TCP/IP transport (IEC 60870-5-104)
  • Spontaneous transmission — controlled stations push changes without polling
  • Time-tagged data — CP56Time2a timestamps with millisecond resolution
  • Cause of transmission — every ASDU carries why it was sent (spontaneous, interrogated, periodic, etc.)
  • General interrogation — client can request a full snapshot of all data points
  • Common address (CASDU) — identifies the station/logical device (1-65534)
  • Information object address (IOA) — identifies each data point within a station (1-16777215)

ControlForge abstracts the protocol complexity. Your ST programs read and write typed data objects by IOA — the runtime handles APCI framing, I/S/U-format messages, sequence numbering, t1/t2/t3 timers, and connection supervision internally.

IEC 104 Data Types

IEC 104 organizes data into typed information objects. Understanding these mappings is essential for integrating with utility SCADA systems.

Data TypeASDU Type IDControlForge TypeDirectionTypical Use
Single Point (SP)M_SP_NA_1 (1) / M_SP_TB_1 (30)BOOLServer → ClientBreaker status, switch position, alarm flags
Double Point (DP)M_DP_NA_1 (3) / M_DP_TB_1 (31)INTServer → ClientBreaker position (00=indeterminate, 01=off, 10=on, 11=indeterminate)
Measured Float (MF)M_ME_NC_1 (13) / M_ME_TF_1 (36)REALServer → ClientVoltage, current, power, frequency, temperature
Measured Scaled (MS)M_ME_NB_1 (11) / M_ME_TE_1 (35)INTServer → ClientTap position, percentage values, scaled measurements
Integrated Total (IT)M_IT_NA_1 (15) / M_IT_TB_1 (37)INTServer → ClientEnergy counters (kWh, MVArh), pulse accumulators
Single Command (SC)C_SC_NA_1 (45) / C_SC_TA_1 (58)BOOLClient → ServerTrip/close commands, start/stop, enable/disable
Setpoint CommandC_SE_NC_1 (50) / C_SE_TC_1 (63)REALClient → ServerVoltage setpoint, power setpoint, tap target

Double Point values: 0 = indeterminate/transit, 1 = OFF/open, 2 = ON/closed, 3 = indeterminate/fault. This encoding is defined by IEC 60870-5-101 and maps directly to breaker and disconnect switch positions.

IOA Addressing

Every data point in IEC 104 is identified by an Information Object Address (IOA) — a 24-bit integer (1-16777215) that is unique within a given common address (station). Unlike DNP3's zero-based type-specific indexing, IEC 104 uses a flat address space where every point has a globally unique IOA within the station.

ConceptDescriptionRange
Common Address (CASDU)Station/device identifier1-65534
IOAPoint address within the station1-16777215

Typical IOA allocation follows utility convention:

IOA RangeData TypeExample Use
1-999Single Point (SP)Breaker status, alarm contacts
1000-1999Double Point (DP)Breaker position, disconnect status
2000-2999Measured FloatVoltage, current, power, frequency
3000-3999Measured ScaledTap position, percentage values
4000-4999Integrated TotalsEnergy counters
5000-5999Single CommandBreaker trip/close
6000-6999Setpoint CommandVoltage/power setpoints

Convention only: IOA ranges are not mandated by the standard — they are engineering conventions. Each utility or system integrator defines their own IOA map. ControlForge does not enforce any mapping; you read and write any IOA with any function.

IEC 104 vs DNP3

Both protocols serve the same purpose (telecontrol SCADA) but differ in adoption and design philosophy:

FeatureIEC 60870-5-104DNP3 (IEEE 1815)
GeographyEurope, Asia, Africa, South AmericaNorth America, Australia
TransportTCP only (port 2404)TCP or serial
Point addressingFlat IOA space (1-16M)Type-specific zero-based index
Double pointNative (2-bit status)Binary input pairs (convention)
Time syncClock sync command (C_CS_NA_1)Time sync over link layer
Standard bodyIEC (Geneva)IEEE (USA)
ControlForge supportThis guideSee DNP3 guide

2. Client Functions (Controlling Station)

The IEC 104 client connects to remote controlled stations (RTUs, IEDs, bay controllers) and performs read/write operations. ControlForge handles general interrogation, spontaneous data reception, and point caching internally. Your ST code reads cached point values — every read returns the most recent value received from the controlled station.

2.1 Connection Management

IEC104_CLIENT_CREATE — Create Named Client Connection

ParamTypeRequiredDescription
nameSTRINGYesUnique connection name
hostSTRINGYesIP address or hostname of the controlled station
portINTYesTCP port (typically 2404)
commonAddrINTNoCommon address / CASDU (default 1)

Returns: BOOL — TRUE if the client connection was created successfully.

(* Connect to a substation RTU at 10.0.0.100, default common address *)
ok := IEC104_CLIENT_CREATE('sub1', '10.0.0.100', 2404);

(* Connect with explicit common address *)
ok := IEC104_CLIENT_CREATE('sub1', '10.0.0.100', 2404, 47);

(* Multiple substations *)
ok := IEC104_CLIENT_CREATE('sub_north', '10.0.1.50', 2404, 1);
ok := IEC104_CLIENT_CREATE('sub_south', '10.0.1.51', 2404, 2);

Named connections: Every client connection has a unique string name. This name is used in all subsequent calls. Create one connection per controlled station — the typical pattern for SCADA polling.

IEC104_CLIENT_CONNECT — Establish TCP Connection

ParamTypeDescription
nameSTRINGConnection name from IEC104_CLIENT_CREATE

Returns: BOOL — TRUE if connected successfully. The runtime automatically sends a STARTDT (Start Data Transfer) activation and issues a general interrogation (C_IC_NA_1) to populate the initial point table.

ok := IEC104_CLIENT_CONNECT('sub1');

IEC104_CLIENT_DISCONNECT — Close TCP Connection

ParamTypeDescription
nameSTRINGConnection name

Returns: BOOL — TRUE if disconnected successfully. Sends a STOPDT (Stop Data Transfer) before closing the TCP connection.

ok := IEC104_CLIENT_DISCONNECT('sub1');

IEC104_CLIENT_IS_CONNECTED — Check Connection State

ParamTypeDescription
nameSTRINGConnection name

Returns: BOOL — TRUE if the TCP connection is active and data transfer is active (STARTDT confirmed).

IF NOT IEC104_CLIENT_IS_CONNECTED('sub1') THEN
IEC104_CLIENT_CONNECT('sub1');
END_IF;

Example: Connection Lifecycle

PROGRAM POU_IEC104Init
VAR
state : INT := 0;
ok : BOOL;
END_VAR

CASE state OF
0: (* Create client connection *)
ok := IEC104_CLIENT_CREATE('sub1', '10.0.0.100', 2404, 1);
IF ok THEN
state := 1;
END_IF;

1: (* Connect — triggers STARTDT + general interrogation *)
ok := IEC104_CLIENT_CONNECT('sub1');
IF ok THEN
state := 10;
END_IF;

10: (* Running — read/write in other programs *)
IF NOT IEC104_CLIENT_IS_CONNECTED('sub1') THEN
state := 1; (* Reconnect *)
END_IF;
END_CASE;
END_PROGRAM

2.2 Read Functions

All read functions return the most recent cached value from the controlled station. ControlForge receives spontaneous data updates and general interrogation responses automatically in the background. Reads never block.

IEC104_CLIENT_READ_SP — Read Single Point

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address

Returns: BOOL — The current single point value at the specified IOA.

(* Read breaker status — IOA 1 *)
breaker_closed := IEC104_CLIENT_READ_SP('sub1', 1);

(* Read alarm contact — IOA 10 *)
overtemp_alarm := IEC104_CLIENT_READ_SP('sub1', 10);

ASDU types: The runtime accepts both M_SP_NA_1 (1) and M_SP_TB_1 (30) — with and without time tags. Time-tagged variants are preferred by the controlled station for spontaneous updates; your read call returns the value regardless of which variant was received.

IEC104_CLIENT_READ_DP — Read Double Point

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address

Returns: INT — The current double point value (0-3).

ValueMeaningIEC Interpretation
0IndeterminateTransit / not available
1OFFOpen / de-energized
2ONClosed / energized
3IndeterminateFault / inconsistent
(* Read breaker position — IOA 1000 *)
breaker_pos := IEC104_CLIENT_READ_DP('sub1', 1000);

IF breaker_pos = 2 THEN
(* Breaker is closed *)
ELSIF breaker_pos = 1 THEN
(* Breaker is open *)
ELSE
(* Indeterminate — transit or fault *)
END_IF;

Double point vs single point: Use double point for equipment that has distinct open and closed feedback contacts (breakers, disconnectors). The 2-bit encoding detects mid-travel and contact disagreement — critical for protection coordination.

IEC104_CLIENT_READ_FLOAT — Read Measured Value (Floating Point)

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address

Returns: REAL — The current measured value in engineering units.

(* Read substation measurements *)
bus_voltage := IEC104_CLIENT_READ_FLOAT('sub1', 2000); (* kV *)
line_current := IEC104_CLIENT_READ_FLOAT('sub1', 2001); (* A *)
active_power := IEC104_CLIENT_READ_FLOAT('sub1', 2002); (* MW *)
frequency := IEC104_CLIENT_READ_FLOAT('sub1', 2003); (* Hz *)

ASDU types: Maps to M_ME_NC_1 (13) and M_ME_TF_1 (36) — short floating point with and without time tag. These carry IEEE 754 single-precision values directly, with no scaling required.

IEC104_CLIENT_READ_SCALED — Read Measured Value (Scaled)

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address

Returns: INT — The raw scaled value (-32768 to 32767).

(* Read transformer tap position — IOA 3000 *)
tap_pos := IEC104_CLIENT_READ_SCALED('sub1', 3000);

(* Read percentage value — IOA 3010 *)
load_pct := IEC104_CLIENT_READ_SCALED('sub1', 3010);

Scaling: Scaled values (M_ME_NB_1 / M_ME_TE_1) are 16-bit signed integers. The engineering unit conversion depends on the point configuration at the controlled station. A tap changer might report position 1-33 directly; a load percentage might use 0-10000 to represent 0.00-100.00%. Consult the station's IOA map for scaling factors.

IEC104_CLIENT_READ_COUNTER — Read Integrated Total

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address

Returns: INT — The current counter value.

(* Read energy counters *)
kwh_import := IEC104_CLIENT_READ_COUNTER('sub1', 4000);
kwh_export := IEC104_CLIENT_READ_COUNTER('sub1', 4001);
mvarh := IEC104_CLIENT_READ_COUNTER('sub1', 4002);

Counter interrogation: The runtime can issue counter interrogation commands (C_CI_NA_1) to freeze and read counters atomically. Integrated totals use ASDU types M_IT_NA_1 (15) and M_IT_TB_1 (37).


2.3 Write Functions (Commands)

Write functions send commands from the controlling station to the controlled station. IEC 104 commands follow a select-before-operate (SBO) or direct execution model, depending on station configuration. ControlForge uses direct execution by default.

IEC104_CLIENT_WRITE_SC — Write Single Command

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address of the command point
valueBOOLCommand value (TRUE = ON, FALSE = OFF)

Returns: BOOL — TRUE if the command was acknowledged by the controlled station.

(* Trip breaker — IOA 5000 *)
ok := IEC104_CLIENT_WRITE_SC('sub1', 5000, FALSE);

(* Close breaker — IOA 5000 *)
ok := IEC104_CLIENT_WRITE_SC('sub1', 5000, TRUE);

(* Enable capacitor bank — IOA 5010 *)
ok := IEC104_CLIENT_WRITE_SC('sub1', 5010, TRUE);

ASDU type: Sends C_SC_NA_1 (45) — single command. The controlled station validates the command and responds with an activation confirmation or negative acknowledgment. The return value reflects whether the command was accepted.

IEC104_CLIENT_WRITE_SETPOINT — Write Setpoint Command

ParamTypeDescription
nameSTRINGConnection name
ioaINTInformation Object Address of the setpoint
valueREALSetpoint value in engineering units

Returns: BOOL — TRUE if the setpoint was acknowledged.

(* Set voltage reference — IOA 6000 *)
ok := IEC104_CLIENT_WRITE_SETPOINT('sub1', 6000, 110.5);

(* Set active power setpoint for wind farm curtailment — IOA 6010 *)
ok := IEC104_CLIENT_WRITE_SETPOINT('sub1', 6010, 45.0);

(* Set transformer tap target — IOA 6020 *)
ok := IEC104_CLIENT_WRITE_SETPOINT('sub1', 6020, 15.0);

ASDU type: Sends C_SE_NC_1 (50) — setpoint command, short floating point. For scaled setpoints, the runtime converts the REAL value to a scaled integer internally when communicating with stations that expect M_ME_NB_1-style values.


2.4 Lifecycle Management

IEC104_CLIENT_DELETE — Remove Client Connection

ParamTypeDescription
nameSTRINGConnection name

Returns: BOOL — TRUE if the connection was removed. Disconnects first if still connected.

ok := IEC104_CLIENT_DELETE('sub1');

IEC104_CLIENT_LIST — List All Client Connections

Returns: []STRING — Array of all active client connection names.

clients := IEC104_CLIENT_LIST();
(* Returns: ['sub_north', 'sub_south'] *)

3. Server Functions (Controlled Station)

The IEC 104 server listens for incoming connections from controlling stations (SCADA masters, control centers). ControlForge manages connection acceptance, general interrogation responses, spontaneous data transmission, and APCI-level keepalives (TESTFR) automatically. Your ST program sets data point values and reads incoming commands.

3.1 Connection Management

IEC104_SERVER_CREATE — Create Named Server

ParamTypeRequiredDescription
nameSTRINGYesUnique server name
portINTYesTCP listen port (typically 2404)
commonAddrINTNoCommon address / CASDU (default 1)

Returns: BOOL — TRUE if the server was created successfully.

(* Create server on default IEC 104 port *)
ok := IEC104_SERVER_CREATE('station1', 2404);

(* Create server with explicit common address *)
ok := IEC104_SERVER_CREATE('station1', 2404, 47);

(* Multiple servers for different logical devices *)
ok := IEC104_SERVER_CREATE('bay1', 2404, 1);
ok := IEC104_SERVER_CREATE('bay2', 2405, 2);

Common address: The CASDU identifies this controlled station to connecting clients. In a substation with multiple bay controllers, each bay typically has its own common address. Clients filter incoming ASDUs by common address.

IEC104_SERVER_START — Begin Listening

ParamTypeDescription
nameSTRINGServer name

Returns: BOOL — TRUE if the server began listening. Accepts incoming TCP connections and responds to STARTDT, general interrogation, and TESTFR automatically.

ok := IEC104_SERVER_START('station1');

IEC104_SERVER_STOP — Stop Listening

ParamTypeDescription
nameSTRINGServer name

Returns: BOOL — TRUE if stopped. Disconnects all connected controlling stations.

ok := IEC104_SERVER_STOP('station1');

IEC104_SERVER_IS_CONNECTED — Check If Any Client Is Connected

ParamTypeDescription
nameSTRINGServer name

Returns: BOOL — TRUE if at least one controlling station is connected and data transfer is active.

IF IEC104_SERVER_IS_CONNECTED('station1') THEN
(* At least one SCADA master is connected *)
END_IF;

Example: Server Lifecycle

PROGRAM POU_IEC104Server
VAR
state : INT := 0;
ok : BOOL;
END_VAR

CASE state OF
0: (* Create server *)
ok := IEC104_SERVER_CREATE('station1', 2404, 1);
IF ok THEN
state := 1;
END_IF;

1: (* Start listening *)
ok := IEC104_SERVER_START('station1');
IF ok THEN
state := 10;
END_IF;

10: (* Running — set data points in other programs *)
;
END_CASE;
END_PROGRAM

3.2 Set Functions (Controlled Station → Controlling Station)

Set functions update the server's data point table. When a controlling station sends a general interrogation or the server sends spontaneous data, the client receives these values. Call these from your ST program to publish field data.

IEC104_SERVER_SET_SP — Set Single Point

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address
valueBOOLPoint value

Returns: BOOL — TRUE if the point was updated.

(* Report equipment status to SCADA *)
IEC104_SERVER_SET_SP('station1', 1, breaker_closed);
IEC104_SERVER_SET_SP('station1', 2, transformer_alarm);
IEC104_SERVER_SET_SP('station1', 3, door_open);
IEC104_SERVER_SET_SP('station1', 10, protection_trip);

Spontaneous transmission: When a single point changes state, the server automatically generates a spontaneous ASDU (cause of transmission = 3) with a CP56Time2a timestamp. The controlling station receives the change without polling — ensuring no state transitions are missed between general interrogations.

IEC104_SERVER_SET_DP — Set Double Point

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address
valueINTDouble point value (0-3)

Returns: BOOL — TRUE if the point was updated.

(* Report breaker position: 1=OFF/open, 2=ON/closed *)
IEC104_SERVER_SET_DP('station1', 1000, 2); (* Breaker closed *)
IEC104_SERVER_SET_DP('station1', 1001, 1); (* Disconnect open *)

(* Report transient state during switching *)
IEC104_SERVER_SET_DP('station1', 1000, 0); (* In transit *)

IEC104_SERVER_SET_FLOAT — Set Measured Value (Floating Point)

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address
valueREALMeasured value in engineering units

Returns: BOOL — TRUE if the point was updated.

(* Report substation measurements to SCADA *)
IEC104_SERVER_SET_FLOAT('station1', 2000, bus_voltage); (* 110.2 kV *)
IEC104_SERVER_SET_FLOAT('station1', 2001, line_current); (* 245.6 A *)
IEC104_SERVER_SET_FLOAT('station1', 2002, active_power); (* 27.1 MW *)
IEC104_SERVER_SET_FLOAT('station1', 2003, reactive_power); (* 8.4 MVAr *)
IEC104_SERVER_SET_FLOAT('station1', 2004, frequency); (* 50.01 Hz *)
IEC104_SERVER_SET_FLOAT('station1', 2005, ambient_temp); (* 35.2 C *)

Deadband: Analog spontaneous events are generated when the value changes by more than the configured deadband. The runtime applies a default deadband appropriate for the point's scale, preventing the event buffer from flooding with noise on fluctuating measurements.

IEC104_SERVER_SET_SCALED — Set Measured Value (Scaled)

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address
valueINTScaled value (-32768 to 32767)

Returns: BOOL — TRUE if the point was updated.

(* Report tap changer position *)
IEC104_SERVER_SET_SCALED('station1', 3000, tap_position); (* e.g. 17 *)

(* Report load as percentage x100 *)
IEC104_SERVER_SET_SCALED('station1', 3001, load_pct_x100); (* 8750 = 87.50% *)

IEC104_SERVER_SET_COUNTER — Set Integrated Total

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address
valueINTCounter value

Returns: BOOL — TRUE if the point was updated.

(* Report energy counters *)
IEC104_SERVER_SET_COUNTER('station1', 4000, kwh_import);
IEC104_SERVER_SET_COUNTER('station1', 4001, kwh_export);
IEC104_SERVER_SET_COUNTER('station1', 4002, mvarh_total);

3.3 Get Functions (Controlling Station → Controlled Station)

Get functions read command values that a controlling station has written to the server. Use these to receive control commands and setpoints from the SCADA system.

IEC104_SERVER_GET_SC — Get Single Command

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address of the command point

Returns: BOOL — The last commanded value from the controlling station.

(* Check if SCADA commanded breaker close *)
close_cmd := IEC104_SERVER_GET_SC('station1', 5000);
IF close_cmd THEN
(* Execute close sequence on local equipment *)
END_IF;

(* Check capacitor bank command *)
cap_enable := IEC104_SERVER_GET_SC('station1', 5010);

IEC104_SERVER_GET_SETPOINT — Get Setpoint Command

ParamTypeDescription
nameSTRINGServer name
ioaINTInformation Object Address of the setpoint

Returns: REAL — The last setpoint value from the controlling station.

(* Read voltage setpoint from EMS *)
voltage_sp := IEC104_SERVER_GET_SETPOINT('station1', 6000);

(* Read active power curtailment setpoint *)
power_limit := IEC104_SERVER_GET_SETPOINT('station1', 6010);

(* Read tap position target *)
tap_target := IEC104_SERVER_GET_SETPOINT('station1', 6020);

3.4 Diagnostics and Lifecycle

IEC104_SERVER_GET_STATS — Server Statistics

ParamTypeDescription
nameSTRINGServer name

Returns: STRING — JSON-formatted connection and protocol statistics.

stats := IEC104_SERVER_GET_STATS('station1');
(* Returns: {"connected_clients": 2, "interrogations": 156,
"spontaneous_sent": 12847, "commands_received": 42,
"testfr_sent": 3210, "testfr_recv": 3208} *)

IEC104_SERVER_DELETE — Remove Server

ParamTypeDescription
nameSTRINGServer name

Returns: BOOL — TRUE if the server was removed. Stops listening and disconnects all clients first.

ok := IEC104_SERVER_DELETE('station1');

IEC104_SERVER_LIST — List All Servers

Returns: []STRING — Array of all active server names.

servers := IEC104_SERVER_LIST();
(* Returns: ['station1', 'bay2'] *)

4. Complete Examples

4.1 Substation Gateway — Poll IEDs, Serve to SCADA

A common architecture: ControlForge sits at the substation as a data concentrator. It polls bay-level IEDs as an IEC 104 client, aggregates the data, and serves it to the utility control center as an IEC 104 server.

PROGRAM POU_SubstationGateway
VAR
init_done : BOOL := FALSE;
ok : BOOL;

(* Bay 1 measurements from IED *)
bay1_breaker : INT;
bay1_voltage : REAL;
bay1_current : REAL;
bay1_power : REAL;

(* Bay 2 measurements from IED *)
bay2_breaker : INT;
bay2_voltage : REAL;
bay2_current : REAL;
bay2_power : REAL;

(* Outgoing command from SCADA *)
bay1_close_cmd : BOOL;
bay1_voltage_sp : REAL;
END_VAR

IF NOT init_done THEN
(* Create client connections to bay IEDs *)
ok := IEC104_CLIENT_CREATE('bay1_ied', '10.0.10.1', 2404, 1);
ok := IEC104_CLIENT_CREATE('bay2_ied', '10.0.10.2', 2404, 2);
ok := IEC104_CLIENT_CONNECT('bay1_ied');
ok := IEC104_CLIENT_CONNECT('bay2_ied');

(* Create server for SCADA uplink *)
ok := IEC104_SERVER_CREATE('scada_uplink', 2404, 47);
ok := IEC104_SERVER_START('scada_uplink');

init_done := TRUE;
END_IF;

(* === Read from bay IEDs === *)
bay1_breaker := IEC104_CLIENT_READ_DP('bay1_ied', 1000);
bay1_voltage := IEC104_CLIENT_READ_FLOAT('bay1_ied', 2000);
bay1_current := IEC104_CLIENT_READ_FLOAT('bay1_ied', 2001);
bay1_power := IEC104_CLIENT_READ_FLOAT('bay1_ied', 2002);

bay2_breaker := IEC104_CLIENT_READ_DP('bay2_ied', 1000);
bay2_voltage := IEC104_CLIENT_READ_FLOAT('bay2_ied', 2000);
bay2_current := IEC104_CLIENT_READ_FLOAT('bay2_ied', 2001);
bay2_power := IEC104_CLIENT_READ_FLOAT('bay2_ied', 2002);

(* === Publish aggregated data to SCADA === *)
IEC104_SERVER_SET_DP('scada_uplink', 1000, bay1_breaker);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2000, bay1_voltage);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2001, bay1_current);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2002, bay1_power);

IEC104_SERVER_SET_DP('scada_uplink', 1100, bay2_breaker);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2100, bay2_voltage);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2101, bay2_current);
IEC104_SERVER_SET_FLOAT('scada_uplink', 2102, bay2_power);

(* === Forward SCADA commands to bay IED === *)
bay1_close_cmd := IEC104_SERVER_GET_SC('scada_uplink', 5000);
IF bay1_close_cmd THEN
IEC104_CLIENT_WRITE_SC('bay1_ied', 5000, TRUE);
END_IF;

bay1_voltage_sp := IEC104_SERVER_GET_SETPOINT('scada_uplink', 6000);
IF bay1_voltage_sp > 0.0 THEN
IEC104_CLIENT_WRITE_SETPOINT('bay1_ied', 6000, bay1_voltage_sp);
END_IF;
END_PROGRAM

4.2 Wind Farm SCADA Interface

A wind farm controller exposes turbine data to the grid operator's EMS via IEC 104 and accepts curtailment setpoints.

PROGRAM POU_WindFarmSCADA
VAR
init_done : BOOL := FALSE;
ok : BOOL;
i : INT;

(* Turbine telemetry (from internal Modbus polling — not shown) *)
turbine_active : ARRAY[1..20] OF BOOL;
turbine_power : ARRAY[1..20] OF REAL;
turbine_wind : ARRAY[1..20] OF REAL;
total_power : REAL;
total_energy : INT;

(* Grid operator commands *)
curtail_cmd : BOOL;
power_limit : REAL;
END_VAR

IF NOT init_done THEN
ok := IEC104_SERVER_CREATE('grid_ems', 2404, 100);
ok := IEC104_SERVER_START('grid_ems');
init_done := TRUE;
END_IF;

(* === Publish farm-level data === *)
total_power := 0.0;
FOR i := 1 TO 20 DO
(* Per-turbine status: IOA 1..20 *)
IEC104_SERVER_SET_SP('grid_ems', i, turbine_active[i]);

(* Per-turbine power: IOA 2000..2019 *)
IEC104_SERVER_SET_FLOAT('grid_ems', 1999 + i, turbine_power[i]);

(* Per-turbine wind speed: IOA 2100..2119 *)
IEC104_SERVER_SET_FLOAT('grid_ems', 2099 + i, turbine_wind[i]);

IF turbine_active[i] THEN
total_power := total_power + turbine_power[i];
END_IF;
END_FOR;

(* Farm total power: IOA 2500 *)
IEC104_SERVER_SET_FLOAT('grid_ems', 2500, total_power);

(* Cumulative energy: IOA 4000 *)
IEC104_SERVER_SET_COUNTER('grid_ems', 4000, total_energy);

(* === Receive grid operator commands === *)
curtail_cmd := IEC104_SERVER_GET_SC('grid_ems', 5000);
power_limit := IEC104_SERVER_GET_SETPOINT('grid_ems', 6000);

IF curtail_cmd AND power_limit > 0.0 THEN
(* Apply curtailment to turbine controllers *)
END_IF;
END_PROGRAM

4.3 Redundant SCADA Polling with Failover

Two ControlForge instances poll the same substation. The primary handles commands; the secondary monitors and takes over if the primary disconnects.

PROGRAM POU_RedundantPoll
VAR
init_done : BOOL := FALSE;
ok : BOOL;
is_primary : BOOL := TRUE; (* Set by configuration *)

(* Substation data *)
breaker_status : INT;
bus_voltage : REAL;
line_current : REAL;
END_VAR

IF NOT init_done THEN
ok := IEC104_CLIENT_CREATE('sub1', '10.0.0.100', 2404, 1);
ok := IEC104_CLIENT_CONNECT('sub1');
init_done := TRUE;
END_IF;

IF NOT IEC104_CLIENT_IS_CONNECTED('sub1') THEN
IEC104_CLIENT_CONNECT('sub1');
END_IF;

(* Read — both primary and secondary receive data *)
breaker_status := IEC104_CLIENT_READ_DP('sub1', 1000);
bus_voltage := IEC104_CLIENT_READ_FLOAT('sub1', 2000);
line_current := IEC104_CLIENT_READ_FLOAT('sub1', 2001);

(* Write — only primary sends commands *)
IF is_primary THEN
(* Command logic here *)
END_IF;
END_PROGRAM

4.4 Protocol Translation: IEC 104 to Modbus TCP

Bridge legacy Modbus field devices into an IEC 104 SCADA system. ControlForge reads Modbus registers and publishes them as IEC 104 data objects.

PROGRAM POU_IEC104ModbusBridge
VAR
init_done : BOOL := FALSE;
ok : BOOL;

(* Modbus data (from ModbusTCPClientRead — see Modbus guide) *)
flow_rate : REAL;
tank_level : REAL;
pump_running : BOOL;
total_volume : INT;

(* SCADA commands *)
pump_cmd : BOOL;
flow_sp : REAL;
END_VAR

IF NOT init_done THEN
(* Modbus client to field device — see Modbus TCP guide *)
ok := MB_CLIENT_CREATE('flowmeter', '10.0.0.50', 502);
ok := MB_CLIENT_CONNECT('flowmeter');

(* IEC 104 server for SCADA *)
ok := IEC104_SERVER_CREATE('water_scada', 2404, 10);
ok := IEC104_SERVER_START('water_scada');

init_done := TRUE;
END_IF;

(* === Read Modbus, publish IEC 104 === *)
IEC104_SERVER_SET_SP('water_scada', 1, pump_running);
IEC104_SERVER_SET_FLOAT('water_scada', 2000, flow_rate);
IEC104_SERVER_SET_FLOAT('water_scada', 2001, tank_level);
IEC104_SERVER_SET_COUNTER('water_scada', 4000, total_volume);

(* === Receive SCADA commands, write Modbus === *)
pump_cmd := IEC104_SERVER_GET_SC('water_scada', 5000);
flow_sp := IEC104_SERVER_GET_SETPOINT('water_scada', 6000);
END_PROGRAM

5. Protocol Details

5.1 APCI (Application Protocol Control Information)

IEC 104 wraps every ASDU in an APCI frame over TCP. The runtime manages all APCI framing automatically — you never build frames manually.

┌──────┬──────┬───────────────────────┬────────────────────┐
│ 0x68 │ LEN │ Control Field (4B) │ ASDU (variable) │
│ start│ │ │ │
└──────┴──────┴───────────────────────┴────────────────────┘

Three frame formats:

FormatPurposeControl Field
I-formatData transfer (numbered)Send seq + Recv seq
S-formatSupervisory (ACK only)Recv seq
U-formatUnnumbered controlSTARTDT / STOPDT / TESTFR

5.2 Connection Supervision Timers

The runtime manages the four IEC 104 timers automatically:

TimerDefaultPurpose
t030 sTCP connection establishment timeout
t115 sSend/test APDU timeout
t210 sAcknowledgment timeout (triggers S-format)
t320 sIdle timeout (triggers TESTFR)

No configuration needed. The defaults conform to IEC 60870-5-104 Section 10 and work with all major SCADA vendors (ABB, Siemens, GE, Schneider, Hitachi Energy). The runtime sends TESTFR keepalives automatically.

5.3 General Interrogation

When a client connects (or reconnects), the runtime automatically issues a general interrogation command (C_IC_NA_1, type 100) to populate the complete point table. The controlled station responds with all configured data points. Subsequent updates arrive as spontaneous transmissions.

5.4 Cause of Transmission (COT)

Every ASDU carries a reason code. The runtime handles these internally, but understanding them aids debugging:

COTValueMeaning
Periodic1Cyclic transmission
Background2Background scan
Spontaneous3Value changed
Initialized4Station initialized
Request5Requested by client
Activation6Command activation
ActivationCon7Command confirmed
Deactivation8Command deactivation
DeactivationCon9Deactivation confirmed
ActivationTerm10Command terminated
Interrogated20Response to GI (station)

6. Troubleshooting

Connection fails immediately

  • Verify the controlled station is reachable: ping 10.0.0.100
  • Confirm port 2404 is open: nc -zv 10.0.0.100 2404
  • Check the common address matches the station configuration — mismatched CASDU causes silent rejection

Connected but no data

  • The runtime sends general interrogation automatically on connect. If the controlled station ignores GI, verify the common address matches.
  • Some stations require a specific originator address — the default (0) works with most implementations.

Commands rejected

  • Check the ASDU type expected by the controlled station. Some stations require time-tagged commands (type 58/63 instead of 45/50).
  • Verify the IOA is configured as a command point at the controlled station. Writing to a monitoring IOA is rejected.
  • Confirm select-before-operate (SBO) is not required. If SBO is mandatory, the direct execute command will be rejected.

Spontaneous data not arriving

  • Ensure STARTDT was confirmed. Check IEC104_CLIENT_IS_CONNECTED returns TRUE.
  • The controlled station may have spontaneous transmission disabled for some points — verify station configuration.
  • Check that t3 keepalives are working — a firewall may be dropping idle TCP connections.

Double point stuck at 0 or 3

  • Value 0 (indeterminate) during switching is normal and transient.
  • Value 3 (indeterminate/fault) means the open and close contacts disagree — check the field wiring and auxiliary contacts on the switchgear.

Appendix A: Quick Reference

Client Functions (13)

FunctionReturnsDescription
IEC104_CLIENT_CREATE(name, host, port [, commonAddr])BOOLCreate named client connection
IEC104_CLIENT_CONNECT(name)BOOLEstablish TCP + STARTDT + GI
IEC104_CLIENT_DISCONNECT(name)BOOLSTOPDT + close TCP
IEC104_CLIENT_IS_CONNECTED(name)BOOLCheck connection and data transfer state
IEC104_CLIENT_READ_SP(name, ioa)BOOLRead single point
IEC104_CLIENT_READ_DP(name, ioa)INTRead double point (0-3)
IEC104_CLIENT_READ_FLOAT(name, ioa)REALRead measured float
IEC104_CLIENT_READ_SCALED(name, ioa)INTRead measured scaled
IEC104_CLIENT_READ_COUNTER(name, ioa)INTRead integrated total
IEC104_CLIENT_WRITE_SC(name, ioa, value)BOOLSend single command
IEC104_CLIENT_WRITE_SETPOINT(name, ioa, value)BOOLSend setpoint command
IEC104_CLIENT_DELETE(name)BOOLRemove client connection
IEC104_CLIENT_LIST()[]STRINGList all client connections

Server Functions (14)

FunctionReturnsDescription
IEC104_SERVER_CREATE(name, port [, commonAddr])BOOLCreate named server
IEC104_SERVER_START(name)BOOLBegin listening for clients
IEC104_SERVER_STOP(name)BOOLStop listening
IEC104_SERVER_IS_CONNECTED(name)BOOLCheck if any client is connected
IEC104_SERVER_SET_SP(name, ioa, value)BOOLSet single point
IEC104_SERVER_SET_DP(name, ioa, value)BOOLSet double point (0-3)
IEC104_SERVER_SET_FLOAT(name, ioa, value)BOOLSet measured float
IEC104_SERVER_SET_SCALED(name, ioa, value)BOOLSet measured scaled
IEC104_SERVER_SET_COUNTER(name, ioa, value)BOOLSet integrated total
IEC104_SERVER_GET_SC(name, ioa)BOOLGet single command from client
IEC104_SERVER_GET_SETPOINT(name, ioa)REALGet setpoint from client
IEC104_SERVER_GET_STATS(name)STRINGConnection/protocol statistics (JSON)
IEC104_SERVER_DELETE(name)BOOLRemove server
IEC104_SERVER_LIST()[]STRINGList all servers

Appendix B: ASDU Types Used

Type IDNameDescriptionControlForge Function
1M_SP_NA_1Single point — staticReadSP / SetSP
3M_DP_NA_1Double point — staticReadDP / SetDP
11M_ME_NB_1Measured scaled — staticReadScaled / SetScaled
13M_ME_NC_1Measured float — staticReadFloat / SetFloat
15M_IT_NA_1Integrated total — staticReadCounter / SetCounter
30M_SP_TB_1Single point — time-taggedReadSP / SetSP
31M_DP_TB_1Double point — time-taggedReadDP / SetDP
35M_ME_TE_1Measured scaled — time-taggedReadScaled / SetScaled
36M_ME_TF_1Measured float — time-taggedReadFloat / SetFloat
37M_IT_TB_1Integrated total — time-taggedReadCounter / SetCounter
45C_SC_NA_1Single commandWriteSC / GetSC
50C_SE_NC_1Setpoint command (float)WriteSetpoint / GetSetpoint
58C_SC_TA_1Single command — time-taggedWriteSC / GetSC
63C_SE_TC_1Setpoint command (float) — time-taggedWriteSetpoint / GetSetpoint
100C_IC_NA_1General interrogation(automatic on connect)
101C_CI_NA_1Counter interrogation(automatic for counters)
103C_CS_NA_1Clock sync command(automatic)

ControlForge v1.0.533 | IEC 60870-5-104 Client + Server | IEC 61131-3 Structured Text

(c) 2026 JMB Technical Services LLC. All rights reserved.