How to Group control in NiagaraFramework - Engineer of NiagaraFramework

Tridium, NiagaraFramework, SCADA, HMI, PLC, Automation, SmartFAM etc.. About controls.
나이아가라 프레임워크 QnA : neverlikekami@gmail.com

2019년 4월 17일 수요일

How to Group control in NiagaraFramework

개요

나이아가라에서 그룹제어를 효과적으로 하는 방법에 대해 알아보자


    효과적인 그룹제어하기 

    간단한 그룹제어 

    본문에서는 그룹제어를 위한 프로그램 방법을 알아본다.
    물론 여러가지 방법이 있겠지만 1차원적인 방법으로 아래와같은 방법이 있다.

    <1차원적인 그룹 컨트롤>

    얼핏 보기에는 심플하고 좋은 방법 같지만 실제로 적용해보면 문제점들이 발생한다.
    (특히 많은 포인트를 제어해야할때는 99%이상 문제가 발생한다)


    1차원적인 방법의 문제점

    NiagaraSupervisor는 PC레벨에서 동작한다. 즉, 스펙도 좋고 퍼포먼스도 좋다.

    조명제어를 예를 들어보겠다. (조명을 예로 드는 이유는 한 기기가 담당하는 Points수가 많기떄문)

    조명컨트롤러는 보통 약 256개의 회로를 관리한다. 회로에 대한 정보를 메인 컨트롤러가 갖고 있으며 내부에 ModbusSlave 서버를 두어 상부의 HMI나 기타 컨트롤러와 통신하게 된다.

    상부에서 위와같은 방식으로 그룹제어를 하게되면 통상 비슷한 시간대에 모든 포인트에 명령을 내보내게 된다. (점심시간, 퇴근시간, 출근시간 등...)

    중요한것은 위와같이 여러 포인트를 동시간대에, 한번에 명령을 내릴경우 컨트롤러가 이를 소화하기 어렵다는 점이다. 이는 출력을 내보내는 시스템에서도, 입력을 받아들이는 시스템에서도 부담스러울것이다. 심지어 임베디드 컨트롤러에 이런식으로 명령을 주고받는다면 운영자에게도, 컨트롤러에도 신뢰에 금이가는 모습을 몸소 느끼게 될것이다.

    한번에 다량의 명령을 내보내는것은 웹 서버를 다운시키기위한 DDOS공격과 다를게 없다.


    효과적인 그룹제어를 위한 방법

    내 스스로 나의 컨트롤러를 공격하는 행위를 하면서까지 운영자들에게 그룹제어의 기능을 제공하고싶지 않다. 이런식으로 기능을 제공해봤자 제살파먹기다.

    이 문제를 해결하기위해 필요한 기술은 쓰레드이다.

    엔지니어들이 쓰레드를 완전히 이해하고 사용하기엔 무리가 있다.

    하지만 "무엇이다" 정도의 개념을 알게된다면 어떻게 해야하는지도 궁굼해질것이다.

    쓰레드는 멀티프로세스를 소프트웨어적으로 구현한것이다.

    더 쉽게 직설적으로 표현하면 프로그램을 번갈아 가며 빠르게 실행할 수 있도록 하는것이다.

    쓰레드를 이용하여 포인트들을 약간의 텀을두고 순회하며 제어한다면 컨트롤러에게 부담을 줄여줄 수 있는 그룹제어 프로그램을 만들 수 있다.




    프로그램 작성하기

    아래의 Source, Slot, Imports를 적용하여 ProgramObject를 작성한다.

    SOURCE

    * 참고 : AX Version 에서 사용되던 getProgram()함수는 N4로 넘어가며 getComponent()함수로 변경되었다.  아래의 소스는 N4버전을 기준으로 작성되었다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    String GroupNameList[];
    BComponent progParent;
    public void onExecute() throws Exception{
    // execute code (set executeOnChange flag on inputs)                
    GroupNameList = getGroupNameList().getNames();   
    progParent = (BComponent)getComponent().getParent();
    Thread thread = new Thread(this,getComponent().getName());
    thread.start();
    }
    public void run(){
      System.out.println("started task["+Thread.currentThread().getName()+"]");
      //현재의위치와그룹리스트들과리스트에들어갈벨류값을넘겨준다
      ProcessChild(progParent,GroupNameList);  
      System.out.println("ended task["+Thread.currentThread().getName()+"]");
      //경로의 객체에 값을 직접 쏴주는 부분 Null을 쓰기위한 프로그램
      //test.setFallback(new BStatusBoolean(false,nullStatus)로 써버리면 false라는 밸류가
      //넘어감으로서 불이 꺼진다. true는 반대로 켜진다. 즉 현재값에서 상태값만 null을 만들어야한다.
      //즉 제어 포인트의 현재값을 읽어와서 해당 값의 밸류에 스테이터스만 NULL을 넣도록 만든다. 
      BOrd ord1 = BOrd.make(getNullORD());
      BBooleanWritable test = (BBooleanWritable)ord1.get();
      test.setFallback(new BStatusBoolean(test.getFallback().getValue(),BStatus.nullStatus));
    }
    public void ProcessChild(BComponent Parent,String[] GroupNameList){
      BComponent children[];
      children = Parent.getChildComponents();
      BBooleanWritable bFound;
      int counter1,counter2;
      for(counter1 = 0 ; counter1<children.length; counter1++){
         if(children[counter1].getTypeDisplayName(null).equals("Boolean Writable")){
            bFound = (BBooleanWritable)children[counter1];
            for(counter2=0;counter2<GroupNameList.length;counter2++){
                    if(bFound.getName().equals(GroupNameList[counter2])){
                        BStatusBoolean setNULL  = new BStatusBoolean(bFound.getFallback().getValue(),BStatus.nullStatus); 
                        //널을만들고     그룹제어할값을넣고       다시널로만든다                                          
                        bFound.setFallback(setNULL);
                        bFound.setFallback(new BStatusBoolean(getGroupValue().getValue())); 
                        try{
                            Thread.sleep(getSleepInterval());
                           }catch(Exception ie){}
                        bFound.setFallback(setNULL);
                    }//End of if 
              }//end of for      
          }//end of if
          ProcessChild(children[counter1],GroupNameList);
      }// end of for
    }


    Slot



    간단히 용도설명을 해보자면 아래와 같다.

    GroupNameList
      그룹으로 지정하고자 하는 오브젝트의 리스트를 적는다.
      구분자는 세미콜론(;)이고 마지막 포인트명 뒤에는 세미콜론을 붙이지 않는다.
    GroupValue
      제어를 어떻게 할 것인지, True인지 False 인지를 입력받는다.
    SleepInterval
      명령을 쉬어갈 시간을 MilliSecond 단위로 적는다.
    NullORD
      명령을 제어 후 똑같은 명령을 다시 내보낼 수 있도록 Null로 바꿀 오브젝트의
      위치값을 넣는다.


    Imports

    온전한 컴파일을 할 수 있도록 라이브러리를 추가한다.


    완성된 ProgramObject사용법

    프로그램 오브젝트를 제어하고자하는 포인트들의 위치에 붙여넣는다.
    (퍼포먼스 향상과 예기치않은 버그를 막기위함)

    포인트들의 이름을 참고하여 제어하고자하는 GroupList를 작성한다.
    (ex: R01_1;R02_2;R02_3)

    ON/OFF제어값을 전달할 포인트와 GroupValue를 연결해준다.
    (스케줄을 이용해도 무방하다)

    SleepInterval값을 입력한다. 100정도만 줘도 충분하다.

    NullORD에 ON/OFF제어값을 전달할 포인트의 경로를 적어준다.
    (오브젝트 선택 후 Ctrl+L을 누르면 나오는 경로값을 편집하여 이용한다. Station:|slot:/Program/PointName)

    제어를 테스트 해본 후 결과를 확인하며 퍼포먼스를 조절한다.
    (Interval 혹은 통신설정 조정)





    결론

    경험으로 비추어볼때 1차원적인 방법은 사용하면 안된다. 적어도 내가 경험한 임베디드 장비들의 성능은 PC에서 무자비하게 쏘아대는 패킷을 감당할 수 없다. 위에서 제시한 방법이 정답은 아니지만 문제없이 몇백개의 그룹도, 몇백개의 포인트도 충분히 잘 동작함을 확인했다.

    간단히 모듈로 배포하여 어떻게 만들었는지는 캡슐화하고 사용법만 작성하는것은 미래지향적이지 못하다고 생각했다. 어차피 세상에 날고기는 넘사벽 프로그래머들은 많다.

    오픈마인드를 지향하며 더좋은 프로그램들이 많이 공유되었으면 하는 바램으로 글을 마친다.

    댓글 없음:

    댓글 쓰기

    Post list