///|
pub enum AlarmState {
NormalAlarm
Acknowledged
Shelved
ActiveAlarm
} derive(Debug, Eq)
///|
pub struct Alarm {
code : String
tag : String
value : Float
low : Float
high : Float
state : AlarmState
message : String
} derive(Debug, Eq)
///|
pub fn alarm(
code : String,
tag : String,
value : Float,
low : Float,
high : Float,
message : String,
) -> Alarm {
{
code,
tag,
value,
low,
high,
state: if value < low || value > high {
ActiveAlarm
} else {
NormalAlarm
},
message,
}
}
///|
pub fn Alarm::is_active(self : Alarm) -> Bool {
self.state is ActiveAlarm
}
///|
pub fn Alarm::is_normal(self : Alarm) -> Bool {
self.state is NormalAlarm
}
///|
pub fn Alarm::severity(self : Alarm) -> String {
if self.value < self.low || self.value > self.high {
"high"
} else {
"normal"
}
}
///|
pub fn Alarm::acknowledge(self : Alarm) -> Alarm {
if self.is_active() {
{ ..self, state: Acknowledged }
} else {
self
}
}
///|
pub fn Alarm::shelve(self : Alarm) -> Alarm {
{ ..self, state: Shelved }
}
///|
pub struct AlarmBoard {
alarms : Array[Alarm]
updated_at : String
} derive(Debug, Eq)
///|
pub fn alarm_board(alarms : Array[Alarm], updated_at : String) -> AlarmBoard {
{ alarms, updated_at }
}
///|
pub fn AlarmBoard::count(self : AlarmBoard) -> Int {
self.alarms.length()
}
///|
pub fn AlarmBoard::active_count(self : AlarmBoard) -> Int {
let mut count = 0
for alarm in self.alarms {
if alarm.is_active() {
count = count + 1
}
}
count
}
///|
pub fn AlarmBoard::normal_count(self : AlarmBoard) -> Int {
self.count() - self.active_count()
}
///|
pub fn AlarmBoard::acknowledge_all(self : AlarmBoard) -> AlarmBoard {
{ ..self, alarms: self.alarms.map(fn(alarm) { alarm.acknowledge() }) }
}
///|
pub fn AlarmBoard::shelve_code(self : AlarmBoard, code : String) -> AlarmBoard {
{
..self,
alarms: self.alarms.map(fn(alarm) {
if alarm.code == code {
alarm.shelve()
} else {
alarm
}
}),
}
}
///|
pub fn AlarmBoard::table(self : AlarmBoard) -> ReportTable {
let rows : Array[Array[String]] = []
for alarm in self.alarms {
let state = match alarm.state {
NormalAlarm => "normal"
Acknowledged => "acknowledged"
Shelved => "shelved"
ActiveAlarm => "active"
}
rows.push([alarm.code, alarm.tag, "\{alarm.value}", state, alarm.message])
}
table(["code", "tag", "value", "state", "message"], rows)
}
///|
pub enum ControllerMode {
Manual
Automatic
Cascade
Failed
} derive(Debug, Eq)
///|
pub fn manual_mode() -> ControllerMode {
Manual
}
///|
pub fn automatic_mode() -> ControllerMode {
Automatic
}
///|
pub fn cascade_mode() -> ControllerMode {
Cascade
}
///|
pub fn failed_mode() -> ControllerMode {
Failed
}
///|
pub struct Controller {
tag : String
setpoint : Float
process_value : Float
output : Float
gain : Float
integral : Float
mode : ControllerMode
} derive(Debug, Eq)
///|
pub fn controller(
tag : String,
setpoint : Float,
process_value : Float,
output : Float,
gain : Float,
integral : Float,
mode : ControllerMode,
) -> Controller {
{ tag, setpoint, process_value, output, gain, integral, mode }
}
///|
pub fn Controller::error(self : Controller) -> Float {
self.setpoint - self.process_value
}
///|
pub fn Controller::next_output(
self : Controller,
step : Float,
minimum : Float,
maximum : Float,
) -> Float {
clamp(
self.output +
self.gain * self.error() * step +
self.integral * self.error() * step,
minimum,
maximum,
)
}
///|
pub fn Controller::update(
self : Controller,
process_value : Float,
step : Float,
minimum : Float,
maximum : Float,
) -> Controller {
let next = { ..self, process_value, }
{ ..next, output: next.next_output(step, minimum, maximum) }
}
///|
pub fn Controller::is_automatic(self : Controller) -> Bool {
self.mode is Automatic || self.mode is Cascade
}
///|
pub fn Controller::is_healthy(self : Controller) -> Bool {
self.mode != Failed
}
///|
pub fn controller_error(controller : Controller) -> Float {
controller.error()
}
///|
pub struct ControlLoop {
controllers : Array[Controller]
alarms : AlarmBoard
} derive(Debug, Eq)
///|
pub fn control_loop(
controllers : Array[Controller],
alarms : AlarmBoard,
) -> ControlLoop {
{ controllers, alarms }
}
///|
pub fn ControlLoop::controller_count(self : ControlLoop) -> Int {
self.controllers.length()
}
///|
pub fn ControlLoop::healthy_count(self : ControlLoop) -> Int {
let mut count = 0
for controller in self.controllers {
if controller.is_healthy() {
count = count + 1
}
}
count
}
///|
pub fn ControlLoop::update(
self : ControlLoop,
values : Array[Float],
step : Float,
minimum : Float,
maximum : Float,
) -> ControlLoop {
let controllers = self.controllers.copy()
for index, controller in controllers {
if index < values.length() {
controllers[index] = controller.update(
values[index],
step,
minimum,
maximum,
)
}
}
{ ..self, controllers, }
}
///|
pub fn ControlLoop::table(self : ControlLoop) -> ReportTable {
let rows : Array[Array[String]] = []
for controller in self.controllers {
let mode = match controller.mode {
Manual => "manual"
Automatic => "automatic"
Cascade => "cascade"
Failed => "failed"
}
rows.push([
controller.tag,
"\{controller.setpoint}",
"\{controller.process_value}",
"\{controller.output}",
mode,
])
}
table(["tag", "setpoint", "process", "output", "mode"], rows)
}
///|
pub fn control_quality_gate(
control : ControlLoop,
maximum_active_alarms : Int,
) -> Bool {
control.healthy_count() == control.controller_count() &&
control.alarms.active_count() <= maximum_active_alarms
}
///|
pub struct MaintenanceTask {
asset : String
task : String
interval_hours : Float
operating_hours : Float
critical : Bool
} derive(Debug, Eq)
///|
pub fn maintenance_task(
asset : String,
task : String,
interval_hours : Float,
operating_hours : Float,
critical : Bool,
) -> MaintenanceTask {
{ asset, task, interval_hours, operating_hours, critical }
}
///|
pub fn MaintenanceTask::due(self : MaintenanceTask) -> Bool {
self.interval_hours <= 0.0 || self.operating_hours >= self.interval_hours
}
///|
pub fn MaintenanceTask::remaining(self : MaintenanceTask) -> Float {
self.interval_hours - self.operating_hours
}
///|
pub fn MaintenanceTask::risk(self : MaintenanceTask) -> String {
if self.due() && self.critical {
"critical"
} else if self.due() {
"due"
} else {
"planned"
}
}
///|
pub fn maintenance_table(tasks : Array[MaintenanceTask]) -> ReportTable {
let rows : Array[Array[String]] = []
for task in tasks {
rows.push([
task.asset,
task.task,
"{task.interval_hours}",
"{task.operating_hours}",
task.risk(),
])
}
table(["asset", "task", "interval", "operating hours", "risk"], rows)
}
///|
pub fn maintenance_gate(tasks : Array[MaintenanceTask]) -> Bool {
for task in tasks {
if task.due() && task.critical {
return false
}
}
true
}