장치 통합
ControlBee는 하드웨어 장치에 대한 팩토리 기반 추상화 계층을 제공하여 하드웨어 독립적인 액터 개발과 쉬운 장치 교체를 가능하게 합니다.
팩토리 패턴
장치는 팩토리 인터페이스를 통해 생성되어 액터를 특정 하드웨어 구현에서 분리합니다:
public class StageActor : Actor
{
public IAxis X, Z;
public IDigitalOutput Vacuum;
public IDigitalInput MaterialSensor;
public StageActor(ActorConfig config) : base(config)
{
// Create devices through factories - actual hardware type determined at runtime
X = config.AxisFactory.Create();
Z = config.AxisFactory.Create();
Vacuum = config.DigitalOutputFactory.Create();
MaterialSensor = config.DigitalInputFactory.Create();
}
}
이점:
- 하드웨어 독립성 - 동일한 액터 코드가 다른 하드웨어에서 작동합니다
- 쉬운 테스트 - 테스트를 위해 시뮬레이션 장치를 사용합니다
- 핫 스왑 - 액터 코드를 수정하지 않고 하드웨어를 변경합니다
- 시뮬레이션 모드 - 물리적 하드웨어 없이 실행합니다
장치 타입
모션 제어 (IAxis)
단일 모션 축을 나타냅니다:
public class AxisActor : Actor
{
public IAxis X;
public AxisActor(ActorConfig config) : base(config)
{
X = config.AxisFactory.Create();
}
public override void Start()
{
base.Start();
// Enable the axis
X.Enable = true;
// Move to position
X.TrapezoidalMove(position: 100, speed: 50, accel: 100, decel: 100);
// Wait for completion
X.Wait();
// Check status
Console.WriteLine($"Position: {X.ActualPosition}");
Console.WriteLine($"Moving: {X.IsMoving}");
Console.WriteLine($"Error: {X.Error}");
}
}
주요 속성:
X.Enable = true; // Enable/disable axis
double pos = X.CommandPosition; // Commanded position
double actual = X.ActualPosition; // Actual position from encoder
bool moving = X.IsMoving; // Is axis currently moving?
bool error = X.Error; // Is axis in error state?
주요 메서드:
// Trapezoidal move (non-blocking)
X.TrapezoidalMove(position: 100, speed: 50, accel: 100, decel: 100);
// Wait for move completion
X.Wait();
// Combined move and wait
X.TrapezoidalMoveAndWait(100, 50, 100, 100);
// Stop motion
X.Stop();
// Set position (homing)
X.SetCommandPosition(0);
디지털 I/O
디지털 입력 (IDigitalInput)
바이너리 센서 상태를 읽습니다:
public class SensorActor : Actor
{
public IDigitalInput MaterialSensor;
public IDigitalInput SafetyGate;
public SensorActor(ActorConfig config) : base(config)
{
MaterialSensor = config.DigitalInputFactory.Create();
SafetyGate = config.DigitalInputFactory.Create();
}
public void CheckSensors()
{
// Read sensor states
bool hasMaterial = MaterialSensor.Value;
bool gateOpen = SafetyGate.Value;
if (hasMaterial && !gateOpen)
{
StartProcessing();
}
}
}
디지털 출력 (IDigitalOutput)
바이너리 액추에이터를 제어합니다:
public class ActuatorActor : Actor
{
public IDigitalOutput Vacuum;
public IDigitalOutput Cylinder;
public IDigitalOutput StatusLight;
public ActuatorActor(ActorConfig config) : base(config)
{
Vacuum = config.DigitalOutputFactory.Create();
Cylinder = config.DigitalOutputFactory.Create();
StatusLight = config.DigitalOutputFactory.Create();
}
public void PickPart()
{
// Extend cylinder
Cylinder.Value = true;
Thread.Sleep(100); // Wait for position
// Turn on vacuum
Vacuum.Value = true;
Thread.Sleep(500); // Wait for vacuum
// Retract cylinder
Cylinder.Value = false;
// Indicate success
StatusLight.Value = true;
}
}
편의 메서드:
Vacuum.TurnOn(); // Same as: Vacuum.Value = true
Vacuum.TurnOff(); // Same as: Vacuum.Value = false
bool state = Vacuum.Value;
아날로그 I/O
아날로그 입력 (IAnalogInput)
연속 센서 값을 읽습니다:
public class SensorActor : Actor
{
public IAnalogInput PressureSensor;
public IAnalogInput TemperatureSensor;
public SensorActor(ActorConfig config) : base(config)
{
PressureSensor = config.AnalogInputFactory.Create();
TemperatureSensor = config.AnalogInputFactory.Create();
}
public void MonitorConditions()
{
double pressure = PressureSensor.Value; // Read in engineering units
double temperature = TemperatureSensor.Value;
Console.WriteLine($"Pressure: {pressure:F2} kPa");
Console.WriteLine($"Temperature: {temperature:F1} °C");
if (pressure < -80 || temperature > 60)
{
TriggerAlarm();
}
}
}
아날로그 출력 (IAnalogOutput)
연속 출력을 제어합니다:
public class ControlActor : Actor
{
public IAnalogOutput ValvePosition;
public IAnalogOutput HeaterPower;
public ControlActor(ActorConfig config) : base(config)
{
ValvePosition = config.AnalogOutputFactory.Create();
HeaterPower = config.AnalogOutputFactory.Create();
}
public void SetControlOutputs()
{
// Set valve to 50% open
ValvePosition.Value = 50.0;
// Set heater to 25% power
HeaterPower.Value = 25.0;
}
}
비전 시스템 (IVision)
이미지 캡처 및 처리:
public class VisionActor : Actor
{
public IVision Camera;
public VisionActor(ActorConfig config) : base(config)
{
Camera = config.VisionFactory.Create();
}
public VisionResult InspectPart()
{
// Trigger image capture
Camera.Trigger();
// Process image (implementation depends on vision system)
var result = Camera.GetResult();
Console.WriteLine($"X offset: {result.OffsetX}");
Console.WriteLine($"Y offset: {result.OffsetY}");
Console.WriteLine($"Angle: {result.Angle}");
Console.WriteLine($"Pass: {result.Pass}");
return result;
}
}
장치 구성
구성 딕셔너리
장치는 생성 중에 전달되는 문자열 딕셔너리를 사용하여 구성됩니다:
// In device initialization code
var axisConfig = new Dictionary<string, string>
{
["AxisNumber"] = "0",
["PulsePerUnit"] = "1000",
["HomeDirection"] = "Negative",
["HomeSpeed"] = "10",
["SoftwareLimitPositive"] = "300",
["SoftwareLimitNegative"] = "-10"
};
var axis = axisFactory.Create();
axis.Init(axisConfig);
일반 구성 매개변수
모션 축:
AxisNumber- 물리적 축 인덱스PulsePerUnit- 엔코더 해상도HomeDirection- 원점 복귀 방향 (Positive/Negative)HomeSpeed- 원점 복귀 속도SoftwareLimitPositive- 상한SoftwareLimitNegative- 하한
디지털 I/O:
DeviceIndex- 카드/장치 인덱스ChannelNumber- I/O 채널 번호Inverted- 논리 반전 (true/false)
아날로그 I/O:
DeviceIndex- 카드/장치 인덱스ChannelNumber- I/O 채널 번호Scale- 변환 계수Offset- 제로 오프셋MinValue- 최소값MaxValue- 최대값
장치 수명 주기
1. 생성
장치는 팩토리를 통해 생성됩니다:
public StageActor(ActorConfig config) : base(config)
{
// Factories provided by ActorConfig
X = config.AxisFactory.Create();
Vacuum = config.DigitalOutputFactory.Create();
}
2. 초기화
장치는 프레임워크에 의해 구성으로 초기화됩니다:
// Framework calls Init() with configuration
axis.Init(configDictionary);
// After Init(), device is ready to use
axis.Enable = true;
3. 사용
액터 수명 내내 장치를 사용합니다:
public override void Start()
{
base.Start();
// Device is initialized and ready
X.Enable = true;
X.TrapezoidalMoveAndWait(100, 50, 100, 100);
}
4. 정리
장치는 정리를 위해 IDisposable을 구현합니다:
public void Dispose()
{
// Framework calls Dispose() when actor is destroyed
X?.Dispose();
Vacuum?.Dispose();
}
사용자 정의 장치 생성
장치 드라이버 인터페이스
모든 장치는 IDevice를 구현합니다:
public interface IDevice : IDisposable
{
void Init(Dictionary<string, string> config);
}
예제: 사용자 정의 모션 장치
public class MyMotionController : IMotionDevice
{
private int axisNumber;
private double pulsePerUnit;
private IntPtr cardHandle; // Hardware-specific
public bool Enable { get; set; }
public double CommandPosition { get; private set; }
public double ActualPosition { get; private set; }
public bool IsMoving { get; private set; }
public bool Error { get; private set; }
public void Init(Dictionary<string, string> config)
{
// Parse configuration
axisNumber = int.Parse(config["AxisNumber"]);
pulsePerUnit = double.Parse(config["PulsePerUnit"]);
// Initialize hardware
cardHandle = NativeApi.OpenCard();
NativeApi.ConfigureAxis(cardHandle, axisNumber);
}
public void TrapezoidalMove(double position, double speed, double accel, double decel)
{
// Convert to hardware units
int targetPulse = (int)(position * pulsePerUnit);
int speedPulse = (int)(speed * pulsePerUnit);
// Send command to hardware
NativeApi.Move(cardHandle, axisNumber, targetPulse, speedPulse);
CommandPosition = position;
IsMoving = true;
}
public void Wait()
{
// Poll until motion complete
while (IsMoving)
{
int status = NativeApi.GetStatus(cardHandle, axisNumber);
IsMoving = (status & 0x01) != 0;
// Update actual position
int pulse = NativeApi.GetPosition(cardHandle, axisNumber);
ActualPosition = pulse / pulsePerUnit;
Thread.Sleep(10);
}
}
public void Stop()
{
NativeApi.StopAxis(cardHandle, axisNumber);
IsMoving = false;
}
public void SetCommandPosition(double position)
{
int pulse = (int)(position * pulsePerUnit);
NativeApi.SetPosition(cardHandle, axisNumber, pulse);
CommandPosition = position;
}
public void Dispose()
{
if (cardHandle != IntPtr.Zero)
{
NativeApi.CloseCard(cardHandle);
cardHandle = IntPtr.Zero;
}
}
}
예제: 사용자 정의 디지털 I/O 장치
public class MyDioCard : IDigitalIoDevice
{
private IntPtr cardHandle;
private int deviceIndex;
public void Init(Dictionary<string, string> config)
{
deviceIndex = int.Parse(config["DeviceIndex"]);
cardHandle = NativeApi.OpenDioCard(deviceIndex);
}
public bool GetDigitalInputBit(int channel)
{
return NativeApi.ReadInput(cardHandle, channel);
}
public void SetDigitalOutputBit(int channel, bool value)
{
NativeApi.WriteOutput(cardHandle, channel, value);
}
public void Dispose()
{
if (cardHandle != IntPtr.Zero)
{
NativeApi.CloseDioCard(cardHandle);
cardHandle = IntPtr.Zero;
}
}
}
시뮬레이션 장치
ControlBee는 테스트를 위한 시뮬레이션 장치를 제공합니다:
public class SimulatedAxis : IAxis
{
public bool Enable { get; set; }
public double CommandPosition { get; private set; }
public double ActualPosition { get; private set; }
public bool IsMoving { get; private set; }
public bool Error { get; private set; }
public void TrapezoidalMove(double position, double speed, double accel, double decel)
{
CommandPosition = position;
IsMoving = true;
// Simulate motion in background
Task.Run(() =>
{
Thread.Sleep(100); // Simulate move time
ActualPosition = position;
IsMoving = false;
});
}
public void Wait()
{
while (IsMoving)
{
Thread.Sleep(10);
}
}
// ... other methods
}
사용법:
// In initialization code, choose device based on mode
if (systemConfig.FakeMode)
{
axis = new SimulatedAxis();
}
else
{
axis = new RealMotionController();
}
장치 모니터링
장치 관리자 (IDeviceManager)
모든 하드웨어 장치의 중앙 관리:
// Access through system services
var deviceManager = actorSystem.GetService<IDeviceManager>();
// Get all devices
var allDevices = deviceManager.GetAllDevices();
// Get devices by type
var motionDevices = deviceManager.GetDevicesByType<IMotionDevice>();
var dioDevices = deviceManager.GetDevicesByType<IDigitalIoDevice>();
// Monitor device status
foreach (var device in motionDevices)
{
if (device.Error)
{
Console.WriteLine($"Device {device} in error state");
}
}
장치 모니터 (IDeviceMonitor)
디지털 입력의 실시간 모니터링:
public class MonitorActor : Actor
{
private IDeviceMonitor deviceMonitor;
public override void Start()
{
base.Start();
// Get device monitor service
deviceMonitor = GetService<IDeviceMonitor>();
// Monitor all digital inputs
deviceMonitor.InputChanged += OnInputChanged;
}
private void OnInputChanged(object sender, InputChangedEventArgs e)
{
Console.WriteLine($"Device {e.DeviceIndex}, Channel {e.Channel}: {e.Value}");
// React to input changes
if (e.Channel == 5 && e.Value)
{
HandleEmergencyStop();
}
}
}
모범 사례
1. 팩토리 사용, 직접 인스턴스화 금지
// ✅ Good - Uses factory
public StageActor(ActorConfig config) : base(config)
{
X = config.AxisFactory.Create();
}
// ❌ Bad - Direct instantiation
public StageActor(ActorConfig config) : base(config)
{
X = new SpecificMotionCard(); // Couples to specific hardware
}
2. 장치 상태 확인
// ✅ Good - Checks for errors
X.TrapezoidalMoveAndWait(100, 50, 100, 100);
if (X.Error)
{
HandleMotionError();
}
// ❌ Bad - Assumes success
X.TrapezoidalMoveAndWait(100, 50, 100, 100);
// Continues without checking
3. 사용하지 않을 때 축 비활성화
// ✅ Good - Disables when idle
public override void OnEntry(Message msg)
{
X.Enable = true;
}
public override void OnExit(Message msg)
{
X.Enable = false; // Disable when leaving state
}
// ❌ Bad - Always enabled
public override void OnEntry(Message msg)
{
X.Enable = true;
}
// Never disabled - wastes power, may overheat
4. 설명적인 장치 이름 사용
// ✅ Good - Clear purpose
public IAxis LoaderX;
public IAxis LoaderZ;
public IDigitalOutput VacuumGripper;
public IDigitalInput PartDetectSensor;
// ❌ Bad - Unclear purpose
public IAxis Axis1;
public IAxis Axis2;
public IDigitalOutput DO1;
public IDigitalInput DI3;
5. 하드웨어 초기화 오류 처리
// ✅ Good - Validates initialization
public StageActor(ActorConfig config) : base(config)
{
try
{
X = config.AxisFactory.Create();
if (X == null)
{
throw new Exception("Failed to create X axis");
}
}
catch (Exception ex)
{
LogError($"Axis initialization failed: {ex.Message}");
// Set error flag or use simulated device
}
}
6. 올바르게 해제
// ✅ Good - Disposes devices
public override void Dispose()
{
X?.Dispose();
Z?.Dispose();
Vacuum?.Dispose();
base.Dispose();
}
// ❌ Bad - Doesn't dispose
// Memory leaks, hardware resources not released
7. 테스트를 위한 시뮬레이션 사용
// ✅ Good - Can run without hardware
if (SystemConfig.FakeMode)
{
Console.WriteLine("Running in simulation mode");
}
// Test actor logic without physical devices
X.TrapezoidalMoveAndWait(100, 50, 100, 100); // Works in both real and sim