[MED-003] WIA 생체신호 스트리밍 표준 전자책 | 제6장 (총 8장)
"보안 없는 통신은 위험한 통신이다"
Phase 3은 생체신호 데이터의 안전하고 효율적인 전송을 보장하는 통신 프로토콜을 정의합니다.
WIA-MED-003 전용 Bluetooth LE GATT 서비스:
Service UUID: 00001820-0000-1000-8000-00805f9b34fb Service Name: WIA-MED-003 Vital Sign Service Characteristics: 1. 기기 정보 (Read) UUID: 00002a1e-0000-1000-8000-00805f9b34fb - 제조사, 모델, 펌웨어 버전 2. 신호 구성 (Read/Write) UUID: 00002a1f-0000-1000-8000-00805f9b34fb - 샘플링 레이트, 필터 설정 3. 데이터 스트림 (Notify) UUID: 00002a20-0000-1000-8000-00805f9b34fb - 실시간 생체신호 데이터 4. 제어 명령 (Write) UUID: 00002a21-0000-1000-8000-00805f9b34fb - START, STOP, PAUSE, RESUME 5. 상태 (Notify) UUID: 00002a22-0000-1000-8000-00805f9b34fb - 배터리, 신호 품질, 오류
BLE 패킷 (최대 20 bytes): [Header 4 bytes][Payload N bytes][CRC 2 bytes] Header: - Byte 0: 패킷 유형 (0x01=ECG, 0x02=SpO2, etc.) - Byte 1: 시퀀스 번호 - Byte 2-3: 페이로드 길이 Payload: - 신호 데이터 (압축 또는 원본) CRC: - CRC-16 체크섬 예제 (ECG 데이터): 01 3A 00 0C 05 12 18 0A 15 1C 09 14 1B 08 A3 4F │ │ │ │ └─────────────────────┘ └──┘ │ │ │ │ ECG 샘플 (12 bytes) CRC │ │ │ └─ 길이 (12) │ │ └─ 길이 MSB │ └─ 시퀀스 #58 └─ ECG (0x01)
| 파라미터 | 최소 | 최대 | 권장 | 용도 |
|---|---|---|---|---|
| Connection Interval | 7.5 ms | 4000 ms | 20-50 ms | 실시간 스트리밍 |
| Slave Latency | 0 | 500 | 0-2 | 배터리 절약 |
| Supervision Timeout | 100 ms | 32000 ms | 2000 ms | 연결 유지 |
// 서버 (기기)
const server = net.createServer((socket) => {
console.log('클라이언트 연결:', socket.remoteAddress);
// TLS 업그레이드
const secureSocket = new tls.TLSSocket(socket, {
cert: deviceCertificate,
key: devicePrivateKey,
ca: [caCertificate]
});
// 데이터 전송
const interval = setInterval(() => {
const data = getVitalSignData();
const packet = encodeWIA(data);
secureSocket.write(packet);
}, 20); // 50Hz 전송
secureSocket.on('close', () => {
clearInterval(interval);
});
});
server.listen(8883);
// 클라이언트 (앱)
const client = tls.connect(8883, deviceIP, {
ca: [caCertificate],
checkServerIdentity: (host, cert) => {
// 기기 인증서 검증
return verifyDeviceCertificate(cert);
}
}, () => {
console.log('안전한 연결 확립');
// 데이터 수신
client.on('data', (packet) => {
const data = decodeWIA(packet);
processVitalSignData(data);
});
});
// Server-Sent Events (SSE)
app.get('/stream/ecg/:deviceId', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const stream = device.startStream('ECG');
stream.on('data', (data) => {
res.write(`event: ecg-data\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
req.on('close', () => {
stream.stop();
});
});
// 클라이언트
const eventSource = new EventSource('/stream/ecg/device-123');
eventSource.addEventListener('ecg-data', (event) => {
const data = JSON.parse(event.data);
updateECGChart(data);
});
WIA-MED-003 MQTT 토픽 계층:
wia-med-003/
├─ {deviceId}/
│ ├─ info # 기기 정보 (retained)
│ ├─ status # 상태 업데이트
│ ├─ data/
│ │ ├─ ecg # ECG 데이터
│ │ ├─ spo2 # SpO2 데이터
│ │ ├─ bp # 혈압 데이터
│ │ └─ hrv # HRV 데이터
│ ├─ alerts # 알림
│ └─ control # 제어 명령
└─ broadcast/
└─ discovery # 기기 발견
예제:
발행: wia-med-003/ECG-12345/data/ecg
구독: wia-med-003/+/data/ecg (모든 기기의 ECG)
| 토픽 | QoS | 이유 |
|---|---|---|
| data/* | 0 | 실시간 데이터, 다음 샘플이 곧 도착 |
| alerts | 1 | 중요하지만 중복 허용 |
| control | 2 | 정확히 한 번 실행 필요 |
| info | 1 + retained | 새 구독자가 즉시 확인 필요 |
// TLS/SSL 연결
const mqttClient = mqtt.connect('mqtts://broker.example.com:8883', {
ca: fs.readFileSync('ca.crt'),
cert: fs.readFileSync('client.crt'),
key: fs.readFileSync('client.key'),
rejectUnauthorized: true
});
// 사용자 인증
const mqttClient = mqtt.connect('mqtts://broker.example.com:8883', {
username: 'device-12345',
password: generateDeviceToken(), // 시간 제한 토큰
clientId: 'ECG-12345'
});
// 토픽 ACL
// broker.conf:
acl:
- pattern: wia-med-003/{clientid}/data/#
access: write
- pattern: wia-med-003/{clientid}/control
access: read
- pattern: wia-med-003/+/data/#
access: read
// 서버 (Node.js + ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 8080,
verifyClient: (info, callback) => {
// JWT 토큰 검증
const token = new URL(info.req.url, 'ws://base').searchParams.get('token');
jwt.verify(token, SECRET_KEY, (err, decoded) => {
callback(!err, 200, 'OK');
});
}
});
wss.on('connection', (ws, req) => {
const deviceId = new URL(req.url, 'ws://base').searchParams.get('deviceId');
// 기기 스트림 시작
const stream = startDeviceStream(deviceId);
stream.on('data', (data) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
});
ws.on('message', (message) => {
const command = JSON.parse(message);
handleCommand(stream, command);
});
ws.on('close', () => {
stream.stop();
});
});
// 브라우저 클라이언트
const token = await getAuthToken();
const ws = new WebSocket(`wss://api.example.com/stream?deviceId=ECG-12345&token=${token}`);
ws.onopen = () => {
console.log('WebSocket 연결됨');
// 스트림 시작 요청
ws.send(JSON.stringify({
command: 'start',
signalType: 'ECG',
samplingRate: 512
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.signalType === 'ECG') {
updateECGChart(data);
}
};
ws.onerror = (error) => {
console.error('WebSocket 오류:', error);
};
ws.onclose = (event) => {
console.log('WebSocket 닫힘:', event.code, event.reason);
// 자동 재연결
setTimeout(() => reconnect(), 5000);
};
// Ping/Pong으로 연결 유지
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
| 프로토콜 | 장점 | 단점 | 적합한 용도 |
|---|---|---|---|
| Bluetooth LE | 저전력, 근거리, 널리 지원됨 | 제한된 범위, 낮은 대역폭 | 웨어러블, 휴대용 기기 |
| WiFi/TCP | 높은 대역폭, 안정적 | 높은 전력 소모 | 가정용 모니터, 병원 기기 |
| MQTT | 경량, IoT 최적화, Pub/Sub | 중앙 브로커 필요 | 원격 모니터링, 다중 기기 |
| WebSocket | 양방향, 브라우저 지원 | 방화벽 문제 가능 | 웹 대시보드, 원격의료 |
// AES-256-GCM 암호화
const crypto = require('crypto');
function encryptData(data, key) {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
data: encrypted,
authTag: authTag.toString('hex')
};
}
function decryptData(encryptedData, key) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
// OAuth 2.0 Device Flow
async function authenticateDevice(deviceId) {
// 1. 기기 코드 요청
const response = await fetch('https://auth.wialive.smilestory.ai/device/code', {
method: 'POST',
body: JSON.stringify({
client_id: 'wia-med-003',
scope: 'vital-signs:read vital-signs:write'
})
});
const { device_code, user_code, verification_uri } = await response.json();
// 2. 사용자에게 코드 표시
displayUserCode(user_code, verification_uri);
// 3. 폴링으로 토큰 대기
const token = await pollForToken(device_code);
return token;
}
// JWT 토큰 검증
function verifyToken(token) {
try {
const decoded = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'https://auth.wialive.smilestory.ai',
audience: 'wia-med-003'
});
return {
valid: true,
deviceId: decoded.sub,
permissions: decoded.scope.split(' ')
};
} catch (error) {
return { valid: false, error: error.message };
}
}
HIPAA 준수:
GDPR 준수:
// 감사 로그 예제
{
"timestamp": "2025-01-15T10:30:00.000Z",
"eventType": "data_access",
"userId": "doctor-456",
"patientId": "patient-123",
"dataType": "ECG",
"action": "view",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"purpose": "medical_consultation",
"consentId": "consent-789"
}
[OK] 핵심 내용:
제7장에서는 Phase 4인 생태계 통합을 다룹니다. EHR/EMR, 원격의료, 클라우드 스토리지, AI/ML 분석 플랫폼과의 통합 방법을 학습합니다.