어플리케이션 디자인 패턴: 상태 머신

개요

상태 머신은 LabVIEW 개발자들이 어플리케이션을 빠르게 개발하기 위해 자주 사용하는 기본 아키텍처 중 하나입니다. 상태 머신 아키텍처는 상태 다이어그램이나 순서도로 나타나는 복잡한 의사 결정 알고리즘을 구현하는 데 사용할 수 있습니다. 상태 머신은 기본 LabVIEW 함수로 구현할 수 있으며, 아키텍처에 추가적인 툴킷이나 모듈이 필요하지 않습니다.

 

이 문서에서는 상태 머신이란 무엇인지, 사용 사례, 상태 머신의 개념을 보여주는 예제, 상태 머신 코드의 상세 예제를 살펴봅니다.

 

기본 상태 머신 템플릿에 대한 개발자 가이드를 참조하십시오.

내용

상태 머신이란?

상태 머신은 이전 상태 또는 사용자 입력의 값에 따라 동적으로 상태를 변경하는 프로그래밍 아키텍처입니다.

이 아키텍처는 다음을 결합한 어플리케이션에 적당합니다.

  • 상태
  • 언제 특정 상태로 이동할 것인지 결정하는 의사결정 로직


상태는 프로그램의 전반적인 작업을 수행하는 동안의 프로그램 내 상태로 정의할 수 있습니다. 상태의 예로는 초기화 중, 대기 중, 계산 실행 중, 상태 확인 중 등이 있습니다.

논리적 구문을 바탕으로 언제 새로운 상태로 이동할지와 어떤 상태로 이동할지 결정할 수 있습니다. 이벤트는 한 상태에서 다음 상태로 이동하는 데 사용할 수 있습니다. 이는 프로그램적인 이벤트이거나 버튼을 누르는 것과 같이 사용자 정의된 이벤트일 수 있습니다.

상태 머신의 각 상태는 고유한 작업을 수행하고 다른 상태를 호출합니다. 상태 통신은 조건이나 시퀀스에 따라 다릅니다. 상태 다이어그램을 LabVIEW 프로그래밍 아키텍처로 변환하려면 다음 인프라가 필요합니다.

  1. While 루프 – 여러 개의 상태를 연속적으로 실행
  2. 케이스 구조 – 각 케이스에 각 상태에 대해 실행되는 코드가 있음
  3. 시프트 레지스터 – 상태 전이 정보를 포함
  4. 전이 코드 – 시퀀스의 다음 상태를 결정 (아래 전이 코드 예제 섹션 참조)

상태 머신을 쓰는 이유

상태 머신은 상태가 뚜렷하게 구별되는 어플리케이션에서 사용됩니다. 각 상태는 한 가지 또는 여러 가지 상태로 이어질 수 있고 프로세스를 종료할 수도 있습니다. 상태 머신은 사용자의 입력 또는 현재 상태 내의 계산에 따라 다음으로 진행할 상태를 결정합니다. 많은 어플리케이션은 기본 상태 다음에 여러 작업이 실행될 수 있는 초기 상태를 요구합니다. 실행되는 작업은 이전 및 현재 입력과 상태에 따라 달라질 수 있습니다. 그리고 “종료” 상태로 정리 작업을 수행할 수 있습니다.

상태 머신은 의사 결정 알고리즘을 구현할 수 있는 강력한 기능을 갖고 있으면서, 어플리케이션 계획의 기능적인 형태입니다. 어플리케이션이 더욱 복잡해지면서 적절한 설계의 필요성도 더 커집니다. 상태 다이어그램과 순서도는 설계 프로세스에 유용하며 때로는 필수적입니다. 상태 머신은 어플리케이션 계획에 도움이 될 뿐만 아니라 만들기도 쉽습니다.

사용 사례

예를 들어, 다음과 같은 어플리케이션에서 상태 머신 패턴이 유용합니다.

  • 단일 페이지 또는 탭 대화 상자. 대화 상자의 각 탭에 상태가 연결됩니다. 사용자는 특정 탭을 클릭하여 상태를 변경합니다. 각 탭에서, 사용자가 수행하는 모든 동작은 상태에 포함됩니다.
  • 현금 자동 인출기 (ATM). 이 어플리케이션의 상태는 사용자 입력 대기, 요청 금액과 계정 잔액 비교, 현금 배출, 영수증 인쇄하기 등을 포함할 수 있습니다.
  • 한 번 측정하고 이를 디스크에 기록한 후, 다른 사용자 동작을 기다리는 어플리케이션. 이 어플리케이션의 상태는 사용자 입력 대기, 측정 수행, 데이터 기록, 데이터 표시 등을 포함할 수 있습니다.
  • 상태 머신은 사용자 인터페이스를 프로그래밍할 때 가장 일반적으로 사용됩니다. 사용자 인터페이스에서는, 사용자 입력이 다르면 사용자 인터페이스가 다른 코드 영역을 실행합니다. 이렇게 서로 다른 코드 영역은 상태 머신에서 상태 역할을 합니다. 이러한 영역은 다른 영역으로 이어져 코드가 계속 실행되거나 사용자의 입력을 기다리도록 합니다. 이 예에서 상태 머신은 사용자의 다음 입력을 계속 기다립니다.
  • 프로세스 테스트는 상태 머신의 또 다른 일반적인 어플리케이션입니다. 이 예에서는, 프로세스의 각 부분이 상태로 대응됩니다. 각 상태에서의 테스트 결과에 따라 호출되는 상태가 달라집니다. 이 과정을 연속으로 수행하면, 테스트 중인 프로세스를 심층 분석할 수 있습니다.


사용자 인터페이스 구현에 사용할 수 있는 또 다른 디자인 패턴인 큐 메시지 핸들러도 있습니다. 큐 메시지 핸들러는 보다 정교한 상태 머신이며 더 유연하긴 하지만 더 복잡하기도 합니다. 

상태 머신 만들기

효과적인 상태 머신을 만들려면 설계자가 (1) 가능한 상태의 목록을 만들어야 합니다. 설계자는 이 목록으로 (2) 각 상태와 다른 상태의 관계를 계획할 수 있습니다. 그 후 상태 다이어그램을 (3) LabVIEW 그래픽 프로그래밍 아키텍처로 변환할 수 있습니다.

예: 대포 발사

이 예에서는 위험하게 과열되지 않고 연속적으로 대포를 발사하는 어플리케이션을 생성하려고 합니다.

(1) 가능한 상태 나열
먼저 작업에서 가능한 모든 상태의 목록을 만듭니다. 대포를 연속 발사하려면 다음을 수행해야 합니다.

  • 프로그램 초기화
  • 대포 전원 켜기
  • 대포 발사
  • 디바이스 온도 (상태) 확인
  • 디바이스를 수동으로 냉각 (온도가 여전히 가동 범위 내에 있지만 상승하는 경우)
  • 디바이스를 적극적으로 냉각 (온도가 가동 범위를 벗어난 경우)
  • 디바이스 끄기
     

(2) 상태 다이어그램으로 상태 간 관계 연결
다음으로 이러한 상태가 서로 어떻게 관련되어 있는지 살펴보고 상태 다이어그램을 만듭니다. 상태 다이어그램을 작성할 때, 프로그램이 한 상태에서 다른 상태로 이동하는 원인을 살펴보십시오. 자동 변환입니까? 사용자로부터의 외부 트리거가 있습니까? 계산 결과에 기반한 변환입니까?

예를 들어, 초기화 상태와 다른 상태 사이의 관계를 생각해봅시다.

  1. 초기화는 프로그램의 첫 번째 단계입니다. 이 상태에서는 입력이 없습니다.
  2. 초기화 후에 에러가 없으면 대포의 전원을 켭니다.
  3. 에러가 있는 경우, 프로그램을 종료합니다.
  4. 초기화 중 사용자가 정지 버튼을 누르면, 종료 상태로 이동하려는 것입니다.

상태가 서로 어떻게 관련되어 있는지 분석하면서 상태 간 이동 로직이 정의되기 시작된 점을 주목해 보십시오. 코드에 사용할 프로그래밍 로직은 (1) 변이 상태의 개수, (2) 고려되는 입력의 개수에 따라 달라집니다. 코드 전이 옵션은 아래의 전이 코드 예제 섹션에서 설명됩니다.

전체 상태 다이어그램이 완성될 때까지 상태의 상호 연관성을 계속 살펴보십시오. 상태 다이어그램은 모든 상태와 각 상태 사이의 관계를 포함합니다 (그림 1). 이 다이어그램에서 상태 (타원 노드)는 해당 상태에서 수행되는 동작을 나타내는 반면, 변이 (화살표)는 프로세스가 언제 어떻게 움직일 수 있는지를 나타냅니다.

그림 1: 대포 발사 상태 다이어그램

각 상태 사이의 관계는 상태 변이 로직 프로그래밍에 도움이 됩니다.

(3) LabVIEW에서 상태 머신 구축
상태 다이어그램에서 상태와 상태 간 관계를 정의하고 나면, 이를 LabVIEW의 코딩 아키텍처로 변환할 수 있습니다 (그림 2). 상태 다이어그램에서 상태 사이의 흐름 (그림1)은 루프로 구현됩니다. 개별 상태는 케이스 구조에서 케이스로 대체됩니다. 위 다이어그램의 각 상태는 케이스 구조의 서브다이어그램에 해당합니다. 각각의 상태:

  1. 특정 동작 수행
  2. While 루프의 시프트 레지스터에 지시사항을 전달하여 다음 상태가 무엇인지 상태 머신에 알려줍니다 (전이 코드).


While 루프의 시프트 레지스터는 현재 상태를 추적하며, 이는 케이스 구조 입력으로 전달됩니다.

그림 2: 상태 머신

상태 머신 예제 프로그램

이 템플릿을 측정 어플리케이션에 맞게 변경하는 예제를 보려면 프로젝트 생성 대화 상자의 단일 측정 샘플 프로젝트를 참조하십시오.

  • 초기화 후, 상태 머신은 "이벤트 대기" 상태로 이동합니다. 이 상태는 프런트패널 변경을 기다리는 이벤트 구조를 포함합니다. 사용자가 버튼을 클릭하면 LabVIEW는 그 이벤트를 인식해 이벤트 구조의 적절한 서브다이어그램으로 전환합니다. 이 서브다이어그램은 적절한 상태로의 전환을 시작합니다.
  • 각 상태는 데이터 클러스터에 접근할 수 있습니다. 이 클러스터의 데이터 타입은 Data.ctl에 정의되어 있습니다.
  • 타입정의인 State.ctl에는 유효한 상태가 열거되어 있습니다. 상태 변경에 타입정의를 사용하면 가능한 상태 변경이 제한되어, 상태 머신이 인식할 수 없는 상태로 변경될 가능성이 줄어듭니다.
  • 정지 상태만 어플리케이션을 정지시킬 수 있습니다. 이 설계를 통해 다음 사항을 보장함으로써 의도치 않은 부분적인 종료를 방지합니다.
     

1.     사용자가 어플리케이션의 정지를 원할 때에만 종료 코드가 실행됩니다.
2.     종료 코드는 항상 완료될 때까지 실행됩니다.

  • 한 번에 하나의 상태만 실행되며, While 루프가 하나라는 것은 모든 작업이 같은 속도로 실행된다는 것을 의미합니다. 여러 속도 또는 병렬 작업이 필요하면, 프로젝트 생성 대화 상자에서 큐 메시지 핸들러 또는 액터 프레임워크 템플릿을 사용해 보십시오.
  • 이벤트 대기 (Wait for Event) 상태는 사용자 입력을 인식하는 유일한 상태입니다. 상태 머신이 사용자 입력을 받으려면 이 상태여야 합니다.

자체적인 상태 머신 어플리케이션 생성하기

시작하는 방법은 기본 상태 머신 수정 LabVIEW 템플릿 길라잡이를 참조하십시오.  

필요사항 결정하기

이 템플릿을 사용자 정의하기 전에 다음 질문에 답해 보십시오.

  • 어플리케이션은 어떤 상태로 구성됩니까? 이 질문에 대한 답에 따라 추가할 상태가 결정됩니다.
  • 각 상태의 다음 상태는 무엇입니까? 이 질문에 대한 답에 따라 각 상태가 While 루프의 시프트 레지스터에 보내는 다음 상태 열거형 값이 결정됩니다.

    조건에 따라 하나의 상태가 여러 상태로 전환될 수 있습니다. 이 템플릿의 이벤트 대기 (Wait for Event) 상태가 그 한 예입니다. 이 상태에서는 사용자 입력에 따라 상태가 변경됩니다.
  • 각 상태에서 필요한 데이터는 무엇입니까? 이 질문에 대한 답에 따라 Data.ctl에 어떤 데이터 타입을 추가할지 결정됩니다.
  • 어떤 에러가 발생할 수 있으며 어플리케이션이 이러한 에러에 어떻게 반응해야 합니까? 이 질문에 대한 답에 따라 필요한 에러 처리의 정도가 결정됩니다.
     

전이 코드 예제

다음 상태를 결정하는 데에는 다양한 방법이 있습니다. 그 방법은 아래에 설명됩니다.
예제 이미지는 "초기화" 상태를 보여주지만, 이러한 전이는 모든 상태에 적용될 수 있습니다.

1 대 1: 항상 상태 A에서 상태 B로 전환하면, 로직을 작성할 필요가 없이 다음 케이스 (케이스 B)의 이름을 시프트 레지스터에 전달하기만 하면 됩니다.

그림 3a: 유일하게 가능한 전이 상태

1 대 2: 상태 A에서 상태 B 또는 상태 C로 전환할 수 있는 경우, [선택] 함수를 사용하여 인디케이터의 상태를 평가할 수 있습니다. 어떤 상태로 이동할지를 결정하는 요소를 평가해야 합니다. 예를 들어 그림 3b의 정지 버튼 사용자 입력에 따라 전원 가동 상태에서 이동할지 아니면 종료로 이동할지 결정됩니다.

그림 3b: 두 가지 전이 상태

배열을 통한 1 대 다: 여러 상태로 전환할 수 있는 경우, 열거형 상수와 연관된 불리언 영역을 사용하여 전환을 프로그램할 수 있습니다. 예를 들어 그림 3c에서는 실행되는 코드의 결과가 불리언 배열로 나타나며 이 결과로 변환이 결정됩니다. 불리언 배열은 전환 가능한 상태의 목록을 나타내는 열거형 상수와 상호 연관됩니다. [배열 인덱스] 함수를 사용하여, 불리언 배열에서 첫 번째 "참" 불리언의 인덱스가 출력됩니다. 그런 다음 [배열 부분] 함수를 사용하여 해당 열거형 상수에서 적절한 값을 가져올 수 있습니다.

그림 3c

: 배열의 인덱스는 0부터 시작하고 열거형은 1부터 시작한다는 점을 기억하십시오. 불리언 배열을 열거형 상수와 연결하려면, [증가] 함수를 사용하여 인덱스를 수정하십시오.

While 루프를 통한 1 대 다: 여러 가능한 전이 상태가 있는 경우를 위한 다른 방법은 케이스 안에 While 루프를 사용하는 것입니다. While 루프 내부의 코드는 불리언 상태가 참으로 설정되어 정지 버튼을 트리거할 때까지 계속됩니다. 이렇게 하면 트리거 이벤트가 발생할 때까지 코드가 효과적으로 실행될 수 있습니다.

그림 3d는 내부 루프와 케이스 구조를 사용하여 다음 상태로 전환하는 "초기화" 상태를 보여줍니다. 내부 케이스 구조는 현재 상태에서 나가는 각각의 전환에 대해 하나의 다이어그램을 포함합니다. 내부 케이스 구조의 각 케이스에는 두 개의 출력이 있습니다. 하나는 전환할지 여부를 결정하는 불리언 값, 그리고 하나는 전환할 상태를 정하는 열거형 상수입니다. 루프 인덱스를 케이스 구조의 입력으로 사용함으로써, 이 코드는 “참” 불리언 출력이 있는 다이어그램을 찾을 때까지 각 전환 케이스를 확인합니다. “참” 불리언 출력이 발견되면, 케이스는 전환할 새 상태를 출력합니다. 이 방법은 이전 방법보다 약간 복잡하지만, 루프 인덱스의 출력을 열거형 타입으로 “캐스트”하면 전이에 이름을 붙일 수 있습니다. 이러한 이점을 사용하면 전환 코드를 “자동으로 문서화”할 수 있습니다.

그림 3d

기타 프로그래밍 고려사항

 

코드 중복
문제: 상태 머신을 생성할 때 가장 어려운 부분은 상태 다이어그램의 상태를 나누는 부분입니다. 예를 들어, 콜라 자판기 상태 다이어그램 (그림 4)에서 동전 유형에 따라 하나의 상태에서 다른 상태로 이동하는 “응답 대기” 상태 대신, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50센트 상태를 사용할 수도 있었습니다. 그러면 정확히 같은 케이스 다이어그램에서 11개의 상태가 생성됩니다. 코드 중복은 더 큰 어플리케이션에서는 큰 문제를 일으킬 수 있습니다.

해결책
: 다른 상태가 같은 케이스 다이어그램을 가지고 있는 경우, 하나의 상태로 결합해 보십시오. 예를 들어, 코드 중복을 피하기 위해 “응답 대기” 상태를 만들 수 있습니다.

열거형 사용
문제: 열거형은 상태 머신의 케이스 선택자로 널리 사용됩니다. 사용자가 이 열거형에 상태를 추가하거나 삭제하면, 이 열거형의 복사본에 연결된 나머지 와이어가 끊어집니다. 이 현상은 열거형으로 상태 머신을 구현할 때 발생하는 가장 일반적인 장애 요소 중 하나입니다.

해결책: 다음은 이 문제를 해결하는 수 있는 두 가지 방법입니다.
1. 모든 열거형이 변경된 열거형에서 복사되면 중단되는 문제가 사라집니다.
2. 그 열거형을 가진 새 컨트롤을 생성하고 서브메뉴에서 “타입정의”를 선택합니다. 타입정의를 선택하면, 사용자가 상태를 추가하거나 제거할 때 모든 열거형 복사본이 자동으로 수정됩니다.

다음 단계

LabVIEW의 상태 머신과 기타 고급 아키텍처에 대해 자세히 알아보려면, LabVIEW Core 2 고객 교육과정을 수강하십시오.