% OPC는 기존의 OLE를 개선하여 데이터 통신을 하기위한 Framework 입니다.
% OPC는 UA, DA, HDA등이 있습니다. 데이터 통신은 DA를 사용하기 때문에 여기서는 DA를 다루겠습니다.
% OPC DA는 현재 version 3.0까지 있습니다.
제가 사용하는 DLL은
OpcNetApi.dll, OpcNetApi.Com.dll, OpcRcw.Da.dll, OpcRcw.Comm.dll, OpcRcw.Hda.dll
입니다.
% 솔루션에서 첨부하는 화일은 OpcNetApi.dll, OpcNetApi.Com.dll만 합니다. 나머지는 같은 폴더에 있으면 복사합니다.
% 소스는 C#, WPF 기준입니다.
1. IDiscovery를 만듭니다. 제일 기본이 되는 것으로 서버를 가져올때 사용됩니다.
private IDiscovery m_discovery = new OpcCom.ServerEnumerator();
2. 서버를 가져오는 코드입니다.
Opc.Server[] servers = m_discovery.GetAvailableServers(Specification.COM_DA_20, ServerText.Text.ToString(), null);
if (servers != null)
{
foreach (Opc.Da.Server server in servers)
{
if (String.Compare(server.Name, ServerNameText.Text, true) == 0)
{
//establish connection.
m_server = server;
break;
}
}
}
첫줄에 사용되는 "Specification.COM_DA_20"은 DA 2.0서버를 가져오는 명령입니다.
서버의 종류는 다음과 같습니다.
public static readonly Specification COM_AE_10;
public static readonly Specification COM_HDA_10;
public static readonly Specification COM_DX_10;
public static readonly Specification COM_DA_30;
public static readonly Specification XML_DA_10;
public static readonly Specification COM_DA_10;
public static readonly Specification COM_BATCH_20;
public static readonly Specification COM_BATCH_10;
public static readonly Specification COM_DA_20;
ServerNameText.Text에 사용하려는 서버의 이름이 들어있습니다.
예를 들어 "SWToolbox.TOPServer.V5" 같은 것이 될 수 있습니다.
참고로 이 서버는 http://opchub.com/product/topserver.asp 여기서 제공하는 Trial 서버입니다.
다운로드 링크는 아래와 같습니다.
www.opchub.com/download/TOPserver_v5.exe
3. 서버에서 아이템들을 가져오는 코드입니다.
BrowsePosition position;
ItemIdentifier tItem = new ItemIdentifier();
BrowseFilters tFilter = new BrowseFilters();
tFilter.BrowseFilter = Opc.Da.browseFilter.all;
BrowseElement[] children = m_server.Browse(tItem, tFilter, out position);
chidren에 서버 루트에 있는 아이템이나 브랜치들이 들어 있습니다.
BrowserFilters의 값은 다음과 같이 될 수 있습니다.
Opc.Da.browseFilter.all;
Opc.Da.browseFilter.branch;
Opc.Da.browseFilter.item;
다음의 함수는 mItemNameList에 모든 아이템을 넣은 예제입니다.
List<string> mItempNameList;
private void GetItemsInChildren(BrowseElement[] tParent)
{
for (int i = 0; i < tParent.Length; i++)
{
if (tParent[i].IsItem == true)
{
mItempNameList.Add(tParent[i].ItemName);
}
else {
BrowsePosition position;
ItemIdentifier tItemChild = new ItemIdentifier();
BrowseFilters tFilterChild = new BrowseFilters();
tFilterChild.BrowseFilter = Opc.Da.browseFilter.all;
tItemChild.ItemName = tParent[i].ItemName;
BrowseElement[] tChildren = m_server.Browse(tItemChild, tFilterChild, out position);
GetItemsInChildren(tChildren);
}
}
}
private void GetAllItemInServer()
{
mItempNameList = new List<string>();
BrowsePosition position;
ItemIdentifier tItem = new ItemIdentifier();
BrowseFilters tFilter = new BrowseFilters();
tFilter.BrowseFilter = Opc.Da.browseFilter.all;
BrowseElement[] children = m_server.Browse(tItem, tFilter, out position);
GetItemsInChildren(children);
}
4. Subscription을 만들어서 값을 모니터링하는 코드입니다.
// Set group status
// Group (subscriber) status, equivalent to the parameters of the group in the OPC specification
mReadGroup = new SubscriptionState();
mReadGroup.Name = "Monitoring"; // Group Name
mReadGroup.ServerHandle = null; // The handle assigned by the server to the group.
mReadGroup.ClientHandle = Guid.NewGuid().ToString(); // The handle assigned by the client to the group.
mReadGroup.Active = true; // Activate the group.
mReadGroup.UpdateRate = 10; // The refresh rate is 1 second. -> 1000
mReadGroup.Deadband = 0; // When the dead zone value is set to 0, the server will notify the group of any data changes in the group.
mReadGroup.Locale = null; //No regional values are set.
// Add Group
mMonitoringSubscription = (Subscription)m_server.CreateSubscription(mReadGroup); // Create Group
10ms로 값을 가져오게 하였습니다.
5. Group에 모니터링할 item을 추가하는 부분입니다.
// Define Item List
Item[] items = new Item[MonitoringItemNames.Length]; // Define the data item, ie item
for (int i = 0; i < items.Length; i++) // Item initial assignment
{
items[i] = new Item(); // Create an Item object.
items[i].ClientHandle = Guid.NewGuid().ToString(); // The handle assigned by the client to the data item.
items[i].ItemPath = null; // The path of the data item in the server.
items[i].ItemName = MonitoringItemNames[i]; // The name of the data item in the server.
SensorData tSensorItem;
tSensorItem = new SensorData(); // Create an Item object.
tSensorItem.Name = MonitoringItemNames[i];
cSensorViewModel.ListSensorItem.Add(tSensorItem);
}
// Add Item
ItemResult[] tItemResult = mMonitoringSubscription.AddItems(items);
ItemValueResult[] itemValues = mMonitoringSubscription.Read(mMonitoringSubscription.Items);
foreach (ItemValueResult titem in itemValues)
{
// setup type
cSensorViewModel.AddType(titem.ItemName, titem.Value.GetType().ToString());
}
// Register callback event
mMonitoringSubscription.DataChanged += new DataChangedEventHandler(OnDataChange);
6. Data값이 변할때 호출되는 함수입니다.
// DataChange callback
public void OnDataChange(object subscriptionHandle, ItemValueResult[] values)
{
foreach (ItemValueResult item in values)
{
cSensorViewModel.AddValue(item.ItemName, item.Value);
}
}
7. 사용되는 SensorData Class와 SensorViewModel Class 입니다.
public class SensorData : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _dataType;
public string DataType
{
get { return _dataType; }
set { _dataType = value; }
}
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SensorViewModel
{
public ObservableCollection<SensorData> ListSensorItem;
public SensorViewModel()
{
ListSensorItem = new ObservableCollection<SensorData>();
}
public void AddValue(string tName, object tValue)
{
for (int i = 0; i < ListSensorItem.Count; i++)
{
if (ListSensorItem[i].Name == tName)
{
ListSensorItem[i].Value = tValue.ToString();
}
}
}
public void AddType(string tName, string strType)
{
for (int i = 0; i < ListSensorItem.Count; i++)
{
if (ListSensorItem[i].Name == tName)
{
ListSensorItem[i].DataType = strType;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
전체 프로젝트는 아래에 있습니다.
https://github.com/bagng/OPCClient
% 2020년 9월 10일 추가
OPC DA를 통해 값을 Write하는 함수입니다.
Item OPC_WriteItem = Array.Find(mMonitoringSubscription.Items, x => x.ItemName.Equals(tItem.Name));
ItemValue[] writeValues = new ItemValue[1];
writeValues[0] = new ItemValue(OPC_WriteItem);
writeValues[0].Value = tItem.Value;
IdentifiedResult[] retValues = mMonitoringSubscription.Write(writeValues);
'Windows' 카테고리의 다른 글
Windows10에서 메모장이 사라진 경우 (0) | 2020.08.04 |
---|---|
OPC.DA x64 버전 (0) | 2020.07.30 |
공공데이터 포털에서 데이터 가져오기 (0) | 2020.07.06 |
엑셀 시간 서식에서 초 이하 표시하기 (0) | 2020.07.03 |
MS Access Query 직접 입력하기 (0) | 2020.07.02 |