본문으로 건너뛰기

액터 시스템

ControlBee는 액터 기반 아키텍처를 중심으로 구축되어 있으며, 자동화 시스템의 각 구성 요소는 메시지를 통해 다른 액터와 통신하는 독립적인 액터로 모델링됩니다.

액터란 무엇인가?

액터는 다음과 같은 특징을 가진 독립적인 계산 단위입니다:

  • 상태 캡슐화 - 자체 내부 데이터를 관리합니다
  • 메시지 처리 - 들어오는 메시지에 비동기적으로 응답합니다
  • 메시징을 통한 통신 - 다른 액터에게 메시지를 전송합니다
  • 격리 유지 - 다른 액터의 상태에 직접 접근할 수 없습니다

왜 액터를 사용하나요?

액터 모델은 자동화 시스템에 여러 이점을 제공합니다:

1. 자연스러운 분해

물리적 기계는 자연스럽게 액터로 분해됩니다:

  • 컨베이어 - 재료 운송
  • 스테이지 - 작업물 고정
  • 헤드 - 도구 위치 지정
  • 피커 - 부품 조작

각 액터는 명확한 책임을 가진 실제 구성 요소를 나타냅니다.

2. 동시 실행

액터는 독립적으로 실행되며 병렬로 작동할 수 있습니다:

┌─────────┐      ┌─────────┐      ┌─────────┐
│ Stage │ │ Head │ │ Picker │
│ Actor │◄────►│ Actor │◄────►│ Actor │
└─────────┘ └─────────┘ └─────────┘
│ │ │
Loading Moving Gripping
(parallel) (parallel) (parallel)

3. 장애 격리

액터가 실패해도 전체 시스템이 중단되지 않습니다. 다른 액터는 계속 작동하거나 장애를 우아하게 처리할 수 있습니다.

4. 메시지 기반 조정

액터는 잘 정의된 메시지를 통해 조정되므로 상호작용이 명시적이고 추적 가능합니다.

액터 생성

액터는 Actor 기본 클래스를 상속합니다:

public class ConveyorActor : Actor
{
// Variables - Actor's state
public Variable<bool> Exists = new(VariableScope.Temporary);
public Variable<int> ConveyorSpeed = new(VariableScope.Local, 100);

// IO - Hardware interfaces
public IDigitalInput TrayDet = new DigitalInputPlaceholder();
public IDigitalOutput ConveyorRun = new DigitalOutputPlaceholder();

// Peers - References to other actors
public IActor Syncer = null!;
public IActor NextStage = null!;

public ConveyorActor(ActorConfig config) : base(config)
{
State = new IdleState(this);
}

public void SetPeers(IActor syncer, IActor nextStage)
{
Syncer = syncer;
NextStage = nextStage;
InitPeers([Syncer, NextStage]);
}
}

액터 수명 주기

1. 생성

액터는 ActorFactory를 통해 생성됩니다:

var actorFactory = serviceProvider.GetRequiredService<ActorFactory>();
var conveyor = actorFactory.Create<ConveyorActor>("InConveyor");
var stage = actorFactory.Create<StageActor>("Stage0", StageType.Tray);

2. 피어 설정

액터는 피어와의 관계를 설정합니다:

conveyor.SetPeers(syncer, stage);
stage.SetPeers(syncer, head, camera);

이는 액터 네트워크를 생성합니다:

       ┌─────────┐
│ Syncer │ (Orchestrator)
└────┬────┘

┌───────┼───────┐
│ │ │
┌───▼───┐ ┌─▼─────┐ ┌──▼────┐
│Conveyor│ │ Stage │ │ Head │
└────────┘ └───────┘ └───────┘

3. 변수 로딩

변수는 저장된 레시피에서 로드됩니다:

variableManager.Load(recipeName);

이는 VariableScope.Local 변수를 레시피별 값으로, VariableScope.Global 변수를 공유 값으로 복원합니다.

4. 시작

액터가 메시지 처리를 시작합니다:

syncer.Start();
conveyor.Start();
stage.Start();

각 액터는 초기 상태로 진입하고 메시지 처리를 시작합니다.

액터 구성 요소

변수

액터는 타입 변수를 사용하여 상태를 관리합니다:

// Global - 모든 레시피에서 공유, 자동 저장
public Variable<double> HomeSpeed = new(VariableScope.Global, 10.0);
public Variable<double> SafetyMargin = new(VariableScope.Global, 5.0);

// Local - 레시피별로 다른 값, 자동 저장
public Variable<double> Speed = new(VariableScope.Local, 100.0);
public Variable<Position1D> LoadPos = new(VariableScope.Local);

// Temporary - 런타임 상태, 자동 저장 안 됨
public Variable<bool> Busy = new(VariableScope.Temporary);
public Variable<int> CurrentIndex = new(VariableScope.Temporary);

자세한 내용은 변수 시스템을 참조하세요.

장치

액터는 장치 인터페이스를 통해 하드웨어와 인터페이스합니다:

// Digital IO
public IDigitalInput SensorIn = new DigitalInputPlaceholder();
public IDigitalOutput ValveOut = new DigitalOutputPlaceholder();

// Motion control
public IAxis X = config.AxisFactory.Create();

// Vision
public IVision Camera = new VisionPlaceholder();

자세한 내용은 장치 통합을 참조하세요.

상태 머신

액터는 상태 머신을 사용하여 동작을 관리합니다:

public class IdleState(ConveyorActor actor) : State<ConveyorActor>(actor)
{
public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case "Load":
Actor.State = new LoadingState(Actor);
return true;
case "Unload":
Actor.State = new UnloadingState(Actor);
return true;
}
return false;
}
}

자세한 내용은 상태 관리를 참조하세요.

피어 통신

액터는 상태메시지를 통해 통신합니다:

// Read peer status
bool ready = GetPeerStatus(NextStage, "Ready") is true;

// Set status for peers to read
SetStatus("Loaded", true);

// Send message to peer
NextStage.Publish(new Message("Transfer"));

자세한 내용은 메시지 전달을 참조하세요.

일반적인 액터 패턴

오케스트레이터 패턴

Syncer 액터가 여러 액터를 조정합니다:

public class SyncerActor : Actor
{
public IActor Head, Stage, Conveyor;

// Grant permission to actors
public void GrantTransfer(IActor requestor)
{
SetStatus("Grant", requestor.Name);
}
}

// Actors check for permission
if (GetPeerStatus(Syncer, "Grant") == Name)
{
// I have permission to act
}

생산자-소비자 패턴

액터가 재료 흐름을 조정합니다:

// Producer (Conveyor)
if (Exists.Value && NextStageReady())
{
SetStatusByActor(Syncer, "ReadyToUnload", trackingId);
}

// Consumer (Stage)
if (!HasMaterial && PrevStageReady())
{
SetStatusByActor(Syncer, "ReadyToLoad", trackingId);
}

// Orchestrator matches producer with consumer
if (HasReadyProducer() && HasReadyConsumer())
{
GrantTransfer(producer, consumer);
}

안전 검사 패턴

액터는 작업 전에 전제 조건을 확인합니다:

public void EnsureReady()
{
if (GetPeerStatus(Syncer, "_auto") is not true)
throw new SequenceError("System not in auto mode");

if (HasPeerFailed(Syncer))
throw new SequenceError("Peer actor has failed");

if (!SensorIn.IsOnOrTrue())
{
ErrorDialog.Show();
throw new SequenceError("Safety sensor not detected");
}
}

모범 사례

1. 단일 책임

각 액터는 명확하고 집중된 목적을 가져야 합니다:

  • ConveyorActor - 재료 운송만 처리합니다
  • MachineActor - 너무 광범위하며 모든 것을 수행합니다

2. 명시적 피어 관계

피어 액터를 명시적으로 정의합니다:

public IActor Syncer = null!;  // Required orchestrator
public IActor? NextStage; // Optional downstream actor

3. 방어적 상태 검사

작업 전에 항상 상태를 확인합니다:

public void Load()
{
if (Exists.Value)
throw new SequenceError("Already have material");
// ... proceed with load
}

4. 의미 있는 상태 키

설명적인 상태 키를 사용합니다:

SetStatus("ReadyToTransfer", true);     // ✅ Clear intent
SetStatus("Flag1", true); // ❌ Unclear meaning

5. 우아한 초기화

초기화 실패를 처리합니다:

public override void Init(ActorConfig config)
{
base.Init(config);
X.SetInitializeAction(() =>
{
try
{
InitializeAxis(X);
}
catch (Exception ex)
{
InitError.Show($"Failed to initialize X axis: {ex.Message}");
throw;
}
});
}

참고