본문으로 건너뛰기

변수 시스템

ControlBee의 변수 시스템은 지속성과 변경 추적 기능을 갖춘 타입 안전하고 범위 인식 상태 관리를 제공합니다.

변수 선언

변수는 세 가지 범위 수준을 가진 강력한 타입입니다:

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

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

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

변수 범위

전역 범위

목적: 모든 레시피에서 공유되는 구성

특성:

  • 모든 레시피에서 공유됨 (모든 제품에서 동일한 값)
  • 값이 변경될 때 자동 저장됨 (AutoVariableSave가 활성화된 경우)
  • DiscardChanges()를 통해 저장되지 않은 변경사항을 되돌릴 수 있음
  • UI를 통해 구성 가능
  • 애플리케이션 재시작 시에도 유지됨

사용 시기:

  • 기계 수준 구성 (안전 마진, 홈 속도)
  • 시스템 전체 매개변수 (통신 타임아웃, 재시도 횟수)
  • 제품에 따라 변하지 않는 공통 설정
  • 현재 레시피와 무관하게 동일해야 하는 모든 매개변수

예제:

// 기계 수준 설정
public Variable<double> SafetyMargin = new(VariableScope.Global, 5.0);
public Variable<int> HomeSpeed = new(VariableScope.Global, 10);

// 시스템 전체 매개변수
public Variable<int> CommunicationTimeout = new(VariableScope.Global, 5000);
public Variable<int> MaxRetryCount = new(VariableScope.Global, 3);

// 공통 구성
public Variable<string> MachineName = new(VariableScope.Global, "Machine1");
public Variable<bool> EnableDebugMode = new(VariableScope.Global, false);

로컬 범위

목적: 제품별로 다른 레시피별 매개변수

특성:

  • 각 레시피마다 다른 값 (Recipe1, Recipe2 등)
  • 값이 변경될 때 자동 저장됨 (AutoVariableSave가 활성화된 경우)
  • Load(recipeName)을 통해 레시피를 전환할 때 로드됨
  • DiscardChanges()를 통해 저장되지 않은 변경사항을 되돌릴 수 있음
  • UI를 통해 구성 가능

사용 시기:

  • 제품마다 다른 위치 변수
  • 레시피별 공정 매개변수
  • 제품별 치수 또는 오프셋
  • 레시피마다 다른 값이 필요한 모든 매개변수

예제:

// 제품별 위치
public Variable<double> LoadSpeed = new(VariableScope.Local, 100.0);
public Variable<Position1D> LoadPosX = new(VariableScope.Local);
public Variable<Position3D> PickPosXYZ = new(VariableScope.Local);

// 레시피별 공정 매개변수
public Variable<int> CycleCount = new(VariableScope.Local, 10);
public Variable<double> Temperature = new(VariableScope.Local, 25.0);

// 제품 치수
public Variable<int> RowCount = new(VariableScope.Local, 5);
public Variable<int> ColCount = new(VariableScope.Local, 8);

임시 범위

목적: 임시 런타임 상태

특성:

  • 자동 저장되지 않음 - SaveTemporaryVariables()를 명시적으로 호출해야 함
  • DiscardChanges()나 레시피 전환의 영향을 받지 않음
  • (수동 저장 시) 애플리케이션 재시작 시에도 유지됨
  • 임시 실행 상태에 사용됨

사용 시기:

  • 런타임 실행 상태 (Busy, Error 플래그)
  • 임시 작업 추적
  • 자동 지속성이 필요하지 않은 일시적 데이터
  • 자동 저장 작업을 트리거하지 않아야 하는 상태

예제:

// 런타임 상태
public Variable<bool> Busy = new(VariableScope.Temporary);
public Variable<bool> Exists = new(VariableScope.Temporary);

// 임시 작업 추적
public Variable<GridContainer> Tray = new(VariableScope.Temporary);
public Variable<int> CurrentIndex = new(VariableScope.Temporary);

지원되는 타입

스칼라 타입

public Variable<int> Count = new(VariableScope.Local, 0);
public Variable<double> Value = new(VariableScope.Local, 0.0);
public Variable<bool> Flag = new(VariableScope.Local, false);
public Variable<string> Name = new(VariableScope.Local, "");

위치 타입

// 1D position (mapped to single axis)
public Variable<Position1D> TargetX = new(VariableScope.Local);

// Multi-axis positions
public Variable<Position2D> TargetXY = new(VariableScope.Local);
public Variable<Position3D> TargetXYZ = new(VariableScope.Local);

배열 타입

// 1D arrays
public Variable<Array1D<bool>> Flags =
new(VariableScope.Local, new Array1D<bool>([true, false, true]));
public Variable<Array1D<Position1D>> Positions =
new(VariableScope.Local, new Array1D<Position1D>(5));

// 2D arrays (grids)
public Variable<Array2D<bool>> Grid =
new(VariableScope.Local, new Array2D<bool>(5, 8));

사용자 정의 컨테이너 타입

// Complex state container
public Variable<GridContainer> Tray = new(VariableScope.Temporary);

public class GridContainer : PropertyVariable
{
public Array2D<bool> CellExists { get; set; }
public Array2D<bool> WorkDone { get; set; }
public Array2D<VisionResult> CellVisionResult { get; set; }

public GridContainer(int rows, int cols)
{
CellExists = new Array2D<bool>(rows, cols);
WorkDone = new Array2D<bool>(rows, cols);
CellVisionResult = new Array2D<VisionResult>(rows, cols);
}
}

변수 작업

값 가져오기 및 설정

// Get value
double currentSpeed = Speed.Value;
bool hasMaterial = Exists.Value;

// Set value
Speed.Value = 150.0;
Exists.Value = true;

// Array access
bool cellDone = WorkGrid.Value[row, col];
WorkGrid.Value[row, col] = true;

변경 알림

변수 변경을 구독합니다:

public StageActor(ActorConfig config) : base(config)
{
// Subscribe to changes
Tray.ValueChanged += OnTrayChanged;
RowCount.ValueChanged += OnLayoutChanged;
}

private void OnTrayChanged(object? sender, ValueChangedArgs e)
{
var oldTray = (GridContainer)e.OldValue!;
var newTray = (GridContainer)e.NewValue!;

Console.WriteLine($"Tray changed from {oldTray.RowCount}x{oldTray.ColCount} " +
$"to {newTray.RowCount}x{newTray.ColCount}");

UpdateStatus();
}

private void OnLayoutChanged(object? sender, ValueChangedArgs e)
{
int oldCount = (int)e.OldValue!;
int newCount = (int)e.NewValue!;

// Reinitialize grid
Tray.Value = new GridContainer(newCount, ColCount.Value);
}

위치-축 매핑

위치 변수는 물리적 축에 매핑됩니다:

public class StageActor : Actor
{
public IAxis X, Z;

public Variable<Position1D> LoadPosX = new(VariableScope.Local);
public Variable<Position1D> UnloadPosX = new(VariableScope.Local);
public Variable<Position1D> LoadPosZ = new(VariableScope.Local);

public StageActor(ActorConfig config) : base(config)
{
X = config.AxisFactory.Create();
Z = config.AxisFactory.Create();

// Map positions to axes
PositionAxesMap.Add(LoadPosX, [X]);
PositionAxesMap.Add(UnloadPosX, [X]);
PositionAxesMap.Add(LoadPosZ, [Z]);
}
}

// Move using position variable
LoadPosX.Value.MoveAndWait(); // Moves X axis
UnloadPosX.Value.MoveAndWait(); // Moves same X axis to different position
LoadPosZ.Value.MoveAndWait(); // Moves Z axis

변수 지속성

변수 저장

// Save all Global variables for current recipe
variableManager.Save();

// Save to specific recipe
variableManager.Save("Recipe1");

변수 로딩

// Load variables for current recipe
variableManager.Load();

// Load specific recipe
variableManager.Load("Recipe1");

// Event notification
variableManager.LoadCompleted += (sender, recipeName) =>
{
Console.WriteLine($"Loaded recipe: {recipeName}");
};

레시피 관리

// Get available recipes
string[] recipes = variableManager.LocalNames;

// Delete a recipe
variableManager.Delete("OldRecipe");

// Rename a recipe
variableManager.RenameLocalName("Recipe1", "ProductA");

변수 변경 이력

감사를 위한 변경 사항 추적:

// Enable change tracking (automatic for Global variables)
variable.Dirty = true; // Marks as changed

// Query change history
var changes = variableManager.ReadVariableChanges(new QueryOptions
{
Filter = "ActorName = 'Stage0'",
OrderBy = "Timestamp DESC",
Limit = 100
});

// Columns: Timestamp, ActorName, ItemPath, OldValue, NewValue, UserName

변수 가시성

UI 가시성을 동적으로 제어합니다:

public class StageActor : Actor
{
public Variable<bool> UseVacuum = new(VariableScope.Local, true);
public Variable<double> VacuumPressure = new(VariableScope.Local, -80.0);

public override void Start()
{
base.Start();

// Hide vacuum parameters if not used
((IActorItemModifier)VacuumPressure).Visible = UseVacuum.Value;

// React to changes
UseVacuum.ValueChanged += (s, e) =>
{
((IActorItemModifier)VacuumPressure).Visible = (bool)e.NewValue!;
};
}
}

모범 사례

1. 올바른 범위 선택

// ✅ Global - User needs to configure
public Variable<double> Speed = new(VariableScope.Local, 100.0);

// ✅ Local - Fixed offset, no persistence needed
public Variable<double> SafetyMargin = new(VariableScope.Local, 5.0);

// ✅ Temporary - Execution state
public Variable<bool> Busy = new(VariableScope.Temporary);

// ❌ Wrong - Runtime state as Global (unnecessary persistence)
public Variable<bool> Busy = new(VariableScope.Local, false);

2. 의미 있는 기본값 제공

// ✅ Sensible default
public Variable<double> Speed = new(VariableScope.Local, 100.0);

// ❌ Missing default (confusing for users)
public Variable<double> Speed = new(VariableScope.Local);

3. 설명적인 이름 사용

public Variable<Position1D> LoadPosition = new(VariableScope.Local);  // ✅
public Variable<Position1D> Pos1 = new(VariableScope.Local); // ❌

4. 변경 유효성 검사

Speed.ValueChanged += (s, e) =>
{
double newSpeed = (double)e.NewValue!;
if (newSpeed < 0 || newSpeed > 200)
{
Speed.Value = (double)e.OldValue!; // Revert
ErrorDialog.Show("Speed must be between 0 and 200");
}
};

5. 관련 변수 그룹화

// ✅ Clear grouping
public Variable<Position1D> LoadPosX = new(VariableScope.Local);
public Variable<Position1D> LoadPosZ = new(VariableScope.Local);
public Variable<Position1D> UnloadPosX = new(VariableScope.Local);
public Variable<Position1D> UnloadPosZ = new(VariableScope.Local);

참고