Task Description

Motivation

Programmable Logic Controllers (PLCs) are widely used in industrial automation systems such as manufacturing, energy, transportation, and process industries. PLC programming requires specialized expertise and knowledge of industrial control systems.

Domain experts and plant operators often describe control logic in natural language, creating a significant gap between system requirements and actual implementation. Errors in PLC programs may lead to safety hazards, production losses, and equipment damage.

This shared task aims to bridge this gap by encouraging the development of AI-based systems capable of generating and refining IEC 61131-3 Structured Text (ST) programs from natural language descriptions.

Use Case Example

Natural Language Description

Start the conveyor belt when 'Start' Button is pressed and upon release the start button the conveyor maintains the running state. 'Stop' Button is used to stop the conveyor belt.

Equivalent Structured Text Program
PROGRAM start_stop
  VAR
    start AT %IX0.0 : BOOL;
    stop AT %IX0.1 : BOOL;
    LED AT %QX0.0 : BOOL;
  END_VAR

  LED := stop AND (LED OR start);
END_PROGRAM


CONFIGURATION Config0

  RESOURCE Res0 ON PLC
    TASK task0(INTERVAL := T#20ms, PRIORITY := 0);
    PROGRAM instance0 WITH task0 : start_stop;
  END_RESOURCE
END_CONFIGURATION
Equivalent Rust Program
// Auto-generated by st_to_rust.py — review before use.
//
// Cargo.toml:
//   [dependencies]
//   roboplc = "0.6"
//   rmodbus = "0.8"
//
// Target: Linux / RoboPLC

#![allow(non_snake_case, non_camel_case_types)]

use roboplc::prelude::*;
use roboplc::controller::Context;
use rmodbus::{client::ModbusRequest, guess_response_frame_len, ModbusProto};
use std::io::{Read, Write};
use std::net::TcpStream;
use std::thread;
use std::time::Duration;

const MODBUS_HOST:    &str = "192.168.1.100:502";   // ← change to your PLC IP
const MODBUS_UNIT_ID: u8   = 1;
const TIMEOUT: Duration    = Duration::from_secs(1);


// ── Modbus helpers ────────────────────────────────────────────────────────────
//use rmodbus::{client::ModbusRequest, guess_response_frame_len, ModbusProto};
//use std::io::{Read, Write};
//use std::net::TcpStream;

/// FC02 — Read Discrete Input (%IX)
fn read_discrete_input(stream: &mut TcpStream, unit: u8, addr: u16) -> Result> {
    let mut mreq = ModbusRequest::new(unit, ModbusProto::TcpUdp);
    let mut request = Vec::new();
    mreq.generate_get_discretes(addr, 1, &mut request)?;
    stream.write_all(&request)?;

    let mut header = [0u8; 6];
    stream.read_exact(&mut header)?;
    let len = guess_response_frame_len(&header, ModbusProto::TcpUdp)?;
    let mut response = header.to_vec();
    if len > 6 {
        let mut rest = vec![0u8; (len - 6) as usize];
        stream.read_exact(&mut rest)?;
        response.extend(rest);
    }
    let mut data = Vec::new();
    mreq.parse_ok(&response)?;
    mreq.parse_bool_u8(&response, &mut data)?;
    Ok(data.first().copied().unwrap_or(0) & 0x01 != 0)
}

/// FC01 — Read Coil (%QX)
fn read_coil(stream: &mut TcpStream, unit: u8, addr: u16) -> Result> {
    let mut mreq = ModbusRequest::new(unit, ModbusProto::TcpUdp);
    let mut request = Vec::new();
    mreq.generate_get_coils(addr, 1, &mut request)?;
    stream.write_all(&request)?;

    let mut header = [0u8; 6];
    stream.read_exact(&mut header)?;
    let len = guess_response_frame_len(&header, ModbusProto::TcpUdp)?;
    let mut response = header.to_vec();
    if len > 6 {
        let mut rest = vec![0u8; (len - 6) as usize];
        stream.read_exact(&mut rest)?;
        response.extend(rest);
    }
    let mut data = Vec::new();
    mreq.parse_ok(&response)?;
    mreq.parse_bool_u8(&response, &mut data)?;
    Ok(data.first().copied().unwrap_or(0) & 0x01 != 0)
}

/// FC05 — Write Single Coil
fn write_coil(stream: &mut TcpStream, unit: u8, addr: u16, value: bool) -> Result<(), Box> {
    let mut mreq = ModbusRequest::new(unit, ModbusProto::TcpUdp);
    let mut request = Vec::new();
    mreq.generate_set_coil(addr, value, &mut request)?;
    stream.write_all(&request)?;

    let mut header = [0u8; 6];
    stream.read_exact(&mut header)?;
    let len = guess_response_frame_len(&header, ModbusProto::TcpUdp)?;
    let mut response = header.to_vec();
    if len > 6 {
        let mut rest = vec![0u8; (len - 6) as usize];
        stream.read_exact(&mut rest)?;
        response.extend(rest);
    }
    mreq.parse_ok(&response)?;
    Ok(())
}

/// FC03 — Read Holding Register (%IW, %QW, %MW)
fn read_holding_register(stream: &mut TcpStream, unit: u8, addr: u16) -> Result> {
    let mut mreq = ModbusRequest::new(unit, ModbusProto::TcpUdp);
    let mut request = Vec::new();
    mreq.generate_get_holdings(addr, 1, &mut request)?;
    stream.write_all(&request)?;

    let mut header = [0u8; 6];
    stream.read_exact(&mut header)?;
    let len = guess_response_frame_len(&header, ModbusProto::TcpUdp)?;
    let mut response = header.to_vec();
    if len > 6 {
        let mut rest = vec![0u8; (len - 6) as usize];
        stream.read_exact(&mut rest)?;
        response.extend(rest);
    }
    let mut data = Vec::new();
    mreq.parse_ok(&response)?;
    mreq.parse_u16(&response, &mut data)?;
    Ok(data.first().copied().unwrap_or(0) as i16)
}

/// FC06 — Write Single Register
fn write_holding_register(stream: &mut TcpStream, unit: u8, addr: u16, value: i16) -> Result<(), Box> {
    let mut mreq = ModbusRequest::new(unit, ModbusProto::TcpUdp);
    let mut request = Vec::new();
    mreq.generate_set_holding(addr, value as u16, &mut request)?;
    stream.write_all(&request)?;

    let mut header = [0u8; 6];
    stream.read_exact(&mut header)?;
    let len = guess_response_frame_len(&header, ModbusProto::TcpUdp)?;
    let mut response = header.to_vec();
    if len > 6 {
        let mut rest = vec![0u8; (len - 6) as usize];
        stream.read_exact(&mut rest)?;
        response.extend(rest);
    }
    mreq.parse_ok(&response)?;
    Ok(())
}



// ── PROGRAM: start_stop ──────────────────────────────────────────────────

#[derive(Debug, Default, Clone)]
pub struct start_stop {
    pub start: bool,  // %IX0.0 → Modbus addr 6400
    pub stop: bool,  // %IX0.1 → Modbus addr 6409
    pub LED: bool,  // %QX0.0 → Modbus addr 6400
}



impl start_stop {
    pub fn call(&mut self, start: bool, stop: bool) -> bool {
        self.start = start;
        self.stop = stop;
        self.LED = self.stop && (self.LED || self.start);
        self.LED
    }
}



// ── RoboPLC Worker ────────────────────────────────────────────────────────────
const ADDR_START: u16 = 0; // %IX0.0
const ADDR_STOP: u16 = 1; // %IX0.1
const ADDR_LED: u16 = 0; // %QX0.0

struct start_stopWorker {
    plc: start_stop,
    stream: TcpStream,
}

impl WorkerOptions for start_stopWorker {
    fn worker_name(&self) -> &str { "start_stopWorker" }
}

impl Worker<(), ()> for start_stopWorker {
    fn run(&mut self, _ctx: &Context<(), ()>) -> WResult {
        loop {
        let start_state = match read_discrete_input(&mut self.stream, MODBUS_UNIT_ID, 0) {
            Ok(val) => val,
            Err(e) => { eprintln!("Modbus read error (start): {e}"); self.plc.start }
        };
        let stop_state = match read_discrete_input(&mut self.stream, MODBUS_UNIT_ID, 1) {
            Ok(val) => val,
            Err(e) => { eprintln!("Modbus read error (stop): {e}"); self.plc.stop }
        };
        let result = self.plc.call(start_state, stop_state);
        if let Err(e) = write_coil(&mut self.stream, MODBUS_UNIT_ID, 0, self.plc.LED) {
            eprintln!("Modbus write error (LED): {e}");
        }
            thread::sleep(Duration::from_millis(10));
        }
    }
}

fn main() -> Result<(), Box> {
    roboplc::setup_panic();
    let stream = TcpStream::connect(MODBUS_HOST)?;
    stream.set_read_timeout(Some(TIMEOUT))?;
    stream.set_write_timeout(Some(TIMEOUT))?;
    let mut controller: Controller<(), ()> = Controller::new();
    controller.spawn_worker(start_stopWorker { plc: start_stop::default(), stream })?;
    controller.block();
    Ok(())
}


Shared Task Structure

The shared task consists of two complementary subtasks.

Task A: Natural Language to Structured Text

Generate IEC 61131-3 compliant Structured Text programs from natural language descriptions of industrial control logic.

Input

Output

Objective

Task B: Natural Language to Rust Code

Generate Rust programs from natural language descriptions of industrial control logic.

Input

Output

Objective

Allowed Resources

LLM Usage Policy

Only open-source models with up to 8B parameters are permitted for participation.