Preparing the OEE Simulation
For the OEE Simulation, a sample enterprise needs to be created in the ISA-95 Equipment Model and in the I/O Model sample production data needs to be generated.
Creating the Sample Enerprise in the ISA-95 Equipment Model
Go to the ISA-95 Equipment Model panel, then open a Console. Copy & paste the following script to the console, and run it. This script prepares the demo Enterprise in the ISA-95 Equipment Model, which then should look similar to this:

Alternatively you can create this structure by hand.
local base = "/"
syslib.mass({
{
class = syslib.model.classes.S95EMEnterprise,
operation = syslib.model.codes.MassOp.UPSERT,
path = base .. "The OEE Demo Enterprise",
["ID"] = "The OEE Demo Enterprise",
["Description"] = "The sample enterprise for OEE Simulation",
["Criticality"] = 0
},
{
class = syslib.model.classes.S95EMSite,
operation = syslib.model.codes.MassOp.UPSERT,
path = base .. "The OEE Demo Enterprise/The OEE Demo Site",
["ID"] = "The OEE Demo Site",
["Description"] = "a sample site for OEE simulation",
["Criticality"] = 0
},
{
class = syslib.model.classes.S95EMArea,
operation = syslib.model.codes.MassOp.UPSERT,
path = base .. "The OEE Demo Enterprise/The OEE Demo Site/The OEE Demo Area",
["ID"] = "The OEE Demo Area",
["Description"] = "a sample area for OEE simulation",
["Criticality"] = 0
},
{
class = syslib.model.classes.S95EMProcessCell,
operation = syslib.model.codes.MassOp.UPSERT,
path = base .. "The OEE Demo Enterprise/The OEE Demo Site/The OEE Demo Area/The OEE Demo Cell",
["ID"] = "The OEE Demo Cell",
["Description"] = "the cell monitored for OEE",
["Criticality"] = 0
}
})
OEE Simulation Data Setup
In the I/O Model, create an Action Item object, calling it 'OEE Simulator'.
Copy the source code of the OEE Simulation Script (below) into the Action Item’s Lua Script Body and enable Dedicated Thread Execution property for the Action Item.

When you click Create, the script will generate a number of Variable objects. The script then produces simulation data (which is stored in those variables) for the OEE calculation.

The OEE Simulation Script
-- !! Please put the content of this file to an action item and enable dedicated thread execution !!
-- CONFIGURATION
local Timebasis = 1000 -- ms, Timebasis of the simulation
local MaxCycle = 20 -- Max number of cycles
local Product = "Discrete Product A" -- Product name
local FailureChance = 10 -- %, Chance of a failure to happen
local FailureMaxDuration = 2 -- How many cycles failure state should last
local PerformanceDropChance = 0 -- %, Chance of a performance drop to happen
local LowestPerfromanceDropLimit = 50 -- %, Performance drop will be calcualted as a random value within [LowestPerfromanceDropLimit%, 100%]
local ScrapChance = 20 -- %, Chance of scrap product to happen
local HighestScrapLimit = 50 -- %, Scrap amount will be calcualted as a random value within [0%, HighestScrapLimit%]
-- PRODUCT DATABASE
-- this database has the data related to a specific produc
local products = {
["Discrete Product A"] = {
performance = 10, -- producing 10 units per specified timebasis
failurecode = 1, -- this failure code will be used in case of an error
digits = 0,
batchpattern = "Discrete Product Batch %02d",
},
["Discrete Product B"] = {
performance = 10,
failurecode = 2,
digits = 0,
batchpattern = "Discrete Product [10] Batch %02d",
},
["Discrete Product C"] = {
performance = 20,
failurecode = 3,
digits = 0,
batchpattern = "Discrete Product [20] Batch %02d",
},
["Liquid Product A"] = {
performance = 2.5,
failurecode = 4,
digits = 2,
batchpattern = "Liquid Product Batch %02d",
},
["Liquid Product B"] = {
performance = 2.5,
failurecode = 5,
digits = 2,
batchpattern = "Liquid Product [2.5] Batch %02d",
},
["Liquid Product C"] = {
performance = 3.5,
failurecode = 6,
digits = 2,
batchpattern = "Liquid Product [3.5] Batch %02d",
}
}
-- HELPER FUNCTIONS
local var = require"esi-variables" -- helper module to deal with variables creatings and writing values into those
local round = function(num, dec) -- helper funciton math.round
local mult = 10 ^ (dec or 0)
return math.floor(num * mult + 0.5) / mult
end
-- SIMULATION
-- this data will hekp us to determine the current state of the simulation,
-- and to understand if we need to continue the simulation, or stop it
local self = syslib.getself() -- get self object
local cfg_ver = self:cfgversion() -- get current cfg version of self
-- get some data from the product database
local perf = ((products or {})[Product] or {}).performance or 1 -- default performance
local failcode = ((products or {})[Product] or {}).failurecode or 1 -- failure code to use in case of a failure
local digits = ((products or {})[Product] or {}).digits or 0 -- for the math.round function
local pattern = ((products or {})[Product] or {}).batchpattern or "Batch %s" -- specific pattern for the batch id
-- helper variables which holds current state of the simulation
local cycle = -2 -- cycle counter, a single cycle is equal to the timebasis
local failcycle = 0 -- fail cycles counter, this one to make the length of failures random
local batchcount = 0 -- the number of the current batch
local currincr = 0 -- internal quantity related to the current cycle
local quantity = 0 -- total quantity output (total), incrementing
local scrap = 0 -- total scrap output (total), incrementin
local eqstate = syslib.model.codes.EquipmentStates.ABORTED -- actual equipment state
local errcode = nil -- actual error coed
-- check if we should continue the simulation
while self:enabled() and cfg_ver == self:cfgversion() do
-- set some output to the action item
syslib.setvalue(self, ("[%d] Simulation is running..."):format(cycle))
-- cycle < 0 indicates that simulation is being resetting.
if cycle < 0 then
-- reset all important variables
batchcount = 0
quantity = 0
scrap = 0
-- set state to ABORTED (so equipment monitor can catch it as a 'reset event')
eqstate = syslib.model.codes.EquipmentStates.ABORTED
end
-- cycle 0 means we have started a new batch
if cycle == 0 then
-- increasing the batch counter
batchcount = batchcount + 1
-- setting quantity and scrap to 0
quantity = 0
scrap = 0
-- equipment state STARTING, so equipment monitor can catch the 'start event'
eqstate = syslib.model.codes.EquipmentStates.STARTING
end
-- until we reached our max cycle value, we do simulation
if cycle >= 1 and cycle <= MaxCycle then
-- check if we need to go into a failure route
if failcycle == 0 and math.random(100) > FailureChance then -- NORMAL ROUTE
-- running state
eqstate = syslib.model.codes.EquipmentStates.EXECUTE_RUNNING
-- emulate performance degradation with some chance
if math.random(100) > PerformanceDropChance then
-- no degradation in performance, increment is our default performance
currincr = perf
else
-- degradation took place, increment is less than our default performance
currincr = round(perf * math.random(LowestPerfromanceDropLimit, 100) / 100, digits)
end
-- adding incremented value to the total quantity
quantity = quantity + currincr
-- emulate the chance of scrap output
if not (math.random(100) > ScrapChance) then
scrap = scrap + round(currincr * math.random(0, HighestScrapLimit) / 100, digits)
end
-- error is nil, we are in the normal route
errcode = nil
else -- FAILURE ROUTE
-- if we stepped in by the failure chance condition, we should decide how long failure should last
if failcycle == 0 then failcycle = math.random(1, FailureMaxDuration) end
-- equipment state is STOPPED
eqstate = syslib.model.codes.EquipmentStates.STOPPED
-- producing a failure event
syslib.setevent({ Message = "An Error Occured" })
-- use our reserved vailure code
errcode = failcode
-- fail cycle count is decreasing, when it reaches 0 we will go back to normal operation
failcycle = failcycle - 1
end
end
-- when we reached max cycle value, we need to indicate the end of the current batch
if cycle == MaxCycle then
-- cycle to -1, so it will be set to 0 later
cycle = -1
-- resolve error to safely close the current batch
errcode = nil
failcycle = 0
-- state COMPLETE, so equipment monitor can catch the 'end event'
eqstate = syslib.model.codes.EquipmentStates.COMPLETE
end
-- create variable (if they are not yet created) and write the current values to those.
-- this all will be carried by esi-variable library, hist = true - means enable archiving
var:SET({path = "Product", v = Product, hist = true})
var:SET({path = "Batch ID", v = (pattern):format(batchcount), hist = true})
var:SET({path = "QuantityTotal", v = quantity, hist = true})
var:SET({path = "QuantityGood", v = quantity - scrap, hist = true})
var:SET({path = "QuantityBad", v = scrap, hist = true})
var:SET({path = "Error Code", v = errcode, hist = true})
var:SET({path = "Equipment State", v = eqstate, hist = true})
-- incrementing the cycle counter variable
cycle = cycle + 1
-- some delay to reflect the timebasis
-- this logic had been slightly improved to make the simulation aborting process more flexible.
local now = syslib.now()
-- untill this time we should delay our code
local untill = now - now % 1000 + Timebasis -- some simple time syncronization
local singlewait = 100
while syslib.now() < untill do
now = syslib.now()
if not (self:enabled() and cfg_ver == self:cfgversion()) then break end
if untill - now < singlewait then singlewait = untill - now end
if singlewait < 10 then break end
syslib.sleep(singlewait)
end
end
Configuration Options
The Configuration section of the script has a number of values which affect the simulated production and which can be changed. Product defines for which entry of the Product Database (see below) the OEE indices will be calculated.
The Product Database section of the script provides a number of sample products. Feel free to add new products, following the example of the existing entries.