npm package: @samlab-corp/usb-relay-hid
Description: Advanced USB HID Relay controller for Node.js with auto-reconnect, handshake validation, and event-based monitoring
Author: samko
Latest version: 1.0.0

# USB Relay HID Controller

고급 USB HID 릴레이 제어 라이브러리로, 자동 재연결, 핸드셰이크 검증, 이벤트 기반 모니터링 기능을 제공합니다.

[![npm version](https://img.shields.io/npm/v/usb-relay-hid.svg)](https://www.npmjs.com/package/usb-relay-hid)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## ✨ 주요 기능

- 🔌 **자동 장치 검색 및 연결**: USB 릴레이 장치 자동 탐지
- 🔄 **자동 재연결**: 연결 끊김 시 자동으로 재연결 시도
- 🤝 **핸드셰이크 검증**: 장치 연결 시 통신 검증
- 📊 **실시간 상태 모니터링**: 릴레이 상태 폴링 및 이벤트 알림
- ⚡ **이벤트 기반 아키텍처**: EventEmitter 기반 비동기 처리
- 🛡️ **에러 복구**: 자동 재시도 및 에러 핸들링
- 📝 **상세한 로깅**: 디버깅을 위한 로그 시스템
- 🎯 **TypeScript 지원**: 타입 정의 포함

## 📦 설치

```bash
npm install usb-relay-hid
```

### 시스템 요구사항

- Node.js >= 14.0.0
- USB HID 릴레이 장치 (VID: 0x16c0, PID: 0x05df)

#### Windows
추가 설정 불필요

#### Linux
```bash
# USB 접근 권한 설정
sudo usermod -a -G plugdev $USER
```

#### macOS
```bash
# Xcode Command Line Tools 필요
xcode-select --install
```

## 🚀 빠른 시작

### 기본 사용법

```javascript
const USBRelayController = require('usb-relay-hid');

// 컨트롤러 인스턴스 생성
const relay = new USBRelayController({
  autoReconnect: true,
  reconnectInterval: 1000,  // 1초마다 재연결 시도
  enableLogging: true
});

// 장치 연결
async function main() {
  // 1. 장치 검색
  const devices = relay.enumerate();
  console.log(`발견된 장치: ${devices.length}개`);

  // 2. 첫 번째 장치 연결
  const success = await relay.open();

  if (success) {
    console.log('연결 성공!');

    // 3. 릴레이 제어
    await relay.openRelay(1);   // 릴레이 1번 열기
    await relay.closeRelay(1);  // 릴레이 1번 닫기

    // 4. 모든 릴레이 제어
    await relay.openAllRelays();
    await relay.closeAllRelays();

    // 5. 상태 확인
    const status = relay.getAllRelayStates();
    console.log('릴레이 상태:', status);
  }
}

main();
```

### 이벤트 기반 사용

```javascript
const relay = new USBRelayController();

// 연결 이벤트
relay.on('connected', (data) => {
  console.log('장치 연결됨:', data.deviceInfo);
  console.log('초기 상태:', data.status);
});

// 연결 끊김 이벤트
relay.on('disconnected', () => {
  console.log('장치 연결 끊김');
});

// 재연결 이벤트
relay.on('reconnecting', () => {
  console.log('재연결 시도 중...');
});

relay.on('reconnected', () => {
  console.log('재연결 성공!');
});

// 릴레이 상태 변경 이벤트
relay.on('relay-changed', (data) => {
  console.log(`릴레이 ${data.channel}: ${data.state ? '열림' : '닫힘'}`);
});

// 상태 폴링 이벤트
relay.on('status-changed', (data) => {
  console.log('상태 변경 감지:', {
    이전: data.oldStatus,
    현재: data.newStatus
  });
});

// 에러 이벤트
relay.on('error', (data) => {
  console.error('에러 발생:', data.error);
});

// 장치 연결
await relay.open();
```

## 📖 API 문서

### 생성자 옵션

```javascript
new USBRelayController(options)
```

| 옵션 | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `autoReconnect` | boolean | `true` | 자동 재연결 활성화 |
| `reconnectInterval` | number | `1000` | 재연결 시도 간격 (ms) |
| `maxRetries` | number | `3` | 명령 재시도 최대 횟수 |
| `commandTimeout` | number | `1000` | 명령 타임아웃 (ms) |
| `statusPollingInterval` | number | `0` | 상태 폴링 간격 (0=비활성화) |
| `enableLogging` | boolean | `true` | 로깅 활성화 |

### 메서드

#### 장치 관리

```javascript
// 장치 검색
relay.enumerate() // => Array<DeviceInfo>

// 장치 연결
await relay.open(serialNumber?) // => Promise<boolean>

// 장치 연결 해제
relay.close() // => void

// 연결 상태 확인
relay.isDeviceConnected() // => boolean

// 장치 정보 가져오기
relay.getDeviceInfo() // => DeviceInfo | null
```

#### 릴레이 제어

```javascript
// 특정 릴레이 열기 (켜기)
await relay.openRelay(channel) // channel: 1-8

// 특정 릴레이 닫기 (끄기)
await relay.closeRelay(channel) // channel: 1-8

// 모든 릴레이 열기
await relay.openAllRelays()

// 모든 릴레이 닫기
await relay.closeAllRelays()

// 릴레이 토글
await relay.toggleRelay(channel)
```

#### 상태 확인

```javascript
// 전체 상태 확인 (비트마스크)
relay.getStatus() // => number

// 특정 릴레이 상태
relay.getRelayStatus(channel) // => boolean | null

// 모든 릴레이 상태 배열
relay.getAllRelayStates() // => Array<boolean>
```

### 이벤트

| 이벤트 | 인자 | 설명 |
|--------|------|------|
| `connected` | `{ deviceInfo, status }` | 장치 연결 성공 |
| `disconnected` | - | 장치 연결 해제 |
| `reconnecting` | - | 재연결 시도 중 |
| `reconnected` | - | 재연결 성공 |
| `handshake-failed` | `{ error }` | 핸드셰이크 실패 |
| `connection-failed` | `{ error }` | 연결 실패 |
| `relay-changed` | `{ channel, state }` | 릴레이 상태 변경 |
| `all-relays-changed` | `{ state }` | 모든 릴레이 상태 변경 |
| `status-changed` | `{ oldStatus, newStatus }` | 상태 변경 감지 |
| `command-failed` | `{ command, error }` | 명령 실행 실패 |
| `device-error` | `{ error }` | 장치 에러 |
| `error` | `{ type, error }` | 일반 에러 |

## 💡 예제

### 1. 간단한 릴레이 제어

```javascript
const USBRelayController = require('usb-relay-hid');

async function simpleControl() {
  const relay = new USBRelayController();

  await relay.open();

  // 릴레이 1번 1초 동안 켜기
  await relay.openRelay(1);
  await new Promise(resolve => setTimeout(resolve, 1000));
  await relay.closeRelay(1);

  relay.close();
}

simpleControl();
```

### 2. 순차 제어

```javascript
async function sequentialControl() {
  const relay = new USBRelayController({ enableLogging: false });

  await relay.open();

  // 릴레이 1-8 순차 제어
  for (let i = 1; i <= 8; i++) {
    console.log(`릴레이 ${i} 켜기`);
    await relay.openRelay(i);
    await new Promise(resolve => setTimeout(resolve, 500));

    console.log(`릴레이 ${i} 끄기`);
    await relay.closeRelay(i);
    await new Promise(resolve => setTimeout(resolve, 500));
  }

  relay.close();
}
```

### 3. 상태 모니터링

```javascript
const relay = new USBRelayController({
  statusPollingInterval: 1000 // 1초마다 상태 폴링
});

relay.on('status-changed', (data) => {
  const oldBinary = data.oldStatus.toString(2).padStart(8, '0');
  const newBinary = data.newStatus.toString(2).padStart(8, '0');

  console.log(`상태 변경: 0b${oldBinary} → 0b${newBinary}`);
});

await relay.open();
```

### 4. 자동 재연결

```javascript
const relay = new USBRelayController({
  autoReconnect: true,
  reconnectInterval: 1000  // 1초마다 재연결 시도
});

relay.on('disconnected', () => {
  console.log('⚠️ USB 연결이 끊어졌습니다');
});

relay.on('reconnecting', () => {
  console.log('🔄 재연결 시도 중...');
});

relay.on('reconnected', () => {
  console.log('✅ 재연결 성공!');
});

await relay.open();

// USB를 뽑았다 다시 꽂으면 자동으로 재연결됩니다
```

### 5. 에러 처리

```javascript
const relay = new USBRelayController();

relay.on('error', (data) => {
  console.error(`에러 발생 [${data.type}]:`, data.error.message);
});

relay.on('command-failed', (data) => {
  console.error(`명령 실패 [${data.command}]:`, data.error);
});

try {
  await relay.open();
  await relay.openRelay(1);
} catch (error) {
  console.error('치명적 에러:', error);
}
```

## 🔧 고급 사용

### TypeScript 사용

```typescript
import USBRelayController from 'usb-relay-hid';

const relay = new USBRelayController({
  autoReconnect: true,
  reconnectInterval: 1000,  // 1초마다 재연결 시도
  maxRetries: 5,
  enableLogging: true
});

relay.on('connected', (data) => {
  console.log('Device:', data.deviceInfo.product);
});

await relay.open();
```

### Express.js와 함께 사용

```javascript
const express = require('express');
const USBRelayController = require('usb-relay-hid');

const app = express();
const relay = new USBRelayController();

app.use(express.json());

// 릴레이 제어 API
app.post('/relay/:channel/open', async (req, res) => {
  const channel = parseInt(req.params.channel);
  const success = await relay.openRelay(channel);
  res.json({ success, channel, state: 'open' });
});

app.post('/relay/:channel/close', async (req, res) => {
  const channel = parseInt(req.params.channel);
  const success = await relay.closeRelay(channel);
  res.json({ success, channel, state: 'closed' });
});

app.get('/status', (req, res) => {
  const states = relay.getAllRelayStates();
  res.json({ states });
});

// 서버 시작
relay.open().then(() => {
  app.listen(3000, () => {
    console.log('릴레이 제어 서버 실행 중: http://localhost:3000');
  });
});
```

## 🖥️ GUI 애플리케이션

이 패키지는 Electron 기반 GUI 애플리케이션도 포함하고 있습니다.

```bash
npm start          # GUI 앱 실행
npm run dev        # 개발 모드 (로그 활성화)
npm run build      # 실행 파일 빌드
```

## 🛠️ 문제 해결

### USB 장치를 찾을 수 없음

```javascript
const devices = relay.enumerate();
if (devices.length === 0) {
  console.log('USB 릴레이 장치가 연결되지 않았습니다');
  console.log('- USB 케이블 확인');
  console.log('- 드라이버 설치 확인');
  console.log('- VID/PID 확인: 0x16c0/0x05df');
}
```

### 연결은 되지만 제어가 안됨

```javascript
relay.on('handshake-failed', (data) => {
  console.error('핸드셰이크 실패:', data.error);
  console.log('장치가 올바른 USB 릴레이인지 확인하세요');
});
```

### Linux 권한 문제

```bash
# udev 규칙 추가
sudo nano /etc/udev/rules.d/99-usb-relay.rules

# 다음 내용 추가:
# SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05df", MODE="0666"

# udev 재시작
sudo udevadm control --reload-rules
sudo udevadm trigger
```

## 📄 라이선스

MIT License - 자유롭게 사용하세요!

## 🤝 기여

이슈와 풀 리퀘스트를 환영합니다!

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📞 지원

- Issues: [GitHub Issues](https://github.com/Samlab-Corp/usb-relay-hid/issues)
- Email: samko@samlab.co.kr
- Website: https://samlab.co.kr

## 🙏 감사의 말

- [node-hid](https://github.com/node-hid/node-hid) - USB HID 통신 라이브러리
- USB 릴레이 커뮤니티의 모든 기여자분들

---

**Made with ❤️ by Samlab Corp**