반응형
[Java] OPC UA Server 코드(Prosys SDK기반)
위는 OPC UA 서버에 접속했을 때의 화면이다.
createObjectsFolder() 메소드를 통해 Objects 밑에 MyObjects라는 오브젝트 기반 폴더를 생성했다.
현재 서버는 opc.tcp://localhost:50500이라는 URL로 동작하고 있고, 서버에 접속하기 위해선 opc.tcp://localhost:50500/OPCUA/OPCUAServer의 URL을 따라가면 접속할 수 있다.
이 서버 코드는 Prosys의 OPC UA Java SDK를 이용해서 작성했으며, prosys의 sdk가 없으면 동작하지 않는다.
하지만 기본적으로 OPC UA가 서버 구동되는 흐름은 동일하기 때문에 sdk가 없어도 참고하는데는 무관하다고 생각한다.
위의 콘솔 화면에서 제시한 대로 명령어를 입력하면 Enum-Action에서 지정한 메소드들이 동작하게 된다.
서버를 구축하는데 가장 중요한 메소드는 initialize()와 initBuildInfo(), createAddressSpace()와 run() 총 4개의 함수이다.
먼저 서버에 필요한 설정들을 초기화하고, 서버 구조를 초기화하고, AddressSpace를 생성하고, 그다음에 서버를 동작시킨다는 내용이다.
코드의 양이 꽤나 길지만 4개의 함수를 중점적으로 인지하고 코드를 파악하면 OPC UA서버가 구동되는데 필요한 메소드와 코드가 무엇인지 알 수 있을 거라고 생각한다.
1. 메인 함수
package com.power21.opcua.home;
import java.io.IOException;
import java.net.URISyntaxException;
import org.apache.log4j.PropertyConfigurator;
import org.opcfoundation.ua.common.ServiceResultException;
import com.prosysopc.ua.SecureIdentityException;
import com.prosysopc.ua.StatusException;
import com.prosysopc.ua.server.UaServerException;
public class Home {
public static void main(String[] args) throws IOException, SecureIdentityException, URISyntaxException,
ServiceResultException, StatusException, UaServerException {
// Initialize log4j logging
PropertyConfigurator.configureAndWatch(OPCUAServer.class.getResource("log.properties").getFile(), 5000);
// *** Initialization and Start Up
OPCUAServer sampleConsoleServer = new OPCUAServer();
// Initialize the server
sampleConsoleServer.initialize();
// Create the address space
sampleConsoleServer.createAddressSpace();
// TCP Buffer size parameters - this may help with high traffic
// situations.
// See http://fasterdata.es.net/host-tuning/background/ for some hints
// how to use it
// UATcpServer.setReceiveBufferSize(700000);
// Start the server, when you have finished your own initializations
// This will allow connections from the clients
// Start up the server (enabling or disabling diagnostics according to
// the cmd line args)
sampleConsoleServer.run(getUseDiags(args));
}
/**
* Check if diagnostics is enabled from the command line
*
* @param args
* @return
*/
protected static boolean getUseDiags(String[] args) {
for (String arg : args)
if (arg.equals("-enablesessiondiags"))
return true;
return false;
}
}
2. Server 함수
package com.power21.opcua.home;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.CertificateParsingException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.opcfoundation.ua.builtintypes.DataValue;
import org.opcfoundation.ua.builtintypes.DateTime;
import org.opcfoundation.ua.builtintypes.ExpandedNodeId;
import org.opcfoundation.ua.builtintypes.LocalizedText;
import org.opcfoundation.ua.builtintypes.NodeId;
import org.opcfoundation.ua.builtintypes.QualifiedName;
import org.opcfoundation.ua.builtintypes.UnsignedInteger;
import org.opcfoundation.ua.core.AccessLevel;
import org.opcfoundation.ua.core.AggregateFilterResult;
import org.opcfoundation.ua.core.ApplicationDescription;
import org.opcfoundation.ua.core.EventFilter;
import org.opcfoundation.ua.core.EventFilterResult;
import org.opcfoundation.ua.core.Identifiers;
import org.opcfoundation.ua.core.MonitoringFilter;
import org.opcfoundation.ua.core.MonitoringParameters;
import org.opcfoundation.ua.core.NodeAttributes;
import org.opcfoundation.ua.core.NodeClass;
import org.opcfoundation.ua.core.StatusCodes;
import org.opcfoundation.ua.core.TimestampsToReturn;
import org.opcfoundation.ua.core.UserTokenPolicy;
import org.opcfoundation.ua.core.UserTokenType;
import org.opcfoundation.ua.core.ViewDescription;
import org.opcfoundation.ua.transport.security.Cert;
import org.opcfoundation.ua.transport.security.SecurityMode;
import org.opcfoundation.ua.utils.NumericRange;
import com.prosysopc.ua.ApplicationIdentity;
import com.prosysopc.ua.CertificateValidationListener;
import com.prosysopc.ua.PkiFileBasedCertificateValidator;
import com.prosysopc.ua.PkiFileBasedCertificateValidator.CertificateCheck;
import com.prosysopc.ua.PkiFileBasedCertificateValidator.ValidationResult;
import com.prosysopc.ua.SecureIdentityException;
import com.prosysopc.ua.StatusException;
import com.prosysopc.ua.WriteAccess;
import com.prosysopc.ua.nodes.UaMethod;
import com.prosysopc.ua.nodes.UaNode;
import com.prosysopc.ua.nodes.UaNodeFactoryException;
import com.prosysopc.ua.nodes.UaObject;
import com.prosysopc.ua.nodes.UaObjectType;
import com.prosysopc.ua.nodes.UaReference;
import com.prosysopc.ua.nodes.UaReferenceType;
import com.prosysopc.ua.nodes.UaType;
import com.prosysopc.ua.nodes.UaVariable;
import com.prosysopc.ua.server.EventManagerListener;
import com.prosysopc.ua.server.IoManagerListener;
import com.prosysopc.ua.server.MonitoredDataItem;
import com.prosysopc.ua.server.MonitoredEventItem;
import com.prosysopc.ua.server.NodeManagerListener;
import com.prosysopc.ua.server.NodeManagerUaNode;
import com.prosysopc.ua.server.ServiceContext;
import com.prosysopc.ua.server.Subscription;
import com.prosysopc.ua.server.UaServer;
import com.prosysopc.ua.server.UaServerException;
import com.prosysopc.ua.server.nodes.CacheVariable;
import com.prosysopc.ua.server.nodes.PlainProperty;
import com.prosysopc.ua.server.nodes.UaObjectTypeNode;
import com.prosysopc.ua.server.nodes.UaVariableNode;
import com.prosysopc.ua.server.nodes.opcua.AcknowledgeableConditionType;
import com.prosysopc.ua.server.nodes.opcua.AlarmConditionType;
import com.prosysopc.ua.server.nodes.opcua.BaseEventType;
import com.prosysopc.ua.server.nodes.opcua.BuildInfoType;
import com.prosysopc.ua.server.nodes.opcua.ConditionType;
import com.prosysopc.ua.server.nodes.opcua.FolderType;
import com.prosysopc.ua.server.nodes.opcua.ShelvedStateMachineType;
public class OPCUAServer {
private static Logger logger = Logger.getLogger(OPCUAServer.class);
private static final String APP_NAME = "OPCUAServer";
protected UaServer server;
private FolderType myObjectsFolder;
private int eventId = 0;
private UaVariableNode myLevel;
enum Action {
ADD_NODE("add a new node"), CLOSE("close the server"), DELETE_NODE("delete a node"),
ENABLE_DIAGNOSTICS("enable/disable server diagnostics"), SEND_EVENT("send an event");
static Map<String, Action> actionMap = new HashMap<String, Action>();
static {
actionMap.put("a", ADD_NODE);
actionMap.put("d", DELETE_NODE);
actionMap.put("D", ENABLE_DIAGNOSTICS);
actionMap.put("e", SEND_EVENT);
actionMap.put("x", CLOSE);
}
public static Action parseAction(String s) {
return actionMap.get(s);
}
private final String description;
Action(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// Server가 동작하면서 입력값으로 테스트를 하기 위해 필요한 변수들
private final ScheduledExecutorService simulator = Executors.newScheduledThreadPool(10);
private static boolean stackTraceOnException = true;
private final Runnable simulationTask = new Runnable() {
double dx = 1;
@Override
public void run() {
if (server.isRunning()) {
final DataValue v = myLevel.getValue();
Double nextValue = v.isNull() ? 0 : v.getValue().doubleValue() + dx;
if (nextValue <= 0)
dx = 1;
else if (nextValue >= 100)
dx = -1;
try {
((CacheVariable) myLevel).updateValue(nextValue);
// if (nextValue > myAlarm.getHighHighLimit())
// activateAlarm(700);
// else if (nextValue > myAlarm.getHighLimit())
// activateAlarm(500);
// else if (nextValue < myAlarm.getLowLowLimit())
// activateAlarm(700);
// else if (nextValue < myAlarm.getLowLimit())
// activateAlarm(500);
// else
// inactivateAlarm();
} catch (Exception e) {
printException(e);
throw new RuntimeException(e); // End the task
}
}
// myBigNodeManager.simulate();
// myCIMDataNodeManager.simulate();
}
};
/**
* @param e
*/
private static void printException(Exception e) {
if (stackTraceOnException)
e.printStackTrace();
else {
println(e.toString());
if (e.getCause() != null)
println("Caused by: " + e.getCause());
}
}
// NodeManager에 등록하기 위한 리스너들
private final CertificateValidationListener validationListener = new CertificateValidationListener() {
@Override
public ValidationResult onValidate(Cert certificate, ApplicationDescription applicationDescription,
EnumSet<CertificateCheck> passedChecks) {
// Do not mind about URI...
if (passedChecks.containsAll(
EnumSet.of(CertificateCheck.Trusted, CertificateCheck.Validity, CertificateCheck.Signature))) {
if (!passedChecks.contains(CertificateCheck.Uri))
try {
println("Client's ApplicationURI (" + applicationDescription.getApplicationUri()
+ ") does not match the one in certificate: "
+ PkiFileBasedCertificateValidator.getApplicationUriOfCertificate(certificate));
} catch (CertificateParsingException e) {
throw new RuntimeException(e);
}
return ValidationResult.AcceptPermanently;
}
return ValidationResult.Reject;
}
};
private NodeManagerUaNode myNodeManager;
private final NodeManagerListener myNodeManagerListener = new NodeManagerListener() {
@Override
public void onAddNode(ServiceContext serviceContext, NodeId parentNodeId, UaNode parent, NodeId nodeId,
UaNode node, NodeClass nodeClass, QualifiedName browseName, NodeAttributes attributes,
UaReferenceType referenceType, ExpandedNodeId typeDefinitionId, UaNode typeDefinition)
throws StatusException {
// Notification of a node addition request
checkUserIdentity(serviceContext);
}
@Override
public void onAddReference(ServiceContext serviceContext, NodeId sourceNodeId, UaNode sourceNode,
ExpandedNodeId targetNodeId, UaNode targetNode, NodeId referenceTypeId, UaReferenceType referenceType,
boolean isForward) throws StatusException {
// Notification of a reference addition request
checkUserIdentity(serviceContext);
}
@Override
public void onAfterCreateMonitoredDataItem(ServiceContext serviceContext, Subscription subscription,
MonitoredDataItem item) {
//
}
@Override
public void onAfterDeleteMonitoredDataItem(ServiceContext serviceContext, Subscription subscription,
MonitoredDataItem item) {
//
}
@Override
public void onAfterModifyMonitoredDataItem(ServiceContext serviceContext, Subscription subscription,
MonitoredDataItem item) {
//
}
@Override
public boolean onBrowseNode(ServiceContext serviceContext, ViewDescription view, NodeId nodeId, UaNode node,
UaReference reference) {
// Perform custom filtering, for example based on the user
// doing the browse
// Default is to return all references for everyone
return true;
}
@Override
public void onCreateMonitoredDataItem(ServiceContext serviceContext, Subscription subscription, UaNode node,
UnsignedInteger attributeId, String indexRange, MonitoringParameters params, MonitoringFilter filter,
AggregateFilterResult filterResult) throws StatusException {
// Notification of a monitored item creation request
// You may, for example start to monitor the node from a physical
// device, only once you get a request for it from a client
}
@Override
public void onDeleteMonitoredDataItem(ServiceContext serviceContext, Subscription subscription,
MonitoredDataItem monitoredItem) {
// Notification of a monitored item delete request
}
@Override
public void onDeleteNode(ServiceContext serviceContext, NodeId nodeId, UaNode node,
boolean deleteTargetReferences) throws StatusException {
// Notification of a node deletion request
checkUserIdentity(serviceContext);
}
@Override
public void onDeleteReference(ServiceContext serviceContext, UaNode sourceNode, ExpandedNodeId targetNodeId,
UaReferenceType referenceType, boolean isForward, boolean deleteBidirectional) throws StatusException {
// Notification of a reference deletion request
checkUserIdentity(serviceContext);
}
@Override
public void onModifyMonitoredDataItem(ServiceContext serviceContext, Subscription subscription,
MonitoredDataItem item, UaNode node, MonitoringParameters params, MonitoringFilter filter,
AggregateFilterResult filterResult) {
// Notification of a monitored item modification request
}
private void checkUserIdentity(ServiceContext serviceContext) throws StatusException {
// Do not allow for anonymous users
if (serviceContext.getSession().getUserIdentity().getType().equals(UserTokenType.Anonymous))
throw new StatusException(StatusCodes.Bad_UserAccessDenied);
}
};
private final EventManagerListener myEventManagerListener = new EventManagerListener() {
@Override
public boolean onAcknowledge(ServiceContext serviceContext, AcknowledgeableConditionType condition,
byte[] eventId, LocalizedText comment) throws StatusException {
// Handle acknowledge request to a condition event
println("Acknowledge: Condition=" + condition + "; EventId=" + eventIdToString(eventId) + "; Comment="
+ comment);
// If the acknowledged event is no longer active, return an error
if (!Arrays.equals(eventId, condition.getEventId()))
throw new StatusException(StatusCodes.Bad_EventIdUnknown);
if (condition.isAcked())
throw new StatusException(StatusCodes.Bad_ConditionBranchAlreadyAcked);
// If the condition is no longer active, set retain to false, i.e.
// remove it from the visible alarms
if (!(condition instanceof AlarmConditionType) || !((AlarmConditionType) condition).isActive())
condition.setRetain(false); // ���� ����
final DateTime now = DateTime.currentTime();
condition.setAcked(true, now);
final byte[] userEventId = getNextUserEventId();
// addComment triggers a new event
condition.addComment(eventId, comment, now, userEventId);
return true; // Handled here
// NOTE: If you do not handle acknowledge here, and return false,
// the EventManager (or MethodManager) will call
// condition.acknowledge, which performs the same actions as this
// handler, except for setting Retain
}
@Override
public boolean onAddComment(ServiceContext serviceContext, ConditionType condition, byte[] eventId,
LocalizedText comment) throws StatusException {
// Handle add command request to a condition event
println("AddComment: Condition=" + condition + "; Event=" + eventIdToString(eventId) + "; Comment="
+ comment);
// Only the current eventId can get comments
if (!Arrays.equals(eventId, condition.getEventId()))
throw new StatusException(StatusCodes.Bad_EventIdUnknown);
// triggers a new event
final byte[] userEventId = getNextUserEventId();
condition.addComment(eventId, comment, DateTime.currentTime(), userEventId);
return true; // Handled here
// NOTE: If you do not handle addComment here, and return false,
// the EventManager (or MethodManager) will call
// condition.addComment automatically
}
@Override
public void onAfterCreateMonitoredEventItem(ServiceContext serviceContext, Subscription subscription,
MonitoredEventItem item) {
//
}
@Override
public void onAfterDeleteMonitoredEventItem(ServiceContext serviceContext, Subscription subscription,
MonitoredEventItem item) {
//
}
@Override
public void onAfterModifyMonitoredEventItem(ServiceContext serviceContext, Subscription subscription,
MonitoredEventItem item) {
//
}
@Override
public void onConditionRefresh(ServiceContext serviceContext, Subscription subscription)
throws StatusException {
//
}
@Override
public boolean onConfirm(ServiceContext serviceContext, AcknowledgeableConditionType condition, byte[] eventId,
LocalizedText comment) throws StatusException {
// Handle confirm request to a condition event
println("Confirm: Condition=" + condition + "; EventId=" + eventIdToString(eventId) + "; Comment="
+ comment);
// If the confirmed event is no longer active, return an error
if (!Arrays.equals(eventId, condition.getEventId()))
throw new StatusException(StatusCodes.Bad_EventIdUnknown);
if (condition.isConfirmed())
throw new StatusException(StatusCodes.Bad_ConditionBranchAlreadyConfirmed);
if (!condition.isAcked())
throw new StatusException("Condition can only be confirmed when it is acknowledged.",
StatusCodes.Bad_InvalidState);
final DateTime now = DateTime.currentTime();
condition.setConfirmed(true, now);
final byte[] userEventId = getNextUserEventId();
// addComment triggers a new event
condition.addComment(eventId, comment, now, userEventId);
return true; // Handled here
// NOTE: If you do not handle Confirm here, and return false,
// the EventManager (or MethodManager) will call
// condition.confirm, which performs the same actions as this
// handler
}
@Override
public void onCreateMonitoredEventItem(ServiceContext serviceContext, NodeId nodeId, EventFilter eventFilter,
EventFilterResult filterResult) throws StatusException {
// Item created
}
@Override
public void onDeleteMonitoredEventItem(ServiceContext serviceContext, Subscription subscription,
MonitoredEventItem monitoredItem) {
// Stop monitoring the item?
}
@Override
public boolean onDisable(ServiceContext serviceContext, ConditionType condition) throws StatusException {
// Handle disable request to a condition
println("Disable: Condition=" + condition);
if (condition.isEnabled()) {
DateTime now = DateTime.currentTime();
// Setting enabled to false, also sets retain to false
condition.setEnabled(false, now);
// notify the clients of the change
condition.triggerEvent(now, null, getNextUserEventId());
}
return true; // Handled here
// NOTE: If you do not handle disable here, and return false,
// the EventManager (or MethodManager) will request the
// condition to handle the call, and it will unset the enabled
// state, and triggers a new notification event, as here
}
@Override
public boolean onEnable(ServiceContext serviceContext, ConditionType condition) throws StatusException {
// Handle enable request to a condition
println("Enable: Condition=" + condition);
if (!condition.isEnabled()) {
DateTime now = DateTime.currentTime();
condition.setEnabled(true, now);
// You should evaluate the condition now, set Retain to true,
// if necessary and in that case also call triggerEvent
// condition.setRetain(true);
// condition.triggerEvent(now, null, getNextUserEventId());
}
return true; // Handled here
// NOTE: If you do not handle enable here, and return false,
// the EventManager (or MethodManager) will request the
// condition to handle the call, and it will set the enabled
// state.
// You should however set the status of the condition yourself
// and trigger a new event if necessary
}
@Override
public void onModifyMonitoredEventItem(ServiceContext serviceContext, Subscription subscription,
MonitoredEventItem monitoredItem, EventFilter eventFilter, EventFilterResult filterResult)
throws StatusException {
// Modify event monitoring, when the client modifies a monitored
// item
}
@Override
public boolean onOneshotShelve(ServiceContext serviceContext, AlarmConditionType condition,
ShelvedStateMachineType stateMachine) throws StatusException {
return false;
}
@Override
public boolean onTimedShelve(ServiceContext serviceContext, AlarmConditionType condition,
ShelvedStateMachineType stateMachine, double shelvingTime) throws StatusException {
return false;
}
@Override
public boolean onUnshelve(ServiceContext serviceContext, AlarmConditionType condition,
ShelvedStateMachineType stateMachine) throws StatusException {
return false;
}
private String eventIdToString(byte[] eventId) {
return eventId == null ? "(null)" : Arrays.toString(eventId);
}
};
private final IoManagerListener myIoManagerListener = new IoManagerListener() {
@Override
public EnumSet<AccessLevel> onGetUserAccessLevel(ServiceContext serviceContext, NodeId nodeId,
UaVariable node) {
// The AccessLevel defines the accessibility of the Variable.Value
// attribute
return EnumSet.of(AccessLevel.CurrentRead, AccessLevel.CurrentWrite, AccessLevel.HistoryRead);
}
@Override
public boolean onGetUserExecutable(ServiceContext serviceContext, NodeId nodeId, UaMethod node) {
// Enable execution of all methods that are allowed by default
return true;
}
/**
* WriteMask 선택적이며 쓰기 가능한 노드 속성을 지정합니다. 즉, OPC UA 클라이언트가 수정할 수 있습니다 WriteAccess
*/
@Override
public EnumSet<WriteAccess> onGetUserWriteMask(ServiceContext serviceContext, NodeId nodeId, UaNode node) {
// Enable writing to everything that is allowed by default
// The WriteMask defines the writable attributes, except for Value,
// which is controlled by UserAccessLevel (above)
// The following would deny write access for anonymous users:
// if
// (serviceContext.getSession().getUserIdentity().getType().equals(
// UserTokenType.Anonymous))
// return EnumSet.noneOf(WriteAccess.class);
return EnumSet.allOf(WriteAccess.class);
}
@Override
public void onReadNonValue(ServiceContext serviceContext, NodeId nodeId, UaNode node,
UnsignedInteger attributeId, DataValue dataValue) throws StatusException {
// OK
}
@Override
public void onReadValue(ServiceContext serviceContext, NodeId nodeId, UaVariable node, NumericRange indexRange,
TimestampsToReturn timestampsToReturn, DateTime minTimestamp, DataValue dataValue)
throws StatusException {
// OK
}
@Override
public boolean onWriteNonValue(ServiceContext serviceContext, NodeId nodeId, UaNode node,
UnsignedInteger attributeId, DataValue dataValue) throws StatusException {
return false;
}
@Override
public boolean onWriteValue(ServiceContext serviceContext, NodeId nodeId, UaVariable node,
NumericRange indexRange, DataValue dataValue) throws StatusException {
return false;
}
};
/**
* 리스너에서 사용하는 print함수
*
* @param string
*/
private static void println(String string) {
System.out.println(string);
}
/**
* @return
* @throws RuntimeException
*/
private byte[] getNextUserEventId() throws RuntimeException {
return BaseEventType.createEventId(eventId++);
}
protected void initialize() throws SecureIdentityException, IOException, UaServerException {
// *** Create the server
// 서버 객체 생성
server = new UaServer();
// 1. Use PKI files to keep track of the trusted and rejected client
// certificates...
// PkiFileBasedCertificateValidator라고 하는 Pki 파일이 정확한 것인지 확인할 수 있는 validator를
// 생성하고 server에 set한다.
final PkiFileBasedCertificateValidator validator = new PkiFileBasedCertificateValidator();
// server.setCertificateValidator(validator);
validator.setValidationListener(validationListener);
// *** 2. Application Identity
// 어플리케이션의 고유한 정보(이름, 언어 설정, uri 등)을 설정한다.
ApplicationDescription appDescription = new ApplicationDescription();
appDescription.setApplicationName(new LocalizedText(APP_NAME, Locale.ENGLISH));
// 'localhost' (all lower case) in the URI is converted to the actual
// host name of the computer in which the application is run
appDescription.setApplicationUri("urn:localhost:UA:OPCUAServer");
appDescription.setProductUri("urn:prosysopc.com:UA:OPCUAServer");
// *** 3. Server Endpoints(Socket Setting)
// 현재 서버의 Endpoint를 설정(포트, 서버이름 등)
// the port for the binary protocol
server.setPort(50500);
// add 'localhost' to the endpoint list
server.setUseLocalhost(true);
// optional server name part of the URI
server.setServerName("OPCUA/OPCUAServer");
// Add the IP address(es) of the server host to the endpoints
server.setUseAllIpAddresses(true);
// 4. Define the Server application identity, including the security
// certificate.
// We do this after defining the endpoints to be able to use getHostNames()
// 보안 인증을 포함한 서버 어플리케이션의 고유한 정보를 설정한다. 패스워드나 키 파일, 서버 호스트 네임 등.
final ApplicationIdentity identity = ApplicationIdentity.loadOrCreateCertificate(appDescription,
"Sample Organisation", /* Private Key Password */"opcua",
/* Key File Path */new File(validator.getBaseDir(), "private"),
/* Enable renewing the certificate */true,
/* Additional host names for the certificate */server.getHostNames());
server.setApplicationIdentity(identity);
// *** 5. Security settings
// 보안을 설정한다. ANONYMOUS, SECURE_USERNAME_PASSWORD 등 Token Policy를 서버에 추가한다.
// 추가적으로 클라이언트에 의해 선택된 인증 모드에 따라 actual types을 검사하는 UserValidator 객체를 생성한다.
// Define the security modes to support - ALL is the default
server.setSecurityModes(SecurityMode.NON_SECURE);
// Define the supported user Token policies
server.addUserTokenPolicy(UserTokenPolicy.ANONYMOUS);
// server.addUserTokenPolicy(UserTokenPolicy.SECURE_USERNAME_PASSWORD);
// server.addUserTokenPolicy(UserTokenPolicy.SECURE_CERTIFICATE);
// Define a validator for checking the user accounts
// server.setUserValidator(userValidator);
// Register on the local discovery server (if present)
// server.setDiscoveryServerUrl("opc.tcp://localhost:4840");
server.setDiscoveryServerUrl("opc.tcp://localhost:50500");
// init() creates the default endpoints according to the above settings
// init()을 호출함으로 위에서 설정한 내용들을 서버에 적용시킨다.
server.init();
// 서버 빌드 구조를 초기화한다.
initBuildInfo();
// You can do your own additions to server initializations here
// 추가적으로 초기화 할 내용들을 추가
}
/**
* Initialize the information to the Server BuildInfo structure
*/
private void initBuildInfo() {
// Initialize BuildInfo - using the version info from the SDK
// You should replace this with your own build information
// server내용을 기반으로 BuildInfoType을 초기화한다.
final BuildInfoType buildInfo = server.getNodeManagerRoot().getServerData().getServerStatus().getBuildInfo();
// Fetch version information from the package manifest
// 매니페스트 파일을 포함하는 패키지 매니페스트로부터 버전 정보를 가져온다.
// Pacakge객체로부터 ManufacturerName, SoftwareVersion, BuildNumber를 초기화 하고, URL로부터
// BuildDate를 초기화한다.
final Package sdkPackage = UaServer.class.getPackage();
final String implementationVersion = sdkPackage.getImplementationVersion();
if (implementationVersion != null) {
int splitIndex = implementationVersion.lastIndexOf(".");
final String softwareVersion = implementationVersion.substring(0, splitIndex);
String buildNumber = implementationVersion.substring(splitIndex + 1);
buildInfo.setManufacturerName(sdkPackage.getImplementationVendor());
buildInfo.setSoftwareVersion(softwareVersion);
buildInfo.setBuildNumber(buildNumber);
}
final URL classFile = UaServer.class.getResource("/com/prosysopc/ua/samples/OPCUAServer.class");
if (classFile != null) {
final File mfFile = new File(classFile.getFile());
GregorianCalendar c = new GregorianCalendar();
c.setTimeInMillis(mfFile.lastModified());
buildInfo.setBuildDate(new DateTime(c));
}
}
/**
* Create a sample address space with a new folder, a device object, a level
* variable, and an alarm condition.
* <p>
* The method demonstrates the basic means to create the nodes and references
* into the address space.
* <p>
* Simulation of the level measurement is defined in {@link #startSimulation()}
*
* @throws StatusException if the referred type nodes are not found from
* the address space
* @throws FileNotFoundException
*
*/
protected void createAddressSpace() throws StatusException, FileNotFoundException {
// 알람의 상태 값, 레벨 변수, 디바이스 오브젝트, 새 폴더와 함께 주소 공간을 생성한다.
// 주소 공간을 생성하며 초기화할 폴더나 데이터들도 같이 생성한다.
// My Node Manager
// OPC UA에서 사용되는 Object들은 Node기반이기 때문에 Node를 Controll하기 위해서 NodeManager를 사용한다.
// NodeManager에 사용하고 싶은 리스너들을 초기화 한 후 등록시켜준다.
myNodeManager = new NodeManagerUaNode(server, "http://www.prosysopc.com/OPCUA/SampleAddressSpace");
// My Node Manager Listener
// 노드생성, 참조생성, 노드탐색 등 NodeManagerListener 인터페이스의 메서드를 Override해서 필요에 따라 구현해준다.
myNodeManager.addListener(myNodeManagerListener);
// My Event Manager Listener
// AC에 해당하는 Alarm & Condition에 관련된 기능들을 정의하는 리스너
myNodeManager.getEventManager().setListener(myEventManagerListener);
// My I/O Manager Listener
myNodeManager.getIoManager().setListener(myIoManagerListener);
// My HistoryManager
// History Access에 관련된 기능들을 정의하는 리스너
// myNodeManager.getHistoryManager().setListener(myHistoryManagerListener);
// +++ My nodes +++
int ns = myNodeManager.getNamespaceIndex();
// UA types and folders which we will use
final UaObject objectsFolder = server.getNodeManagerRoot().getObjectsFolder();
final UaType baseObjectType = server.getNodeManagerRoot().getType(Identifiers.BaseObjectType);
final UaType baseDataVariableType = server.getNodeManagerRoot().getType(Identifiers.BaseDataVariableType);
// Folder for my objects
createObjectsFolder(ns, objectsFolder, baseObjectType, baseDataVariableType);
// A sample alarm node
// createAlarmNode(ns);
// A sample method node
// createMethodNode(ns);
// A sample node manager that can handle a big amount of UA nodes
// without creating UaNode objects in memory
// createBigNodeManager();
// test cim data create Subject
// createCIMDataNodeManager();
// test cim data create folder
// More specific nodes to enable OPC UA compliance testing of more
// advanced features
// createComplianceNodes();
logger.info("Address space created.");
}
/**
* 메인 오브젝트 폴더 생성
*
* @param namespaceIndex
* @param objectsFolder
* @param baseObjectType
* @param baseDataVariableType
*/
private void createObjectsFolder(int namespaceIndex, UaObject objectsFolder, UaType baseObjectType,
UaType baseDataVariableType) {
try {
// Folder for my objects
final NodeId myObjectsFolderId = new NodeId(namespaceIndex, "MyObjectsFolder");
myObjectsFolder = new FolderType(myNodeManager, myObjectsFolderId, "MyObjects", Locale.ENGLISH);
myNodeManager.addNodeAndReference(objectsFolder, myObjectsFolder, Identifiers.Organizes);
// My Device Type
final NodeId myDeviceTypeId = new NodeId(namespaceIndex, "MyDeviceType");
UaObjectType myDeviceType = new UaObjectTypeNode(myNodeManager, myDeviceTypeId, "MyDeviceType",
Locale.ENGLISH);
myNodeManager.addNodeAndReference(baseObjectType, myDeviceType, Identifiers.HasSubtype);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Run the server.
*
* @param enableSessionDiagnostics
* @throws UaServerException
* @throws StatusException
*/
protected void run(boolean enableSessionDiagnostics) throws UaServerException, StatusException {
server.start();
// initHistory();
if (enableSessionDiagnostics)
server.getNodeManagerRoot().getServerData().getServerDiagnostics().setEnabled(true);
startSimulation();
// *** Main Menu Loop
mainMenu();
// *** End
stopSimulation();
// Notify the clients about a shutdown, with a 5 second delay
println("Shutting down...");
server.shutdown(1, new LocalizedText("Closed by user", Locale.ENGLISH));
println("Closed.");
}
/**
* Starts the simulation of the level measurement.
*/
private void startSimulation() {
simulator.scheduleAtFixedRate(simulationTask, 1000, 1000, TimeUnit.MILLISECONDS);
logger.info("Simulation started.");
}
/**
* Ends simulation.
*/
private void stopSimulation() {
simulator.shutdown();
logger.info("Simulation stopped.");
}
void printMenu() {
println("");
println("");
println("");
System.out.println("-------------------------------------------------------");
for (Entry<String, Action> a : Action.actionMap.entrySet())
println("- Enter " + a.getKey() + " to " + a.getValue().getDescription());
}
/**
* @return
*/
private static Action readAction() {
return Action.parseAction(readInput().toLowerCase());
}
/**
* @return
*/
private static String readInput() {
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String s = null;
do
try {
s = stdin.readLine();
} catch (IOException e) {
printException(e);
}
while ((s == null) || (s.length() == 0));
return s;
}
/**
* Main loop for user selecting OPC UA calls
*/
void mainMenu() {
/******************************************************************************/
/* Wait for user command to execute next action. */
do {
printMenu();
try {
switch (readAction()) {
case CLOSE:
return;
case ADD_NODE:
println("Enter the name of the new node (enter 'x' to cancel)");
String name = readInput();
if (!name.equals("x"))
addNode(name);
break;
case DELETE_NODE:
println("Enter the name of the node to delete (enter 'x' to cancel)");
String input = readInput();
if (!input.equals("x")) {
QualifiedName nodeName = new QualifiedName(myNodeManager.getNamespaceIndex(), input);
deleteNode(nodeName);
}
break;
case ENABLE_DIAGNOSTICS:
final PlainProperty<Boolean> enabledFlag = server.getNodeManagerRoot().getServerData()
.getServerDiagnostics().getEnabledFlag();
boolean newValue = !enabledFlag.getCurrentValue();
enabledFlag.setCurrentValue(newValue);
println("Server Diagnostics " + (newValue ? "Enabled" : "Disabled"));
break;
// case SEND_EVENT:
// sendEvent();
default:
continue;
}
} catch (Exception e) {
printException(e);
}
} while (true);
/******************************************************************************/
}
/**
* 메인 폴더 기준으로 노드를 추가
*
* @param name
*/
private void addNode(String name) {
// Initialize NodeVersion property, to enable ModelChangeEvents
myObjectsFolder.initNodeVersion();
server.getNodeManagerRoot().beginModelChange();
try {
NodeId nodeId = new NodeId(myNodeManager.getNamespaceIndex(), UUID.randomUUID());
UaNode node = myNodeManager.getNodeFactory().createNode(NodeClass.Variable, nodeId, name, Locale.ENGLISH,
Identifiers.PropertyType);
myObjectsFolder.addComponent(node);
} catch (UaNodeFactoryException e) {
logger.error(e);
} catch (IllegalArgumentException e) {
logger.error(e);
} finally {
server.getNodeManagerRoot().endModelChange();
}
}
/**
* 이름을 기준으로 노드를 삭제
*
* @param nodeName
* @throws StatusException
*/
private void deleteNode(QualifiedName nodeName) throws StatusException {
UaNode node = myObjectsFolder.getComponent(nodeName);
if (node != null) {
server.getNodeManagerRoot().beginModelChange();
try {
myNodeManager.deleteNode(node, true, true);
} finally {
server.getNodeManagerRoot().endModelChange();
}
} else
println("MyObjects does not contain a component with name " + nodeName);
}
}
반응형
'산업자동화시스템 > OPC UA' 카테고리의 다른 글
OPC UA란? 간단 정리 및 요약(아키텍쳐의 이해, Classic OPC와의 차이) (1) | 2020.05.22 |
---|