Abstract Factories: Alarm Messages
Table of Contents
Introduction
Now, then, let us describe a representation of an alarm in automation technology that is as generally valid as possible. Our alarm should have the following:
A message number to be able to localize the alarm, both in the software for maintenance, and the localization of the language on the HMI. This will be the property messageNumber
.
A message class so that the HMI knows if it is a notification, a warning, or an error, this will be the class
property.
Information either for logging purposes, or if a text was forgotten in the HMI for an error number, then the HMI can at least use it, this will be the info
property.
A state of the alarm that shows, this is the state
property
whether it is inactive
registered for treatment
has been made available to the HMI for display
whether the HMI is displaying the alarm
whether it has been acknowledged by an operator
whether it has been quit by an operator
A stop category that tells the machine how to behave in case of an active alarm this is the property stopCategory
.
We introduce the following stop categories:
The machine should not stop
The machine should pause
The machine should finish the cycle and then stop.
The machine should stop immediately
The machine should stop immediately and be disabled
A function unit to which the alarm belongs, this is the property functionUnit
.
A possibility to check if the message is pending, this is the property isPending
.
An additional text list number for e.g. drive errors, then the HMI can convert the error code directly into a meaningful message. This is the additionalTextListNumber
property.
A display type that tells the HMI if the alarm should be displayed only on the display window or if it should be displayed as a popup window. This is the displayType
property.
An assert
method to check boolean expressions.
A throw
method to activate the alarm.
A clear
method to clear the alarm.
A service which takes over the handling of the errors. The service should have the methods handle
and clear
.
Data Types
Okay, let's start with the types again.
Alias FunctionUnitName TYPE
FunctionUnitName :Tc2_System.T_MaxString;
END_TYPE
|
Alias AlarmMessageInfo TYPE
AlarmMessageInfo :Tc2_System.T_MaxString;
END_TYPE
|
Enumeration HmiAlarmClass {attribute 'qualified_only'}
{attribute 'strict'}
TYPE HmiAlarmClass :(
INFO := 0,
WARNING := 1,
ERROR := 2
)USINT;
END_TYPE
|
Enumeration HmiAlarmState {attribute 'qualified_only'}
{attribute 'strict'}
TYPE HmiAlarmState :(
INACTIVE := 0,
REGISTRED := 1,
PROVIDED_FOR_HMI := 2,
ACTIVE_ON_HMI := 3,
ACKNOWLEDGED_ON_HMI := 4,
QUIT_ON_HMI := 5
) USINT;
END_TYPE
|
Enumeration AlarmStopCategory {attribute 'qualified_only'}
{attribute 'strict'}
TYPE AlarmStopCategory :(
DONT_STOP := 0,
PAUSE := 1,
STOP_AT_END_OF_CYCLE := 2,
STOP_IMMEDIATLY := 3,
DISABLE_MACHINE := 4
) USINT;
END_TYPE
|
Enumerations AlarmDisplayType {attribute 'qualified_only'}
{attribute 'strict'}
TYPE AlarmDisplayType :(
NO_POPUP_WINDOW := 0,
POPUP_WINDOW := 1
) USINT;
END_TYPE
|
Interfaces
Groundhog day surprise surprise interfaces ¯\_(ツ)_/¯. We divide the alarm message to an alarm message interface for the application, a data transfer object interface and a third interface to assign the alarm service. We add a visitor to the dto maybe we need it later.
Interface IAlarmMessageDto INTERFACE IAlarmMessageDto EXTENDS IObject
|
PROPERTY additionalTextListNumber :DWORD
GET
|
PROPERTY class :HmiAlarmClass
GET
|
PROPERTY displayType :AlarmDisplayType
GET
|
PROPERTY functionUnit :FunctionUnitName
GET
|
PROPERTY info :AlarmMessageInfo
GET
|
PROPERTY messageNumber :DWORD
GET
|
PROPERTY state :HmiAlarmState
GET
SET
|
PROPERTY stopCategory :AlarmStopCategory
GET
|
METHOD accept
VAR_INPUT
visitor :IAlarmVisitor;
END_VAR
|
Interface IAlarmVisitor INTERFACE IAlarmVisitor EXTENDS IObject
|
METHOD visitAlarmServiceLinker
VAR_INPUT
alarmMessageDto :IAlarmMessageDto;
END_VAR
|
METHOD visitErrorMessage
VAR_INPUT
alarmMessageDto :IAlarmMessageDto;
END_VAR
|
METHOD visitInfoMessage
VAR_INPUT
alarmMessageDto :IAlarmMessageDto;
END_VAR
|
METHOD visitWarnMessage
VAR_INPUT
alarmMessageDto :IAlarmMessageDto;
END_VAR
|
Interface IAlarmMessage INTERFACE IAlarmMessage EXTENDS IObject
|
METHOD assert
VAR_INPUT
condition :BOOL;
END_VAR
|
METHOD setAdditionalTextListNumber :IAlarmMessage
VAR_INPUT
textListNumber :DWORD;
END_VAR
|
PROPERTY isPending :BOOL
GET
|
PROPERTY dto :IAlarmMessageDto
GET
|
Interface IAlarmMessageService INTERFACE IAlarmMessageService EXTENDS IObject
|
METHOD clear
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
|
METHOD handle
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
|
Interface IAlarmMessageServiceConsumer INTERFACE IAlarmMessageServiceConsumer EXTENDS IObject
|
METHOD assign
VAR_INPUT
service :IAlarmMessageService;
END_VAR
|
Implementation
To save programming time, we first implement an abstract alarm message class. We also make sure that the changeable properties are multitasking safe, for this we use the TC2_System.FB_IecCriticalSection lock object.
Abstract Class AbstractAlarmMessage {attribute 'reflection'}
FUNCTION_BLOCK ABSTRACT AbstarctAlarmMessage EXTENDS Object IMPLEMENTS IAlarmMessage, IAlarmMessageDto
VAR_STAT
alarmService :IAlarmMessageService;
END_VAR
VAR
textListNumber :DWORD;
alarmClass :HmiAlarmClass;
myDisplayType :AlarmDisplayType;
functionUnitName :FunctionUnitName;
myMessageNumber :DWORD;
messageInfo :AlarmMessageInfo;
myStopCategory :AlarmStopCategory;
lockObjectForState :TC2_System.FB_IecCriticalSection;
lockObjectForAdditionalNumber :TC2_System.FB_IecCriticalSection;
alarmState :HmiAlarmState := HmiAlarmState.INACTIVE;
END_VAR
|
METHOD FB_init :BOOL
VAR_INPUT
(* if TRUE, the retain variables are initialized (warm start / cold start) *)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change) *)
bInCopyCode :BOOL;
functionUnitName :FunctionUnitName;
messageNumber :DWORD;
messageInfo :AlarmMessageInfo;
END_VAR
VAR
{attribute 'hide'}
newHashstate3 :Hashcode;
END_VAR
VAR CONSTANT
{attribute 'hide'}
NUMBER_OF_LEFT_SHIFTS :UINT := 17;
{attribute 'hide'}
NUMBER_OF_LEFT_ROTATIONS :UINT := 45;
END_VAR
|
THIS^.functionUnitName := functionUnitName;
THIS^.myMessageNumber := messageNumber;
THIS^.messageInfo := messageInfo;
|
METHOD ABSTRACT accept
VAR_INPUT
visitor : IAlarmVisitor;
END_VAR
|
GET PROPERTY additionalTextListNumber :DWORD |
|
THIS^.lockObjectForAdditionalNumber.Enter();
additionalTextListNumber := THIS^.textListNumber;
THIS^.lockObjectForAdditionalNumber.Leave();
|
GET PROPERTY PROTECTED alarmServiceIsValid :BOOL |
|
alarmServiceIsValid := THIS^.isObjectValid(THIS^.alarmService);
|
METHOD assert
VAR_INPUT
condition :BOOL;
END_VAR
|
IF (NOT condition) THEN
THIS^.throw();
END_IF
|
GET PROPERTY class :HmiAlarmClass |
|
class := THIS^.alarmClass;
|
METHOD clear
|
IF (THIS^.alarmServiceIsValid) THEN
THIS^.alarmService.clear(THIS^);
END_IF
|
GET PROPERTY displayType :AlarmDisplayType |
|
displayType := THIS^.myDisplayType;
|
GET PROPERTY dto :IAlarmMessageDto |
|
dto := THIS^;
|
GET PROPERTY functionUnit :FunctionUnitName |
|
functionUnit := THIS^.functionUnitName;
|
GET PROPERTY info :AlarmMessageInfo |
|
info := THIS^.messageInfo;
|
GET PROPERTY isPending :BOOL |
|
isPending := (THIS^.state > HmiAlarmState.INACTIVE);
|
GET PROPERTY messageNumber :DWORD |
|
messageNumber := THIS^.myMessageNumber;
|
METHOD setAdditionalTextListNumber :IAlarmMessage
VAR_INPUT
textListNumber :DWORD;
END_VAR
|
THIS^.lockObjectForAdditionalNumber.Enter();
THIS^.textListNumber := textListNumber;
THIS^.lockObjectForAdditionalNumber.Leave();
setAdditionalTextListNumber := THIS^;
|
GET PROPERTY state :HmiAlarmState |
THIS^.lockObjectForState.Enter();
state := THIS^.alarmState;
THIS^.lockObjectForState.Leave();
|
SET PROPERTY state :HmiAlarmState |
THIS^.lockObjectForState.Enter();
THIS^.alarmState := state;
THIS^.lockObjectForState.Leave();
|
GET PROPERTY stopCategory :AlarmStopCategory |
|
stopCategory := THIS^.myStopCategory;
|
METHOD throw
|
IF (THIS^.alarmServiceIsValid) THEN
THIS^.alarmService.handle(THIS^);
END_IF
|
Next, the concrete message classes are implemented.
Class InfoMessage {attribute 'enable_dynamic_creation'}
{attribute 'reflection'}
FUNCTION_BLOCK InfoMessage EXTENDS AbstarctAlarmMessage
|
METHOD FB_init :BOOL
VAR_INPUT
(* if TRUE, the retain variables are initialized (warm start / cold start) *)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change) *)
bInCopyCode :BOOL;
functionUnitName :FunctionUnitName;
messageNumber :DWORD;
messageInfo :AlarmMessageInfo;
END_VAR
VAR
{attribute 'hide'}
newHashstate3 :Hashcode;
END_VAR
VAR CONSTANT
{attribute 'hide'}
NUMBER_OF_LEFT_SHIFTS :UINT := 17;
{attribute 'hide'}
NUMBER_OF_LEFT_ROTATIONS :UINT := 45;
END_VAR
|
THIS^.myDisplayType := AlarmDisplayType.NO_POPUP_WINDOW;
THIS^.myStopCategory := AlarmStopCategory.DONT_STOP;
THIS^.alarmClass := HmiAlarmClass.INFO;
|
METHOD accept
VAR_INPUT
visitor : IAlarmVisitor;
END_VAR
|
visitor.visitInfoMessage(THIS^);
|
GET PROPERTY className :ClassName |
|
className := 'InfoMessage';
|
Class WarnMessage {attribute 'enable_dynamic_creation'}
{attribute 'reflection'}
FUNCTION_BLOCK WarnMessage EXTENDS AbstarctAlarmMessage
|
METHOD FB_init :BOOL
VAR_INPUT
(* if TRUE, the retain variables are initialized (warm start / cold start) *)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change) *)
bInCopyCode :BOOL;
functionUnitName :FunctionUnitName;
messageNumber :DWORD;
messageInfo :AlarmMessageInfo;
END_VAR
VAR
{attribute 'hide'}
newHashstate3 :Hashcode;
END_VAR
VAR CONSTANT
{attribute 'hide'}
NUMBER_OF_LEFT_SHIFTS :UINT := 17;
{attribute 'hide'}
NUMBER_OF_LEFT_ROTATIONS :UINT := 45;
END_VAR
|
THIS^.myDisplayType := AlarmDisplayType.NO_POPUP_WINDOW;
THIS^.myStopCategory := AlarmStopCategory.DONT_STOP;
THIS^.alarmClass := HmiAlarmClass.WARNING;
|
METHOD accept
VAR_INPUT
visitor : IAlarmVisitor;
END_VAR
|
visitor.visitWarnMessage(THIS^);
|
GET PROPERTY className :ClassName |
|
className := 'WarnMessage';
|
Error messages class {attribute 'enable_dynamic_creation'}
{attribute 'reflection'}
FUNCTION_BLOCK ErrorMessage EXTENDS AbstarctAlarmMessage
|
METHOD FB_init :BOOL
VAR_INPUT
(* if TRUE, the retain variables are initialized (warm start / cold start) *)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change) *)
bInCopyCode :BOOL;
functionUnitName :FunctionUnitName;
messageNumber :DWORD;
messageInfo :AlarmMessageInfo;
stopCategory :AlarmStopCategory;
END_VAR
|
THIS^.myDisplayType := SEL(
(stopCategory >= AlarmStopCategory.STOP_IMMEDIATLY),
AlarmDisplayType.NO_POPUP_WINDOW,
AlarmDisplayType.POPUP_WINDOW
);
THIS^.myStopCategory := stopCategory;
THIS^.alarmClass := HmiAlarmClass.ERROR;
|
METHOD accept
VAR_INPUT
visitor :IAlarmVisitor;
END_VAR
|
visitor.visitErrorMessage(THIS^);
|
GET PROPERTY className :ClassName |
|
className := 'ErrorMessage';
|
There will still be a pseudo message class to link the message service and make sure it still works after an online change.
Class AlarmServiceLinker {attribute 'reflection'}
FUNCTION_BLOCK AlarmServiceLinker EXTENDS AbstarctAlarmMessage IMPLEMENTS IAlarmMessageServiceConsumer
|
METHOD FB_init :BOOL
VAR_INPUT
(* if TRUE, the retain variables are initialized (warm start / cold start) *)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change) *)
bInCopyCode :BOOL := TRUE;
functionUnitName :FunctionUnitName;
messageNumber :DWORD;
messageInfo :AlarmMessageInfo;
alarmService :IAlarmMessageService;
END_VAR
|
THIS^.assign(alarmService);
|
METHOD accept
VAR_INPUT
visitor :IAlarmVisitor;
END_VAR
|
visitor.visitAlarmServiceLinker(THIS^);
|
METHOD assign
VAR_INPUT
service :IAlarmMessageService;
END_VAR
|
THIS^.alarmService := service;
|
GET PROPERTY className :ClassName |
|
className := 'AlarmServiceLinker';
|