AgentSkillsCN

sequence-development

使用Station Service SDK开发测试序列指南。提供SequenceBase模式、emit方法、manifest.yaml编写方法。当用户询问序列开发、SequenceBase实现、测试自动化代码编写、manifest.yaml设置、emit方法使用时启用。(项目)

SKILL.md
--- frontmatter
name: sequence-development
description: Station Service SDK를 사용한 테스트 시퀀스 개발 가이드. SequenceBase 패턴, emit 메서드, manifest.yaml 작성법 제공. 사용자가 시퀀스 개발, SequenceBase 구현, 테스트 자동화 코드 작성, manifest.yaml 설정, emit 메서드 사용법을 문의할 때 활성화. (project)
allowed-tools: Read, Grep, Glob

Station Service SDK 2.0 Guide

Station Service SDK를 사용한 테스트 시퀀스 개발 가이드입니다.

Package Structure

code
station_service_sdk/
├── core/           # Core components (SequenceBase, Context, Protocol, Exceptions)
├── execution/      # Execution components (Loader, Registry, Simulator, Manual)
├── hardware/       # Hardware integration (Connection Pool, Retry, Health)
├── testing/        # Testing utilities (Mocks, Fixtures, Assertions)
├── observability/  # Observability (Logging, Tracing, Metrics)
├── plugins/        # Plugin system (Manager, Protocol Adapters)
├── cli/            # CLI tools (new, validate, run, debug, lint, doctor)
└── compat/         # Backward compatibility (Decorators, Dependencies)

Quick Start

python
from station_service_sdk import SequenceBase, RunResult

class MySequence(SequenceBase):
    name = "my_sequence"
    version = "1.0.0"
    description = "테스트 시퀀스"

    async def setup(self) -> None:
        """하드웨어 초기화"""
        self.emit_log("info", "초기화 중...")
        config = self.get_hardware_config("device")
        # 하드웨어 연결 로직

    async def run(self) -> RunResult:
        """테스트 실행"""
        total_steps = 2

        # Step 1
        self.emit_step_start("init", 1, total_steps, "초기화")
        # ... 로직
        self.emit_step_complete("init", 1, True, 1.5)

        # Step 2
        self.emit_step_start("measure", 2, total_steps, "측정")
        value = 3.28
        self.emit_measurement("voltage", value, "V", min_value=3.0, max_value=3.6)
        self.emit_step_complete("measure", 2, True, 2.0)

        return {"passed": True, "measurements": {"voltage": value}}

    async def teardown(self) -> None:
        """리소스 정리"""
        self.emit_log("info", "정리 완료")

if __name__ == "__main__":
    exit(MySequence.run_from_cli())

CLI 도구

명령어 목록

bash
# 새 시퀀스 생성
station-sdk new my-sequence                    # 기본 템플릿
station-sdk new my-sequence --template hardware # 하드웨어 템플릿
station-sdk new my-sequence --template multi-step # 다단계 템플릿

# 시퀀스 검증 (업로드 전 필수!)
station-sdk validate .
station-sdk validate ./my-sequence

# 시퀀스 실행
station-sdk run . --dry-run                    # 드라이런 모드
station-sdk run . -p voltage=3.3               # 파라미터 설정
station-sdk run . --verbose                    # 상세 출력

# 디버그 모드
station-sdk debug . --step-by-step             # 단계별 실행
station-sdk debug . -b measure                 # 브레이크포인트 설정

# 코드 품질 검사
station-sdk lint .
station-sdk lint . --fix                       # 자동 수정

# 의존성 검사
station-sdk deps .
station-sdk deps . --install                   # 누락 패키지 설치

# 환경 진단
station-sdk doctor

# 플러그인 목록
station-sdk plugins

# manifest 스키마 출력
station-sdk schema --format json > manifest.schema.json

# SDK 초기화
station-sdk init

# 버전 확인
station-sdk --version

Lifecycle Steps

SDK는 setup()teardown()을 자동으로 UI 스텝으로 emit합니다.

동작 방식

  • setup() 시작 시 자동으로 step 0으로 emit
  • run() 스텝들은 step 1부터 시작
  • teardown() 완료 시 마지막 step으로 emit
  • 총 스텝 수 = run 스텝 수 + 2 (setup + teardown)

manifest.yaml에 lifecycle 스텝 정의

yaml
steps:
  - name: setup
    display_name: "Setup"
    order: 0
    lifecycle: true  # SDK 자동 관리
  - name: init
    display_name: "초기화"
    order: 1
  - name: measure
    display_name: "측정"
    order: 2
  - name: teardown
    display_name: "Teardown"
    order: 3
    lifecycle: true  # SDK 자동 관리

lifecycle: true 스텝은 직접 emit하지 않아도 SDK가 자동 처리합니다.


emit_* 메서드

시퀀스 실행 중 상태를 보고하는 메서드들입니다.

메서드용도예시
emit_log(level, msg)로그 출력emit_log("info", "연결됨")
emit_step_start(name, idx, total, desc)스텝 시작emit_step_start("init", 1, 3, "초기화")
emit_step_complete(name, idx, passed, dur)스텝 완료emit_step_complete("init", 1, True, 2.0)
emit_measurement(name, val, unit, ...)측정값 기록emit_measurement("V", 3.3, "V", min_value=3.0)
emit_error(code, msg, recoverable)에러 보고emit_error("E001", "실패", False)

emit_measurement 상세

python
self.emit_measurement(
    name="voltage",
    value=3.28,
    unit="V",
    passed=None,        # None이면 자동 판정
    min_value=3.0,      # 최소값 (optional)
    max_value=3.6       # 최대값 (optional)
)

manifest.yaml

시퀀스 패키지 설정 파일입니다.

yaml
name: my_sequence
version: "1.0.0"
author: "Developer"
description: "시퀀스 설명"

entry_point:
  module: sequence
  class: MySequence

modes:
  automatic: true     # 자동 순차 실행
  manual: false       # 수동 단계별 실행
  interactive: false  # 실행 중 프롬프트
  cli: true           # CLI 기반 실행 (SDK 모드)

hardware:
  device:
    display_name: "장치명"
    driver: drivers.my_device
    class: MyDriver
    config_schema:
      port:
        type: string
        required: true
        default: "/dev/ttyUSB0"
      baudrate:
        type: integer
        default: 115200

parameters:
  timeout:
    display_name: "타임아웃"
    type: float
    default: 30.0
    min: 1.0
    max: 300.0
    unit: "s"

steps:
  - name: init
    display_name: "초기화"
    order: 1
    timeout: 30.0
  - name: measure
    display_name: "측정"
    order: 2
    timeout: 60.0

dependencies:
  python:
    - pyserial>=3.5

hardware 섹션 규칙

주의: hardware 정의 시 driverclass 필드는 필수입니다.

yaml
# 올바른 예시
hardware:
  device:
    display_name: "장치명"
    driver: drivers.my_device    # 필수
    class: MyDriver              # 필수
    config_schema: ...

# 잘못된 예시 (Pydantic 검증 실패)
hardware:
  device:
    display_name: "장치명"
    driver: null                 # null 불가
    # class 누락                 # 필수 필드 누락

CLI 기반 시퀀스 (외부 프로그램 직접 호출):

  • 하드웨어 드라이버가 필요 없으면 hardware 섹션을 생략
  • driver: null은 유효하지 않음
yaml
# CLI 기반 시퀀스 예시 (hardware 섹션 없음)
name: stm32_firmware_upload
version: "1.0.0"

entry_point:
  module: sequence
  class: STM32FirmwareUpload

modes:
  automatic: true
  manual: true
  cli: true

# hardware 섹션 생략 - STM32CubeProgrammer CLI 직접 사용

parameters:
  firmware_path:
    display_name: "펌웨어 경로"
    type: string
    required: true

modes 섹션

yaml
modes:
  automatic: true     # 자동 순차 실행 (기본)
  manual: false       # 수동 단계별 실행 (ManualSequenceExecutor 필요)
  interactive: false  # 실행 중 사용자 입력 프롬프트
  cli: true           # CLI 기반 서브프로세스 실행

예외 클래스

SDK에서 제공하는 예외 클래스 계층:

python
from station_service_sdk import (
    # 기본
    SequenceError,          # 모든 시퀀스 예외의 베이스

    # Lifecycle
    SetupError,             # 초기화 실패
    TeardownError,          # 정리 실패

    # Execution
    StepError,              # 스텝 실행 오류
    SequenceTimeoutError,   # 타임아웃 (권장)
    TimeoutError,           # 타임아웃 (하위호환 별칭)
    AbortError,             # 사용자/시스템 중단

    # Test results
    TestFailure,            # 테스트 실패 (측정값 범위 벗어남 등)
    TestSkipped,            # 테스트 스킵됨

    # Hardware
    HardwareError,          # 하드웨어 오류 베이스
    HardwareConnectionError,# 연결 오류 (권장)
    ConnectionError,        # 연결 오류 (하위호환 별칭)
    CommunicationError,     # 통신 오류

    # Package/Manifest
    PackageError,           # 패키지 구조 오류
    ManifestError,          # manifest 파싱/검증 오류

    # Validation
    ValidationError,        # 파라미터/설정 검증 오류
    DependencyError,        # 의존성 누락/비호환
)

# 사용 예
async def setup(self) -> None:
    try:
        await self.device.connect()
    except Exception as e:
        raise SetupError(f"연결 실패: {e}")

예외 상세 정보

모든 예외는 다음 속성을 가집니다:

  • message: 사람이 읽을 수 있는 오류 메시지
  • code: 기계가 읽을 수 있는 오류 코드 (분류용)
  • details: 추가 컨텍스트 딕셔너리
python
try:
    ...
except HardwareError as e:
    print(f"Code: {e.code}")      # "HARDWARE_ERROR"
    print(f"Message: {e.message}")
    print(f"Details: {e.details}")

에러 상태 접근자

실행 중 발생한 에러에 접근할 수 있는 프로퍼티들입니다.

프로퍼티타입설명
setup_errorOptional[str]Setup 단계 에러 메시지
run_errorOptional[str]Run 단계 에러 메시지
teardown_errorOptional[str]Teardown 단계 에러 메시지
last_errorOptional[str]가장 최근 에러 (teardown → run → setup 순)
setup_exceptionOptional[Exception]Setup 예외 객체
run_exceptionOptional[Exception]Run 예외 객체
teardown_exceptionOptional[Exception]Teardown 예외 객체
last_exceptionOptional[Exception]가장 최근 예외 객체

활용 예시

python
async def teardown(self) -> None:
    # 이전 단계 에러 확인
    if self.last_error:
        self.emit_log("warning", f"에러 발생: {self.last_error}")
        # 실패 시 추가 진단 수집
        if self.mcu and hasattr(self.mcu, 'get_diagnostics'):
            diag = await self.mcu.get_diagnostics()
            self.emit_log("debug", f"MCU 진단: {diag}")

    # 정리 로직...
    await self.mcu.disconnect()

LifecycleHook 인터페이스

시퀀스 실행 이벤트에 커스텀 동작을 추가할 수 있습니다.

python
from station_service_sdk import SequenceBase, LifecycleHook, CompositeHook
from station_service_sdk.core import ExecutionContext, Measurement

class LoggingHook(LifecycleHook):
    """로깅 hook 예시"""

    async def on_setup_start(self, context: ExecutionContext) -> None:
        print(f"Setup starting: {context.execution_id}")

    async def on_setup_complete(self, context: ExecutionContext, error: Optional[Exception] = None) -> None:
        print(f"Setup complete, error: {error}")

    async def on_step_start(self, context: ExecutionContext, step_name: str, index: int, total: int) -> None:
        print(f"Step {index}/{total}: {step_name} starting")

    async def on_step_complete(self, context: ExecutionContext, step_name: str, index: int, passed: bool, duration: float, error: Optional[str] = None) -> None:
        print(f"Step {step_name}: {'PASS' if passed else 'FAIL'} ({duration:.2f}s)")

    async def on_measurement(self, context: ExecutionContext, measurement: Measurement) -> None:
        print(f"Measurement: {measurement.name}={measurement.value}{measurement.unit}")

    async def on_error(self, context: ExecutionContext, error: Exception, phase: str) -> None:
        print(f"Error in {phase}: {error}")

    async def on_sequence_complete(self, context: ExecutionContext, result: dict) -> None:
        print(f"Sequence complete: {'PASS' if result['passed'] else 'FAIL'}")

# 사용
sequence = MySequence(
    context=context,
    hooks=[LoggingHook(), AnotherHook()],
)

OutputStrategy 인터페이스

출력 형식을 커스터마이징할 수 있습니다.

python
from station_service_sdk import OutputStrategy

class CustomOutput(OutputStrategy):
    """커스텀 출력 전략"""

    def log(self, level: str, message: str, **extra) -> None:
        # 커스텀 로깅
        pass

    def status(self, status: str, progress: float, step: str = None, message: str = None) -> None:
        # 상태 업데이트
        pass

    def step_start(self, step_name: str, index: int, total: int, description: str = "") -> None:
        # 스텝 시작
        pass

    def step_complete(self, step_name: str, index: int, passed: bool, duration: float, **kwargs) -> None:
        # 스텝 완료
        pass

    def measurement(self, name: str, value: Any, unit: str = "", **kwargs) -> None:
        # 측정값
        pass

    def error(self, code: str, message: str, **kwargs) -> None:
        # 에러
        pass

# 사용
sequence = MySequence(
    context=context,
    output_strategy=CustomOutput(),
)

Testing Utilities

테스트 코드 작성을 위한 유틸리티입니다.

기본 테스트

python
import pytest
from station_service_sdk.testing import (
    create_test_context,
    CapturedOutput,
    assert_sequence_passed,
    assert_step_passed,
    assert_measurement_in_range,
)

@pytest.mark.asyncio
async def test_sequence_passes():
    """시퀀스가 성공적으로 완료되는지 테스트"""
    context = create_test_context(sequence_name="my_sequence")
    output = CapturedOutput()

    sequence = MySequence(
        context=context,
        output_strategy=output,
    )

    result = await sequence._execute()

    assert_sequence_passed(output)
    assert result["passed"] is True

Mock Driver

python
from station_service_sdk.testing import MockDriver, MockDriverBuilder

# 빌더 패턴으로 모의 드라이버 생성
mock_device = (
    MockDriverBuilder()
    .with_method("connect", return_value=True)
    .with_method("measure_voltage", return_value=3.3)
    .with_method("disconnect", return_value=None)
    .build()
)

# 시퀀스에 주입
sequence = MySequence(
    context=context,
    hardware_config={"device": mock_device},
)

Assertions

python
# 시퀀스 전체 결과 검증
assert_sequence_passed(output)

# 특정 스텝 검증
assert_step_passed(output, "measure")
assert_step_failed(output, "calibration")

# 측정값 범위 검증
assert_measurement_in_range(
    output,
    name="voltage",
    min_value=3.0,
    max_value=3.6
)

Hardware Module

하드웨어 연결 관리 기능입니다.

Connection Pool

python
from station_service_sdk.hardware import (
    HardwareConnectionPool,
    ConnectionConfig,
)

# 연결 풀 생성
pool = HardwareConnectionPool()

# 연결 설정
config = ConnectionConfig(
    name="device",
    driver_path="drivers.my_device",
    driver_class="MyDriver",
    max_connections=3,
    connection_timeout=10.0,
)

# 연결 획득/반환
async with pool.acquire("device") as conn:
    await conn.measure_voltage()

Retry Strategy

python
from station_service_sdk.hardware import (
    with_retry,
    ExponentialBackoff,
    RetryStrategy,
)

# 데코레이터로 재시도 추가
@with_retry(max_attempts=3, backoff=ExponentialBackoff(base=0.5))
async def measure_voltage(self):
    return await self.device.measure()

# 커스텀 재시도 전략
class MyRetryStrategy(RetryStrategy):
    def should_retry(self, error: Exception, attempt: int) -> bool:
        return isinstance(error, CommunicationError) and attempt < 3

    def get_delay(self, attempt: int) -> float:
        return attempt * 0.5

Health Monitor

python
from station_service_sdk.hardware import (
    HealthCheckable,
    HealthCheckResult,
    HealthMonitor,
)

class MyDevice(HealthCheckable):
    async def health_check(self) -> HealthCheckResult:
        try:
            response = await self.ping()
            return HealthCheckResult(
                healthy=True,
                latency_ms=response.latency,
            )
        except Exception as e:
            return HealthCheckResult(
                healthy=False,
                error=str(e),
            )

# 모니터 사용
monitor = HealthMonitor(check_interval=5.0)
monitor.register("device", my_device)
await monitor.start()

Simulator & Interactive Mode

Dry Run Simulation

python
from station_service_sdk import SequenceSimulator

# 시뮬레이터 생성
simulator = SequenceSimulator(sequence_loader)

# 드라이런 실행
result = await simulator.dry_run(
    sequence_name="my_sequence",
    parameters={"voltage": 3.3},
)

print(f"Status: {result['status']}")
print(f"Steps: {result['steps']}")
print(f"Overall Pass: {result['overall_pass']}")

Interactive Mode

python
from station_service_sdk import InteractiveSimulator, SimulationSession

# 인터랙티브 세션 시작
simulator = InteractiveSimulator()
session: SimulationSession = await simulator.create_session(
    sequence_name="my_sequence",
)

# 단계별 실행
await session.execute_step("init")
await session.execute_step("measure")

# 세션 상태 확인
print(f"Status: {session.status}")
print(f"Steps: {session.get_step_states()}")

Manual Execution

수동 실행 모드 지원입니다.

python
from station_service_sdk import (
    ManualSequenceExecutor,
    ManualSession,
    ManualStepState,
)

# 수동 실행기 생성
executor = ManualSequenceExecutor(sequence_loader)

# 세션 시작
session: ManualSession = await executor.create_session(
    sequence_name="my_sequence",
    hardware_config=config,
)

# 하드웨어 연결
await session.connect_hardware()

# 스텝 실행
step_result = await session.execute_step("init")
print(f"Step result: {step_result}")

# 수동 명령 실행
cmd_result = await session.execute_command(
    hardware_name="device",
    command="measure_voltage",
    params={},
)

# 세션 종료
await session.close()

의존성 관리

pyproject.toml 방식 (권장)

toml
# sequences/my_sequence/pyproject.toml
[project]
name = "my-sequence"
version = "1.0.0"
dependencies = [
    "pyserial>=3.5,<4.0",
    "numpy>=1.20.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

manifest.yaml 방식

yaml
dependencies:
  python:
    - pyserial>=3.5
    - numpy>=1.20.0

SDK 함수 사용

python
from station_service_sdk import (
    ensure_package,
    ensure_dependencies,
    is_installed,
    get_missing_packages,
    install_sequence_dependencies,
)

# 단일 패키지 확인 및 설치
ensure_package("pyserial")
import serial

# 여러 패키지
results = ensure_dependencies(["pyserial", "numpy"])
if all(results.values()):
    import serial
    import numpy

# pyproject.toml에서 의존성 설치
from pathlib import Path
installed = install_sequence_dependencies(Path("sequences/my_sequence"))

실패 시 중단 (stop_on_failure)

manifest.yaml

yaml
parameters:
  stop_on_failure:
    display_name: "실패 시 중단"
    type: boolean
    default: true
    description: "스텝 실패 시 즉시 시퀀스 중단"

sequence.py

python
def __init__(self, ...):
    super().__init__(...)
    self.stop_on_failure = self.get_parameter("stop_on_failure", True)

async def run(self) -> RunResult:
    measurements = {}

    # Step 1
    try:
        self.emit_step_start("init", 1, 2, "초기화")
        # ... 로직
        self.emit_step_complete("init", 1, True, 1.0)
    except Exception as e:
        self.emit_step_complete("init", 1, False, 1.0, error=str(e))
        if self.stop_on_failure:
            return {"passed": False, "measurements": measurements,
                    "data": {"stopped_at": "init"}}

    # Step 2 (stop_on_failure=True면 여기까지 오지 않음)
    ...

유틸리티 메서드

python
# 파라미터 가져오기
timeout = self.get_parameter("timeout", default=30.0)

# 하드웨어 설정 가져오기
config = self.get_hardware_config("device")
port = config.get("port", "/dev/ttyUSB0")

# 중단 체크 (중단 요청 시 AbortError 발생)
self.check_abort()

# 강제 중단
self.abort("사유")

# 사용자 확인 요청 (interactive 모드)
confirmed = await self.request_confirmation("계속하시겠습니까?", timeout=300)

# 사용자 입력 요청 (interactive 모드)
value = await self.request_input(
    prompt="값을 입력하세요",
    input_type="number",  # confirm, text, number, select
    default=10,
    timeout=300,
)

폴더 구조

code
my_sequence/
├── pyproject.toml     # Python 패키지 설정 (권장)
├── manifest.yaml      # 시퀀스 설정 (필수)
├── README.md          # 문서
├── my_sequence/       # 모듈 디렉토리
│   ├── __init__.py    # SequenceBase 구현 (필수)
│   └── drivers/       # 하드웨어 드라이버
│       ├── __init__.py
│       └── my_device.py
└── tests/             # 테스트
    ├── __init__.py
    └── test_sequence.py

타입 정의

python
from station_service_sdk import (
    # 결과 타입
    RunResult,
    ExecutionResult,
    SimulationResult,

    # 측정/스텝 타입
    MeasurementDict,
    StepResultDict,
    StepMeta,
    StepInfo,

    # 설정 타입
    HardwareConfigDict,
    ParametersDict,
    MeasurementsDict,

    # Enum 타입
    ExecutionPhase,
    LogLevel,
    SimulationStatus,
    InputType,
)

async def run(self) -> RunResult:
    return {
        "passed": True,
        "measurements": {"voltage": 3.3},
        "data": {"device_id": "ABC123"}
    }

체크리스트

필수

  • SequenceBase 상속
  • name, version, description 클래스 속성 정의
  • setup(), run(), teardown() 구현
  • manifest.yaml 작성
  • run() 메서드가 RunResult 반환
  • station-sdk validate 실행하여 검증 통과

권장

  • 적절한 emit_step_start/complete 호출
  • 측정값에 emit_measurement 사용
  • 예외 발생 시 SDK 예외 클래스 사용
  • check_abort() 호출로 중단 요청 처리
  • manifest.yaml에 setup/teardown 스텝 정의
  • stop_on_failure 파라미터로 실패 시 동작 제어
  • pyproject.toml 작성하여 의존성 정의
  • 테스트 코드 작성 (station_service_sdk.testing 활용)

Validation 검사 항목

station-sdk validate가 검사하는 항목들:

검사 항목설명
YAML 문법manifest.yaml 파싱 가능 여부
스키마 검증Pydantic 모델 규격 준수
엔트리포인트module.py 파일 및 class 존재 여부
스텝 이름 매칭manifest steps ↔ emit_step_start() 일치
하드웨어 드라이버driver 파일 존재 여부
의존성 설치 (manifest)dependencies.python 패키지 설치 여부
의존성 설치 (pyproject.toml)pyproject.toml의 dependencies 설치 여부

스텝 이름 검증 예시

code
✗ Step name mismatch detected:
   → "sensor_test" emitted in sequence but not defined in manifest
   → "init" defined in manifest but not used in sequence

   Hint: manifest.yaml의 steps에 실제 emit하는 step 이름을 정의하세요.

템플릿 종류

basic

기본적인 2단계 시퀀스 템플릿

hardware

하드웨어 드라이버 통합 시퀀스 템플릿

multi-step

동적 스텝 실행 패턴의 다단계 시퀀스 템플릿

bash
station-sdk new my-sequence --template multi-step