변수 시스템
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);