PLC context

PLC context is the single common variable space where PLC program threads and I/O exchange data.

The context is automatically generated and placed under a read-write lock.

Defining

The context is defined in the PLC configuration (e.g. plc.yml) as

context:
  fields:
    var1: REAL
    var2: DINT
    var3: BOOL

Data types

The context may contain the following IEC-61131 data types, which are automatically translated into Rust data types:

IEC

Rust

Description

BOOL

bool

Boolean

BYTE, USINT

u8

8-bit unsigned integer

WORD, UINT

u16

16-bit unsigned integer

DWORD, UDINT

u32

32-bit unsigned integer

LWORD, ULINT

u64

64-bit unsigned integer

SINT

i8

8-bit signed integer

INT

i16

16-bit signed integer

DINT

i32

32-bit signed integer

LINT

i64

64-bit signed integer

REAL

f32

32-bit float

LREAL

f64

64-bit float

Rust data types can be used directly as well with no limitations.

Arrays

Arrays can be defined in both IEC-61131-way:

context:
  fields:
    vars: REAL[4]

or in a Rust way:

context:
  fields:
    vars: "[f32; 4]"

Structures

If a context field as got sub-fields, they are automatically translated into a structure:

context:
  fields:
    struct1:
      var1: BOOL
      var2: UINT

Arrays of structures

Arrays of structures can be defined as:

context:
  fields:
    "struct1[10]":
      var1: BOOL
      var2: UINT

Boxed arrays

By default arrays are defined in stack memory which provides faster access to variables but may lead to stack overflow for large ones.

If an array is specified with “!” symbol after its size:

context:
  fields:
    vars: REAL[4000!]

it is created as boxed (heap-allocated). The same option can be applied for arrays of structures.

Custom types

If custom types are required in the context, they must be placed into plc_types crate module. If found, the module is automatically imported into the context.

context:
  fields:
    timer1: Duration # another way is use the full path: std::time::Duration
    data: MyStruct

main.rs:

mod plc_types;

plc_types.rs:

pub use std::time::Duration; // external data type re-export

#[derive(Default)]
pub(crate) struct MyStruct {
    var1: bool,
    var2: f32
}

Note

All custom types MUST implement the Default trait.

Accessing

As already mentioned, the context is placed under a read-write lock (parking_lot::RwLock). To prevent other threads, including I/O ones, getting stuck, the context should always be unlocked for a minimal period of time, especially if heavy calculations are planned.

With macros

use rplc::prelude::*;

mod plc;

#[plc_program(loop = "500ms")]
fn p1() {
    let mut var1 = {
        let ctx = plc_context!(); // context is read-locked
        ctx.var1
    }; // context is unlocked
    // perform some heavy calculations
    {
        let mut ctx = plc_context_mut!(); // context is read-write-locked
        ctx.var1 = var1;
    } // context is unlocked
}

Directly

The context can be accessed directly as:

use rplc::prelude::*;

mod plc;

use plc::context::CONTEXT;

#[plc_program(loop = "500ms")]
fn p1() {
    let var1 = { // context is read-locked
        let ctx = CONTEXT.read();
        ctx.var1
    }; // context is unlocked
    // ....
}

Serialization

The context structures are created in C-representation (repr(C)) which allows to send them to externally linked C or Structured Text methods as-is.

Additionally, the context can be declared as Serde (de)serializable:

context:
  serialize: true
  fields:
    var1: BOOL
    var2: REAL

After, the context or its part can be e.g. loaded and saved using e.g. MessagePack, JSON or any other data packer:

use std::fs;

fn main() {
    init_plc!();
    if let Ok(data) = fs::read("plc.dat") {
        info!("loading context");
        // pointer dereference is not required if a part is loaded
        *plc_context_mut!() = rmp_serde::from_slice(&data).unwrap();
    }
    run_plc!();
    fs::write(
        "plc.dat",
        // pointer reference-dereference is not required if a part is saved
        rmp_serde::to_vec_named(&*plc_context!()).unwrap(),
    )
    .unwrap();
}

Note

If custom types are used, all of them MUST implement serde::Serialize and serde::Deserialize.