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.
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(())
}
The shared task consists of two complementary subtasks.
Generate IEC 61131-3 compliant Structured Text programs from natural language descriptions of industrial control logic.
Generate Rust programs from natural language descriptions of industrial control logic.
Only open-source models with up to 8B parameters are permitted for participation.