Abstract Factories: OPC-UA HMI Connection
Table of Contents
Introduction
In the example, we use an opc ua interface for the HMI, firstly because it is more difficult than an ADS interface and secondly because it is more flexible because there are many HMI manufacturers that support opc ua. With the opc ua interface, the alarms have to be on the stack to access the resources from outside. Since application developers do create only an alarm object for it, there is a global array in which the active alarms are entered. The global list must also provide an alarm service, since it is a subscriber to the alarm distribution service.
Data Types
We need structured types for our array.
Type Struct AlarmProperties TYPE AlarmProperties :
STRUCT
additionalTextListNumber :DWORD := 0;
alarmMessage :IAlarmMessageDto := 0;
class :HmiAlarmClass := HmiAlarmClass.INFO;
displayType :AlarmDisplayType := AlarmDisplayType.NO_POPUP_WINDOW;
functionUnit :FunctionUnitName := '';
hashcode :Hashcode := 0;
myMessageNumber :DWORD := 0;
info :AlarmMessageInfo := '';
name :ObjectName := '';
state :HmiAlarmState := HmiAlarmState.INACTIVE;
stopCategory :AlarmStopCategory := AlarmStopCategory.DONT_STOP;
END_STRUCT
END_TYPE
|
Type Struct HmiAlarmProperties TYPE HmiAlarmProperties EXTENDS AlarmProperties :
STRUCT
hmiAlarmActivationBit :BOOL := FALSE;
END_STRUCT
END_TYPE
|
Implementation
Class OpcUaHmiAlarmList {attribute 'reflection'}
FUNCTION_BLOCK OpcUaHmiAlarmList EXTENDS Object IMPLEMENTS IAlarmMessageService, IRunable
VAR
{attribute 'OPC.UA.DA' := '1'}
alarmList :ARRAY[HmiAlarmClass.INFO..HmiAlarmClass.ERROR] OF ARRAY[FIRST_ALARM..LAST_ALARM] OF HmiAlarmProperties;
{attribute 'OPC.UA.DA' := '0'}
alarmListShadow :ARRAY[HmiAlarmClass.INFO..HmiAlarmClass.ERROR] OF ARRAY[FIRST_ALARM..LAST_ALARM] OF HmiAlarmProperties;
{attribute 'OPC.UA.DA.Access' := '1'}
numberOfActiveAlarms :ARRAY[HmiAlarmClass.INFO..HmiAlarmClass.ERROR] OF USINT;
END_VAR
VAR CONSTANT
FIRST_ALARM :USINT := 1;
LAST_ALARM :USINT := 255;
ALARM_INCREMENT :USINT := 1;
NO_ALARM_IS_ACTIVE :USINT := 0;
ALARM_WAS_NOT_FOUND :USINT := 0;
END_VAR
|
METHOD PROTECTED activateAlarm :BOOL
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
VAR_IN_OUT
alarm :HmiAlarmProperties;
END_VAR
|
alarm.hmiAlarmActivationBit := TRUE;
THIS^.setAlarm(
alarmMessage := alarmMessage,
alarm := alarm
);
|
GET PROPERTY className :ClassName |
|
className := 'OpcUaHmiAlarmList';
|
METHOD clear
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
|
IF (THIS^.isObjectValid(alarmMessage)) THEN;
THIS^.clearAlarm(
alarmMessage := alarmMessage,
messagePointer := THIS^.findMessage(alarmMessage)
);
THIS^.updateShadow(alarmMessage.class);
END_IF
|
METHOD PROTECTED clearAlarm
VAR_INPUT
alarmMessage :IAlarmMessageDto;
messagePointer :USINT;
END_VAR
VAR
class :HmiAlarmClass;
actualAlarm :USINT;
END_VAR
VAR CONSTANT
EMPTY_ALARM :HmiAlarmProperties := (
hmiAlarmActivationBit := FALSE,
additionalTextListNumber := 0,
alarmMessage := 0,
class := HmiAlarmClass.INFO,
displayType := AlarmDisplayType.NO_POPUP_WINDOW,
functionUnit := '',
hashcode := 0,
myMessageNumber := 0,
info := '',
name := '',
state := HmiAlarmState.INACTIVE,
stopCategory := AlarmStopCategory.DONT_STOP
);
NEXT_ALARM :USINT := 1;
END_VAR
|
IF (messagePointer <> THIS^.ALARM_WAS_NOT_FOUND) THEN
IF (THIS^.isObjectValid(alarmMessage)) THEN
alarmMessage.state := HmiAlarmState.INACTIVE;
class := alarmMessage.class;
END_IF
THIS^.alarmListShadow[class][messagePointer] :=
THIS^.alarmList[class][messagePointer] :=
EMPTY_ALARM;
THIS^.decrementActiveAlarms(class);
RETURN(THIS^.numberOfActiveAlarms[class] < THIS^.FIRST_ALARM);
RETURN(messagePointer > THIS^.numberOfActiveAlarms[class]);
FOR (actualAlarm := messagePointer) TO (THIS^.numberOfActiveAlarms[class]) BY (NEXT_ALARM) DO
THIS^.alarmListShadow[class][actualAlarm] :=
THIS^.alarmList[class][actualAlarm] :=
THIS^.alarmList[class][actualAlarm+NEXT_ALARM];
END_FOR
END_IF
|
METHOD PROTECTED decrementActiveAlarms :USINT
VAR_INPUT
class :HmiAlarmClass;
END_VAR
|
THIS^.numberOfActiveAlarms[class] := (
LIMIT(
THIS^.NO_ALARM_IS_ACTIVE,
THIS^.numberOfActiveAlarms[class] - THIS^.ALARM_INCREMENT,
THIS^.LAST_ALARM
)
);
decrementActiveAlarms := THIS^.numberOfActiveAlarms[class];
|
GET PROPERTY PROTECTED errorWasChangedByHmi :BOOL |
|
errorWasChangedByHmi := THIS^.wasMessageClassChangedByHmi(HmiAlarmClass.ERROR);
|
METHOD PROTECTED findMessage :USINT
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
VAR
message :USINT;
END_VAR
|
findMessage := THIS^.ALARM_WAS_NOT_FOUND;
FOR message := THIS^.FIRST_ALARM TO THIS^.numberOfActiveAlarms[alarmMessage.class] DO
IF (THIS^.alarmList[alarmMessage.class][message].hashcode = alarmMessage.hashcode) THEN
findMessage := message;
EXIT;
END_IF
END_FOR
|
METHOD handle
VAR_INPUT
alarmMessage :IAlarmMessageDto;
END_VAR
|
RETURN(THIS^.isObjectNull(alarmMessage));
RETURN(THIS^.findMessage(alarmMessage) <> THIS^.ALARM_WAS_NOT_FOUND);
IF (alarmMessage.state < HmiAlarmState.PROVIDED_FOR_HMI) THEN
THIS^.activateAlarm(
alarmMessage := alarmMessage,
alarm := THIS^.alarmList[alarmMessage.class][THIS^.incrementActiveAlarms(alarmMessage.class)]
);
THIS^.updateShadow(alarmMessage.class);
END_IF
|
METHOD PROTECTED handleHmiChangeError
|
THIS^.handleHmiChangeForClass(HmiAlarmClass.ERROR);
|
METHOD PROTECTED handleHmiChangeForClass
VAR_INPUT
class :HmiAlarmClass;
END_VAR
VAR
actualAlarm :USINT;
END_VAR
|
FOR actualAlarm := THIS^.FIRST_ALARM TO THIS^.numberOfActiveAlarms[class] DO
IF (THIS^.wasAlarmChanged(class, actualAlarm)) THEN
IF (THIS^.alarmList[class][actualAlarm].state = HmiAlarmState.QUIT_ON_HMI) THEN
THIS^.clearAlarm(
alarmMessage := THIS^.alarmList[class][actualAlarm].alarmMessage,
messagePointer := actualAlarm
);
ELSE
THIS^.alarmList[class][actualAlarm].alarmMessage.state :=
THIS^.alarmListShadow[class][actualAlarm].state :=
THIS^.alarmList[class][actualAlarm].state;
END_IF
END_IF
END_FOR
THIS^.updateShadow(class);
|
METHOD PROTECTED handleHmiChangeInfo
|
THIS^.handleHmiChangeForClass(HmiAlarmClass.INFO);
|
METHOD PROTECTED handleHmiChangeWarning
|
THIS^.handleHmiChangeForClass(HmiAlarmClass.WARNING);
|
METHOD PROTECTED incrementActiveAlarms :USINT
VAR_INPUT
class :HmiAlarmClass;
END_VAR
|
THIS^.numberOfActiveAlarms[class] := (
LIMIT(
THIS^.FIRST_ALARM,
THIS^.numberOfActiveAlarms[class] + THIS^.ALARM_INCREMENT,
THIS^.LAST_ALARM
)
);
incrementActiveAlarms := THIS^.numberOfActiveAlarms[class];
|
GET PROPERTY PROTECTED infoWasChangedByHmi :BOOL |
|
infoWasChangedByHmi := THIS^.wasMessageClassChangedByHmi(HmiAlarmClass.INFO);
|
METHOD run
|
IF (THIS^.errorWasChangedByHmi) THEN
THIS^.handleHmiChangeError();
END_IF
IF (THIS^.warningWasChangedByHmi) THEN
THIS^.handleHmiChangeWarning();
END_IF
IF (THIS^.infoWasChangedByHmi) THEN
THIS^.handleHmiChangeInfo();
END_IF
|
METHOD PROTECTED setAlarm
VAR_INPUT
alarmMessage : IAlarmMessageDto;
END_VAR
VAR_IN_OUT
alarm : HmiAlarmProperties;
END_VAR
|
alarm.additionalTextListNumber := alarmMessage.additionalTextListNumber;
alarm.alarmMessage := alarmMessage;
alarm.displayType := alarmMessage.displayType;
alarm.functionUnit := alarmMessage.functionUnit;
alarm.info := alarmMessage.info;
alarm.myMessageNumber := alarmMessage.messageNumber;
alarm.name := alarmMessage.name;
alarm.stopCategory := alarmMessage.stopCategory;
alarm.class := alarmMessage.class;
alarm.hashcode := alarmMessage.hashcode;
alarm.state := HmiAlarmState.PROVIDED_FOR_HMI;
alarm.alarmMessage.state := HmiAlarmState.PROVIDED_FOR_HMI;
|
METHOD PROTECTED updateShadow
VAR_INPUT
class :HmiAlarmClass;
END_VAR
|
THIS^.alarmListShadow[class] := THIS^.alarmList[class];
|
GET PROPERTY PROTECTED warningWasChangedByHmi :BOOL |
|
warningWasChangedByHmi := THIS^.wasMessageClassChangedByHmi(HmiAlarmClass.WARNING);
|
METHOD PROTECTED wasAlarmChanged :BOOL
VAR_INPUT
class :HmiAlarmClass;
messagePointer :USINT;
END_VAR
|
wasAlarmChanged := (
Tc2_System.MEMCMP(
ADR(THIS^.alarmList[class][messagePointer]),
ADR(THIS^.alarmListShadow[class][messagePointer]),
SIZEOF(THIS^.alarmList[class][messagePointer])
) <> ComparationResult.EQUAL
);
|
METHOD PROTECTED wasMessageClassChangedByHmi :BOOL
VAR_INPUT
class :HmiAlarmClass;
END_VAR
|
wasMessageClassChangedByHmi := (
Tc2_System.MEMCMP(
ADR(THIS^.alarmList[class]),
ADR(THIS^.alarmListShadow[class]),
SIZEOF(THIS^.alarmList[class])
) <> ComparationResult.EQUAL
);
|