버전: 2.0 최종 업데이트: 2025-12-11 시스템: Merck Korea AOA 빌딩 입출입 관리 시스템
- ✅ 앵커 방향(Orientation) 지원: 문 밖에서 안을 바라보는 앵커 지원 (INWARD/OUTWARD)
- ✅ 앵커 기울기(Tilt Angle) 자동 보정: 45도 기울어진 앵커의 정확한 위치 판단
- ✅ 상태 기반 이벤트 보정: 누락된 입출입 이벤트 자동 생성
- ✅ 재실 현황 정확도 향상: 시스템 재시작 시에도 정확한 재실 현황 유지
- 모든 기존 API 엔드포인트 호환성 유지
- 응답 데이터에
confidence_score추가 (이벤트 신뢰도) - Anchor 관리 API에
orientation,tilt_angle필드 추가
- 개요
- 인증 및 기본 설정
- 실시간 모니터링 API
- 건물 관리 API
- 게이트 관리 API
- 앵커 관리 API
- 태그 관리 API
- 긴급상황 API
- 통계 API
- 에러 처리
- 실시간 WebSocket
Merck AOA System API는 AOA(Angle of Arrival) 블루투스 기술 기반 실시간 입출입 추적 시스템을 위한 RESTful API입니다.
- Base URL:
http://localhost:8001/api/v1 - API 문서:
http://localhost:8001/docs(Swagger UI) - Content-Type:
application/json - 응답 형식: JSON
- 실시간 재실 현황 모니터링
- 건물/게이트/앵커/태그 관리
- 입출입 이력 조회
- 긴급상황 대응
- 통계 및 분석
현재 버전에서는 인증이 구현되어 있지 않습니다. 추후 JWT 기반 인증이 추가될 예정입니다.
Content-Type: application/json
Accept: application/json모든 API는 다음 형식의 응답을 반환합니다:
{
"success": true,
"data": { ... }
}또는 에러 시:
{
"detail": "에러 메시지"
}시스템 상태를 확인합니다.
엔드포인트: GET /health
응답 예시:
{
"status": "healthy",
"redis": "connected",
"mysql": "connected",
"timestamp": "2025-12-03T17:00:00"
}cURL 예시:
curl http://localhost:8001/api/v1/health모든 건물의 현재 재실자 수를 조회합니다.
엔드포인트: GET /realtime/occupancy
응답 예시:
{
"success": true,
"data": [
{
"building_id": "BLDG01",
"building_name": "본관",
"occupant_count": 45,
"rooftop_count": 2,
"last_updated": "2025-12-03T17:00:00"
}
]
}cURL 예시:
curl http://localhost:8001/api/v1/realtime/occupancy특정 건물의 재실자 상세 정보를 조회합니다.
엔드포인트: GET /realtime/occupancy/detailed?building_id={building_id}
파라미터:
building_id(required): 건물 ID
응답 예시:
{
"success": true,
"data": {
"building_id": "BLDG01",
"building_name": "본관",
"occupant_count": 45,
"occupants": [
{
"tag_id": "TAG001",
"entry_time": "2025-12-03T09:30:00",
"last_gate": "GATE01",
"last_seen": "2025-12-03T16:55:00",
"duration_minutes": 445,
"battery_level": 85
}
],
"last_updated": "2025-12-03T17:00:00"
}
}cURL 예시:
curl "http://localhost:8001/api/v1/realtime/occupancy/detailed?building_id=BLDG01"Redis Stream에서 원본 AOA 데이터를 JSON 형식으로 조회합니다. 디버깅 및 시스템 모니터링용입니다.
엔드포인트: GET /realtime/raw-stream?count={count}
파라미터:
count(optional, default=100, max=1000): 가져올 데이터 개수
Rate Limiting: 100 requests/minute
응답 예시:
{
"success": true,
"message": null,
"data": {
"count": 100,
"items": [
{
"stream_id": "1734534123456-0",
"timestamp": "1734534123456",
"data": {
"anchorMac": "AA:BB:CC:DD:EE:01",
"tagId": "C483727A0001",
"distance": 2.5,
"angle1": 45,
"angle2": -30,
"rssi": -75,
"battery": 85,
"timestamp": 1734534123456
}
}
]
}
}특징:
- Redis Stream에서 최신 데이터부터 역순으로 반환
- 자동 타입 변환 (int, float, string)
- 태그별 게이트별 필터링 가능 (프론트엔드에서)
- Stream ID에서 밀리초 타임스탬프 추출
cURL 예시:
# 최근 100개 데이터 조회
curl "http://localhost:8001/api/v1/realtime/raw-stream"
# 최근 500개 데이터 조회
curl "http://localhost:8001/api/v1/realtime/raw-stream?count=500"
# 최대 1000개 제한
curl "http://localhost:8001/api/v1/realtime/raw-stream?count=1500"Python 예시:
import requests
# 원본 데이터 조회
response = requests.get("http://localhost:8001/api/v1/realtime/raw-stream?count=50")
data = response.json()
if data["success"]:
print(f"총 {data['data']['count']}개 데이터:")
for item in data["data"]["items"]:
print(f"Stream ID: {item['stream_id']}")
print(f"태그: {item['data']['tagId']}")
print(f"거리: {item['data']['distance']}m")
print(f"앵커: {item['data']['anchorMac']}")
print("-" * 40)엔드포인트: GET /buildings
응답 예시:
{
"success": true,
"total": 6,
"buildings": [
{
"id": "BLDG01",
"name": "본관",
"description": "메인 오피스 빌딩",
"address": "서울시 강남구",
"floor_count": 10,
"is_active": true,
"gate_count": 6,
"current_occupants": 45,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
]
}cURL 예시:
curl http://localhost:8001/api/v1/buildings엔드포인트: GET /buildings/{building_id}
응답 예시:
{
"success": true,
"data": {
"id": "BLDG01",
"name": "본관",
"description": "메인 오피스 빌딩",
"address": "서울시 강남구",
"floor_count": 10,
"is_active": true,
"gate_count": 6,
"current_occupants": 45,
"gates": [
{
"gate_id": "GATE01",
"name": "1층 정문",
"location": "1F 메인 로비",
"floor": 1,
"gate_type": "NORMAL",
"is_active": true,
"anchor_id": "ANC001",
"anchor_mac": "AA:BB:CC:DD:EE:01",
"last_heartbeat": "2025-12-03T16:59:00"
}
],
"today_entries": 123,
"today_exits": 78,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
}cURL 예시:
curl http://localhost:8001/api/v1/buildings/BLDG01엔드포인트: POST /buildings
요청 본문:
{
"id": "BLDG07",
"name": "신관",
"description": "새로 지은 건물",
"address": "서울시 강남구",
"floor_count": 15
}응답 예시 (201 Created):
{
"success": true,
"message": "건물이 생성되었습니다",
"data": {
"id": "BLDG07",
"name": "신관",
"description": "새로 지은 건물",
"address": "서울시 강남구",
"floor_count": 15,
"is_active": true,
"gate_count": 0,
"current_occupants": 0,
"created_at": "2025-12-03T17:00:00",
"updated_at": "2025-12-03T17:00:00"
}
}cURL 예시:
curl -X POST http://localhost:8001/api/v1/buildings \
-H "Content-Type: application/json" \
-d '{
"id": "BLDG07",
"name": "신관",
"description": "새로 지은 건물",
"address": "서울시 강남구",
"floor_count": 15
}'엔드포인트: PUT /buildings/{building_id}
요청 본문 (모든 필드 선택적):
{
"name": "신관 A동",
"description": "업데이트된 설명",
"floor_count": 20
}응답 예시:
{
"success": true,
"message": "건물 정보가 수정되었습니다",
"building_id": "BLDG07"
}cURL 예시:
curl -X PUT http://localhost:8001/api/v1/buildings/BLDG07 \
-H "Content-Type: application/json" \
-d '{
"name": "신관 A동",
"floor_count": 20
}'엔드포인트: DELETE /buildings/{building_id}
응답 예시:
{
"success": true,
"message": "건물이 비활성화되었습니다",
"building_id": "BLDG07"
}cURL 예시:
curl -X DELETE http://localhost:8001/api/v1/buildings/BLDG07참고: Soft delete이므로 데이터는 보존되며 is_active만 FALSE로 변경됩니다.
엔드포인트: GET /gates
쿼리 파라미터:
building_id(optional): 건물 ID로 필터링zone_type(optional): 구역 타입 필터 (INDOOR/ROOFTOP)is_active(optional): 활성화 상태 필터
응답 예시:
{
"success": true,
"total": 36,
"gates": [
{
"id": "GATE01",
"name": "1층 정문",
"building_id": "BLDG01",
"building_name": "본관",
"anchor_id": "ANC001",
"anchor_mac": "AA:BB:CC:DD:EE:01",
"floor": 1,
"location_description": "1F 메인 로비",
"gate_type": "NORMAL",
"zone_type": "INDOOR",
"is_active": true,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
]
}cURL 예시:
# 전체 게이트 조회
curl http://localhost:8001/api/v1/gates
# 특정 건물의 게이트만 조회
curl "http://localhost:8001/api/v1/gates?building_id=BLDG01"
# 옥상 게이트만 조회
curl "http://localhost:8001/api/v1/gates?zone_type=ROOFTOP"엔드포인트: GET /gates/{gate_id}
응답 예시:
{
"success": true,
"data": {
"id": "GATE01",
"name": "1층 정문",
"building_id": "BLDG01",
"building_name": "본관",
"anchor_id": "ANC001",
"anchor_mac": "AA:BB:CC:DD:EE:01",
"floor": 1,
"location_description": "1F 메인 로비",
"gate_type": "NORMAL",
"zone_type": "INDOOR",
"is_active": true,
"anchor_status": "online",
"last_heartbeat": "2025-12-03T16:59:00",
"recent_events_count": 15,
"today_entries": 45,
"today_exits": 32,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
}cURL 예시:
curl http://localhost:8001/api/v1/gates/GATE01엔드포인트: POST /gates
요청 본문:
{
"id": "GATE37",
"name": "지하주차장 입구",
"building_id": "BLDG01",
"anchor_id": "ANC030",
"floor": -1,
"location_description": "B1 주차장",
"gate_type": "NORMAL",
"zone_type": "INDOOR"
}응답 예시 (201 Created):
{
"success": true,
"message": "게이트가 생성되었습니다",
"data": {
"id": "GATE37",
"name": "지하주차장 입구",
"building_id": "BLDG01",
"building_name": "본관",
"anchor_id": "ANC030",
"anchor_mac": "AA:BB:CC:DD:EE:30",
"floor": -1,
"location_description": "B1 주차장",
"gate_type": "NORMAL",
"zone_type": "INDOOR",
"is_active": true,
"created_at": "2025-12-03T17:00:00",
"updated_at": "2025-12-03T17:00:00"
}
}cURL 예시:
curl -X POST http://localhost:8001/api/v1/gates \
-H "Content-Type: application/json" \
-d '{
"id": "GATE37",
"name": "지하주차장 입구",
"building_id": "BLDG01",
"anchor_id": "ANC030",
"floor": -1,
"location_description": "B1 주차장",
"gate_type": "NORMAL",
"zone_type": "INDOOR"
}'엔드포인트: PUT /gates/{gate_id}
요청 본문 (모든 필드 선택적):
{
"name": "지하주차장 메인 입구",
"zone_type": "INDOOR",
"is_active": true
}cURL 예시:
curl -X PUT http://localhost:8001/api/v1/gates/GATE37 \
-H "Content-Type: application/json" \
-d '{
"name": "지하주차장 메인 입구"
}'엔드포인트: DELETE /gates/{gate_id}
cURL 예시:
curl -X DELETE http://localhost:8001/api/v1/gates/GATE37엔드포인트: GET /anchors
쿼리 파라미터:
building_id(optional): 건물 ID로 필터링status(optional): 상태 필터 (online/offline)
응답 예시:
{
"success": true,
"total": 36,
"online_count": 34,
"offline_count": 2,
"anchors": [
{
"id": "ANC001",
"gate_id": "GATE01",
"gate_name": "1층 정문",
"building_id": "BLDG01",
"building_name": "본관",
"mac_address": "AA:BB:CC:DD:EE:01",
"is_active": true,
"is_online": true,
"last_heartbeat": "2025-12-03T16:59:30",
"seconds_since_heartbeat": 30,
"calibration": {
"installation_height": 2.74,
"door_distance": 2.74,
"tilt_angle": 45.0,
"tag_height": 1.2,
"rssi_at_anchor": -57,
"rssi_at_door": -60,
"rssi_reference": -48,
"path_loss_exponent": 2.0
},
"firmware_version": "1.0.0",
"notes": null
}
]
}cURL 예시:
# 전체 앵커 조회
curl http://localhost:8001/api/v1/anchors
# 온라인 앵커만 조회
curl "http://localhost:8001/api/v1/anchors?status=online"
# 특정 건물의 앵커만 조회
curl "http://localhost:8001/api/v1/anchors?building_id=BLDG01"엔드포인트: GET /anchors/{anchor_id}
응답 예시:
{
"success": true,
"data": {
"id": "ANC001",
"gate_id": "GATE01",
"gate_name": "1층 정문",
"building_id": "BLDG01",
"building_name": "본관",
"mac_address": "AA:BB:CC:DD:EE:01",
"is_active": true,
"is_online": true,
"last_heartbeat": "2025-12-03T16:59:30",
"seconds_since_heartbeat": 30,
"calibration": {
"installation_height": 2.74,
"door_distance": 2.74,
"tilt_angle": 45.0,
"tag_height": 1.2,
"rssi_at_anchor": -57,
"rssi_at_door": -60,
"rssi_reference": -48,
"path_loss_exponent": 2.0
},
"firmware_version": "1.0.0",
"notes": "정상 작동 중"
}
}cURL 예시:
curl http://localhost:8001/api/v1/anchors/ANC001엔드포인트: GET /anchors/{anchor_id}/status
응답 예시:
{
"anchor_id": "ANC001",
"mac_address": "AA:BB:CC:DD:EE:01",
"status": "online",
"is_online": true,
"last_heartbeat": "2025-12-03T16:59:30",
"seconds_since_heartbeat": 30,
"checked_at": "2025-12-03T17:00:00"
}cURL 예시:
curl http://localhost:8001/api/v1/anchors/ANC001/status엔드포인트: POST /anchors
요청 본문:
{
"id": "ANC037",
"gate_id": "GATE37",
"mac_address": "AA:BB:CC:DD:EE:37",
"firmware_version": "1.0.0",
"notes": "신규 설치",
"ip_address": "172.23.104.137",
"subnet_mask": "255.255.255.0",
"gateway": "172.23.104.1",
"installation_height": 2.74,
"door_distance": 2.74,
"tilt_angle": 45.0,
"tag_height": 1.2,
"rssi_at_anchor": -57,
"rssi_at_door": -60,
"rssi_reference": -48,
"path_loss_exponent": 2.0
}응답 예시 (201 Created):
{
"success": true,
"message": "앵커가 생성되었습니다",
"data": {
"id": "ANC037",
"gate_id": "GATE37",
"gate_name": "지하주차장 입구",
"building_id": "BLDG01",
"building_name": "본관",
"mac_address": "AA:BB:CC:DD:EE:37",
"is_active": true,
"is_online": false,
"last_heartbeat": null,
"seconds_since_heartbeat": null,
"calibration": {
"installation_height": 2.74,
"door_distance": 2.74,
"tilt_angle": 45.0,
"tag_height": 1.2,
"rssi_at_anchor": -57,
"rssi_at_door": -60,
"rssi_reference": -48,
"path_loss_exponent": 2.0
},
"firmware_version": "1.0.0",
"notes": "신규 설치"
}
}cURL 예시:
curl -X POST http://localhost:8001/api/v1/anchors \
-H "Content-Type: application/json" \
-d '{
"id": "ANC037",
"gate_id": "GATE37",
"mac_address": "AA:BB:CC:DD:EE:37",
"ip_address": "172.23.104.137",
"subnet_mask": "255.255.255.0",
"gateway": "172.23.104.1",
"installation_height": 2.74,
"door_distance": 2.74,
"tilt_angle": 45.0
}'엔드포인트: PUT /anchors/{anchor_id}
요청 본문 (모든 필드 선택적):
{
"firmware_version": "1.1.0",
"notes": "펌웨어 업데이트 완료",
"door_distance": 3.0,
"tilt_angle": 50.0
}cURL 예시:
curl -X PUT http://localhost:8001/api/v1/anchors/ANC037 \
-H "Content-Type: application/json" \
-d '{
"firmware_version": "1.1.0",
"door_distance": 3.0
}'엔드포인트: DELETE /anchors/{anchor_id}
cURL 예시:
curl -X DELETE http://localhost:8001/api/v1/anchors/ANC037엔드포인트: GET /tags
쿼리 파라미터:
is_active(optional): 활성 상태 필터is_inside(optional): 재실 상태 필터tag_type(optional): 태그 타입 필터 (EMPLOYEE/VISITOR/VEHICLE)building_id(optional): 건물 ID 필터limit(optional, default=100): 결과 수 제한offset(optional, default=0): 오프셋
응답 예시:
{
"success": true,
"total": 530,
"active_count": 520,
"inside_count": 234,
"tags": [
{
"id": "P0001",
"mac_address": "C483727A0001",
"rfid_id": "550e8400e29b41d4a716446655440000",
"tag_type": "EMPLOYEE",
"is_active": true,
"battery_threshold": 20,
"last_battery_level": 85,
"last_seen": "2025-12-03T16:55:00",
"current_building": "BLDG01",
"current_building_name": "본관",
"is_inside": true,
"activation_date": "2025-01-01",
"deactivation_date": null,
"notes": null
}
]
}cURL 예시:
# 전체 태그 조회
curl http://localhost:8001/api/v1/tags
# 현재 재실 중인 태그만 조회
curl "http://localhost:8001/api/v1/tags?is_inside=true"
# 특정 건물에 있는 태그만 조회
curl "http://localhost:8001/api/v1/tags?building_id=BLDG01&is_inside=true"
# 페이지네이션
curl "http://localhost:8001/api/v1/tags?limit=50&offset=100"엔드포인트: GET /tags/{tag_id}/status
응답 예시:
{
"success": true,
"data": {
"tag_id": "P0001",
"current_building": "BLDG01",
"current_building_name": "본관",
"current_gate": "GATE01",
"last_event": "ENTRY",
"last_event_time": "2025-12-03T09:30:00",
"entry_time": "2025-12-03T09:30:00",
"battery": 85,
"last_seen": "2025-12-03T16:55:00",
"is_inside": true
}
}cURL 예시:
curl http://localhost:8001/api/v1/tags/P0001/status엔드포인트: GET /tags/low-battery
응답 예시:
{
"success": true,
"data": {
"total_count": 5,
"critical_count": 2,
"tags": [
{
"tag_id": "P0045",
"battery_level": 8,
"last_seen": "2025-12-03T16:30:00",
"is_critical": true
},
{
"tag_id": "P0123",
"battery_level": 15,
"last_seen": "2025-12-03T16:45:00",
"is_critical": false
}
]
}
}cURL 예시:
curl http://localhost:8001/api/v1/tags/low-battery엔드포인트: POST /tags
요청 본문:
{
"id": "P0531",
"mac_address": "C483727A0531",
"rfid_id": "550e8400e29b41d4a716446655440531",
"tag_type": "EMPLOYEE",
"battery_threshold": 20,
"notes": "신규 직원"
}응답 예시 (200 OK):
{
"success": true,
"message": "태그가 등록되었습니다",
"tag_id": "P0531"
}cURL 예시:
curl -X POST http://localhost:8001/api/v1/tags \
-H "Content-Type: application/json" \
-d '{
"id": "P0531",
"mac_address": "C483727A0531",
"tag_type": "EMPLOYEE"
}'엔드포인트: PUT /tags/{tag_id}
요청 본문 (모든 필드 선택적):
{
"tag_type": "VISITOR",
"battery_threshold": 15,
"notes": "업데이트된 정보"
}cURL 예시:
curl -X PUT http://localhost:8001/api/v1/tags/P0531 \
-H "Content-Type: application/json" \
-d '{
"battery_threshold": 15,
"notes": "업데이트된 정보"
}'엔드포인트: DELETE /tags/{tag_id}
응답 예시:
{
"success": true,
"message": "태그가 비활성화되었습니다",
"tag_id": "P0531"
}cURL 예시:
curl -X DELETE http://localhost:8001/api/v1/tags/P0531긴급상황 시 1초 이내 응답을 보장하는 최우선 엔드포인트입니다.
엔드포인트: GET /emergency/building/{building_id}/occupants
응답 예시:
{
"success": true,
"building_id": "BLDG01",
"building_name": "본관",
"indoor_count": 45,
"rooftop_count": 2,
"total_count": 47,
"occupants": [
{
"tag_id": "P0001",
"entry_time": "2025-12-03T09:30:00",
"duration_minutes": 445,
"last_gate": "GATE01",
"zone_type": "INDOOR"
},
{
"tag_id": "P0034",
"entry_time": "2025-12-03T15:00:00",
"duration_minutes": 120,
"last_gate": "GATE14",
"zone_type": "ROOFTOP"
}
],
"timestamp": "2025-12-03T17:00:00"
}cURL 예시:
curl http://localhost:8001/api/v1/emergency/building/BLDG01/occupants엔드포인트: GET /emergency/summary
응답 예시:
{
"success": true,
"total_buildings": 6,
"total_occupants": 234,
"buildings": [
{
"building_id": "BLDG01",
"building_name": "본관",
"indoor_count": 45,
"rooftop_count": 2,
"total_count": 47
}
],
"timestamp": "2025-12-03T17:00:00"
}cURL 예시:
curl http://localhost:8001/api/v1/emergency/summary엔드포인트: GET /events/history
쿼리 파라미터:
tag_id(optional): 태그 ID 필터building_id(optional): 건물 ID 필터gate_id(optional): 게이트 ID 필터event_type(optional): 이벤트 타입 필터 (ENTRY/EXIT)start_date(optional): 시작일 (YYYY-MM-DD)end_date(optional): 종료일 (YYYY-MM-DD)limit(optional, default=100): 결과 수offset(optional, default=0): 오프셋
응답 예시:
{
"success": true,
"total": 1523,
"events": [
{
"event_id": "evt_12345",
"tag_id": "P0001",
"building_id": "BLDG01",
"building_name": "본관",
"gate_id": "GATE01",
"gate_name": "1층 정문",
"event_type": "ENTRY",
"event_time": "2025-12-03T09:30:15.123",
"confidence_score": 95.5,
"distance": 1.85,
"angle": 12.5,
"battery_level": 85
}
]
}cURL 예시:
# 특정 태그의 이력
curl "http://localhost:8001/api/v1/events/history?tag_id=P0001"
# 오늘 출입 이력
curl "http://localhost:8001/api/v1/events/history?start_date=2025-12-03&end_date=2025-12-03"
# 특정 건물의 입실 이벤트만
curl "http://localhost:8001/api/v1/events/history?building_id=BLDG01&event_type=ENTRY"엔드포인트: GET /stats/daily
쿼리 파라미터:
date(optional, default=today): 날짜 (YYYY-MM-DD)building_id(optional): 건물 ID 필터
응답 예시:
{
"success": true,
"date": "2025-12-03",
"total_events": 856,
"total_entries": 456,
"total_exits": 400,
"unique_tags": 234,
"by_building": [
{
"building_id": "BLDG01",
"building_name": "본관",
"entries": 123,
"exits": 98,
"unique_tags": 67
}
],
"peak_hour": {
"hour": 9,
"entry_count": 89,
"exit_count": 12
}
}cURL 예시:
# 오늘 통계
curl http://localhost:8001/api/v1/stats/daily
# 특정 날짜 통계
curl "http://localhost:8001/api/v1/stats/daily?date=2025-12-01"200 OK: 성공201 Created: 리소스 생성 성공400 Bad Request: 잘못된 요청404 Not Found: 리소스를 찾을 수 없음409 Conflict: 중복된 리소스500 Internal Server Error: 서버 내부 오류
{
"detail": "에러 메시지"
}1. 존재하지 않는 리소스 (404):
{
"detail": "건물을 찾을 수 없습니다"
}2. 중복된 리소스 (409):
{
"detail": "이미 존재하는 건물 ID입니다"
}3. 잘못된 요청 (400):
{
"detail": "수정할 필드가 없습니다"
}4. 이미 비활성화된 리소스 (400):
{
"detail": "이미 비활성화된 건물입니다"
}엔드포인트: ws://localhost:8001/api/v1/ws/positions
메시지 형식:
{
"tag_id": "P0001",
"gate_id": "GATE01",
"building_id": "BLDG01",
"distance": 1.85,
"angle1": 12.5,
"angle2": -35.0,
"rssi": -65,
"battery": 85,
"timestamp": 1701612345678,
"position": {
"x": -0.45,
"y": 1.75,
"z": 0.1
}
}JavaScript 예시:
const ws = new WebSocket('ws://localhost:8001/api/v1/ws/positions');
ws.onopen = () => {
console.log('WebSocket 연결됨');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('위치 업데이트:', data);
};
ws.onerror = (error) => {
console.error('WebSocket 에러:', error);
};
ws.onclose = () => {
console.log('WebSocket 연결 종료');
};엔드포인트: ws://localhost:8001/api/v1/ws/gate/{gate_id}
게이트별로 필터링된 위치 데이터를 수신합니다.
JavaScript 예시:
const gateId = 'GATE01';
const ws = new WebSocket(`ws://localhost:8001/api/v1/ws/gate/${gateId}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log(`${gateId} 위치 업데이트:`, data);
};import requests
BASE_URL = "http://localhost:8001/api/v1"
# 건물 목록 조회
response = requests.get(f"{BASE_URL}/buildings")
buildings = response.json()
print(f"총 건물 수: {buildings['total']}")
# 재실 현황 조회
response = requests.get(f"{BASE_URL}/realtime/occupancy")
occupancy = response.json()
for building in occupancy['data']:
print(f"{building['building_name']}: {building['occupant_count']}명")
# 건물 생성
new_building = {
"id": "BLDG07",
"name": "신관",
"floor_count": 15
}
response = requests.post(
f"{BASE_URL}/buildings",
json=new_building
)
if response.status_code == 201:
print("건물 생성 성공")
else:
print(f"에러: {response.json()['detail']}")import asyncio
import websockets
import json
async def monitor_positions():
uri = "ws://localhost:8001/api/v1/ws/positions"
async with websockets.connect(uri) as websocket:
print("WebSocket 연결됨")
while True:
try:
message = await websocket.recv()
data = json.loads(message)
print(f"태그: {data['tag_id']}")
print(f"위치: x={data['position']['x']:.2f}, "
f"y={data['position']['y']:.2f}")
print(f"배터리: {data['battery']}%")
print("-" * 40)
except websockets.exceptions.ConnectionClosed:
print("연결 종료")
break
# 실행
asyncio.run(monitor_positions())문서 버전: 1.0 API 버전: 1.0.0 최종 업데이트: 2025-12-03
Swagger UI: http://localhost:8001/docs ReDoc: http://localhost:8001/redoc
개발팀: Merck Korea IT Team
이 문서는 Merck Korea AOA System API의 공식 사용 가이드입니다.