필요가 생겨 탬플릿을 다시 작성해본다.

 

프로그램 기본 구조

 

화면 디자인은 그래픽적인 요소가 있어 WPF로 작업했다.

 

WPF로 작업할때 MVVM 형태를 완벽하게 구현하는 것은 아니지만 대체로 비슷한 형태로 구현하는 습관이 자리잡은 것 같다.

 

어떤 프로그램을 구현하던지 기본적으로 세가지 모듈을 가지도록 구성하는 것은 이제 당연한 것이 되어가고 있다.

 

이번 프로그램의 경우도 마찬가지 View(with View Model)를 구성하는 WPF 모듈을 기본 실행파일로 하고 실제 통신을 담당하는 OPC DA Client는 별도 DLL로 분리해서 View 모듈과  연동하도록 구성했다.

 

이렇게 하는 이유는 통신모듈만 별도로 테스트가 가능하기 때문에 통신과 GUI 인터페이스 사이에서 오류를 찾아 헤매는 것을 줄여줄 수 있다.

또한, 전체 로그를 관리하는 로그매니저(주로 log4net Wrapper)를 따로 모듈로 분리해서 각각의 모듈에서 delegate 해서 사용하도록 구성한다.

 

네트워크 인터페이스도 결국 I/O에 해당하기 때문에 쓰레드 처리여부와 상관없이 delegate로 처리하기 때문에 이벤트방식의 비동기적인 실행이 보장된다.

 

아주 대용량의 복잡한 컨넥션이 아니라면 이런 구성만으로도 전체적으로 이해하기 쉬우면서도 모듈간 간섭을 최소화하는 방식으로 프로그래밍이 가능하게 된다.

 

 

 

 

이번 포스팅의 핵심은 구조적인 문제보다는 OPC DA Client에 대한 설명이 목적이니 구조적인 부분은 여기까지만 설명하고 OPC DA Client와 TitainumAS 라이브러리에 대해 설명하겠다.

 

NuGet에 올라온 다른 라이브러리를 사용했던 팀에서 안정화 이슈가 있다고 해서 이 라이브러리로 교체했다.

실제 시스템에 적용해보니 안정성 면에서는 아직 이슈를 발견하지 못했고 전반적으로 만족스럽게 사용할 수 있었다.

 

TitainumAS를 이용한 OPC DA Client에 대해서 간단하게 앞서 포스팅을 했었는데

 

2022.10.28 - [C#] - OPC DA Client (with TitaniumAS)

 

간단히 라이브러리에 대한 소개정도의 수준이었다면 이번 포스팅은 구독과 쓰기를 포항해 구체적인 모듈구성까지 설명하도록 하겠다.

 

OPC DA Client 모듈은 TOpcDa 로 명명하고 그 안에는 두개의 클래스로 나누어 구성했다.

 

TOpcDaCore, TOpcDaRun

 

TOpcDaCore 클래스는 기본적인 Connect, DisConnect, AddGroup, Browsing과 같은 연결관리에 필요한 기본 메소드와 OpcDaServer 변수를 정의해 둔다.

TOpcDaRun 클래스는 TOpcDaCore 클래스를 객체화하여 실행할 동작들 예를 들어 그룹을 지정하고 그룹에 들어갈 태그를 등록하고 그룹별로 구독(Subscribe)설정을 진행하도록 구성했다.

 

좀더 정확하게 객체지향적으로 표현하면 TOpcDaCore를 상속받아 처리하는게 더 정확할 수도 있겠지만 편의상 이렇게 구분했다.

 

그룹을 지정할때 아래의 코드처럼 특정 그룹에 대한 업데이트 레이트를 다르게 지정할 수도 있다.

            foreach (OpcDaGroup group in opcda.Server.Groups)
            {
                if (group.IsActive)
                {
                    group.ValuesChanged += Group_ValuesChanged;
                    if(group.Name == "SelectList")
                        group.UpdateRate = TimeSpan.FromMilliseconds(200);
                    else
                        group.UpdateRate = TimeSpan.FromMilliseconds(1000);
                    group.IsSubscribed = true;
                }

            }

구독 신청한 그룹에 대해서는 아래의 코드처럼 그룹별로 구분해서 처리해주면 된다.

        private void Group_ValuesChanged(object sender, OpcDaItemValuesChangedEventArgs e)
        {
            OpcDaGroup group = (OpcDaGroup)sender;

            try
            {
                switch (group.Name)
                {
                    case "SelectList":
                        {
                            SelectListParsing(e.Values);
                        }
                        break;
                    case "CurrentData":
                        {
                            CurrentDataParsing(e.Values);
                        }
                        break;
                    case "SummaryComplete":
                        {
                            SummaryDataParsing();
                        }
                        break;
                    case "TestGroup0":
                        {
                            foreach (OpcDaItemValue value in e.Values)
                            {
                                if (value.Item == null)
                                    continue;

                                 /* 개별 값에 대한 처리 */
                            }

                            OnDataChanged("test", data);
                        }
                        break;
                    case "TestGroup1":
                        {
                            OnDataChanged("time", e.Values[0].Value.ToString());
                        }
                        break;
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                LogManager.Error(ex.Message, "TOpcDaRun", "Group_ValuesChanged");
            }
        }

개별 그룹에 대해 직접 현재 값을 읽어와야할 경우가 있다면

                OpcDaGroup group = opcda.Server.Groups.FirstOrDefault(i => i.Name == "SummaryData");
                OpcDaItemValue[] odvs = group.Read(group.Items);
                foreach (OpcDaItemValue value in odvs)
                {
                    if (value.Item == null)
                        continue;

                    /* 데이터 처리 루틴 */

                }

이렇게 처리하면 그룹단위의 구독이나 데이터 읽기를 통해 좀더 편리하게 프로그램을 구성할 수 있다.

 

Posted by 휘프노스
,