Inhalte

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 clear
METHOD setAdditionalTextListNumber :IAlarmMessage
VAR_INPUT
	textListNumber :DWORD;
END_VAR
METHOD throw
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';