Skip to content

Latest commit

 

History

History
1055 lines (770 loc) · 60.1 KB

README.ko.md

File metadata and controls

1055 lines (770 loc) · 60.1 KB

OpenIPC NVR — AI 감지와 출입 통제를 갖춘 영상 감시 서버

다른 언어로 보기: Русский · English · 简体中文

분산된 IP 카메라(OpenIPC, Hikvision, Dahua, Vivotek, ONVIF)를 위한 영상 감시 서버로, 신경망 기반 객체·소리 감지, 얼굴 및 번호판 인식, 출입 통제 시스템 연동과 웹 인터페이스를 갖추고 있습니다.

┌──────────────┐  RTSP   ┌────────────┐  MSE/WebRTC  ┌──────────────┐
│   카메라     │────────▶│  go2rtc    │─────────────▶│  Web UI      │
│ OpenIPC/ONVIF│         │ (미디어 서버)│             │  React SPA   │
└──────────────┘         └─────┬──────┘              └──────┬───────┘
                               │                            │ REST
┌──────────────┐   NATS  ┌─────▼────────────────────────────▼───────┐
│ AI Detector  │────────▶│        Go 백엔드 (API + 로직)            │
│ YOLOv8 +     │         └────┬──────────┬───────────┬──────────────┘
│ YAMNet +     │              │          │           │
│ InsightFace  │        ┌─────▼───┐ ┌────▼────┐ ┌────▼──────┐
└──────────────┘        │PostgreSQL│ │ MinIO   │ │  출입통제 │
                        │ 메타데이터│ │  녹화   │ │Hikvision/ │
                        └──────────┘ └─────────┘ │Dahua/     │
                                                  │Promwad    │
                                                  └───────────┘

인터페이스 언어

인터페이스는 러시아어, 영어, 중국어(간체), 한국어 네 가지 언어 사이에서 전환됩니다. 기본 언어는 러시아어입니다.

언어 «서버» 페이지의 버튼 번역 상태
Русский 기본 원본 언어, 완전
English English 부분
简体中文 简体中文 부분
한국어 (북한) 한국어 부분, 남한 규범 — 아래 참조

언어가 자동으로 잡히지 않았다면

인터페이스는 처음 열릴 때 브라우저 설정에서 언어를 가져옵니다. 브라우저가 중국어나 한국어를 알려 주면 그 언어로 바로 열립니다. 하지만 반대의 경우도 있습니다. 브라우저는 러시아어로 설정되어 있는데 사용자에게는 다른 언어가 필요하거나, 브라우저가 알려 준 언어를 인터페이스가 지원하지 않아 러시아어가 표시되는 경우입니다. 이럴 때는 언어를 직접 바꿉니다.

언어 전환기

  1. 서버 페이지를 엽니다 — http://<서버 주소>:3001/server, 평소 인터페이스를 여는 그 주소입니다.
  2. 인터페이스 언어 블록에서 필요한 언어 버튼을 누릅니다. 이 블록은 페이지 맨 위, 안내 문구 바로 아래에 있습니다. 러시아어를 읽지 못하는 사람이 여기에 무엇이 쓰여 있는지 모르면 나머지 설정에는 아예 닿지 못하기 때문입니다.
  3. 선택한 언어에 표시가 되고 즉시 적용됩니다 — 메뉴, 버튼, 그리고 모든 페이지의 문구에 반영됩니다.

선택은 브라우저에 기억되므로 한 번만 설정하면 됩니다. 서버가 아니라 브라우저에 저장되기 때문에 한 워크스테이션은 러시아어, 다른 워크스테이션은 한국어로 쓸 수 있고 서로 간섭하지 않습니다.

같은 화면의 다른 스크린샷: 영어, 중국어, 한국어, 페이지 전체.

국기가 코드로 그려진 이유

국기는 이모지가 아니라 코드 안에 벡터로 직접 그려져 있습니다. 이미지로 불러오지도 않습니다. 이유는 사전과 같습니다. 서버는 인터넷이 아예 없을 수도 있는 집이나 사무실에 설치되고, 그런 네트워크에서 외부에서 무언가를 불러오면 국기 자리에 빈 공간만 남습니다. 폰트에서 이미 겪었습니다. 한자 글리프가 있는 폰트가 없으면 중국어 전체가 빈 사각형으로 보였습니다.

한국어에 대하여

버튼에는 북한 국기가 있고, 글은 남한 규범으로 번역되어 있습니다. 이 불일치는 의도된 것입니다. 이 서버의 고객이 북한에 있고, 국기는 그 나라의 것이어야 합니다. 그러나 북한 규범은 "같은 한국어에 국기만 바꾼 것"이 아니라 고유한 어휘, 호칭, 용어를 가진 별개의 번역 언어이며, 원어민이 필요합니다. 사전에서 단어만 바꿔서 되는 일이 아닙니다.

번역을 돕는 방법, 다른 언어를 고치는 방법, 새 언어를 추가하는 방법은 docs/TRANSLATIONS.md에 있습니다.


기능

영역 구현된 내용
카메라 웹 인터페이스를 통한 추가와 편집(데이터는 PostgreSQL, 수작업 설정 파일 없음), 서브넷 스캐너(ONVIF, mDNS, HTTP 탐색, 자격 증명 대입), 저장 전 스트림 검사로 코덱·해상도·소리 유무 표시
스트림 go2rtc를 통한 RTSP 수신, 브라우저용 MSE/WebRTC, WebRTC, 주 스트림과 보조 스트림, JWT 인증을 붙인 백엔드 프록시
외부 RTSP 접근 외부 시스템(영상벽, 녹화기, 분석 장비)에 스트림 제공. 주소는 /cameras/{N}/streaming/{main|sub}, 별도 포트 9784. 채널 번호는 자동으로 지정되고, 외부 접근 페이지에서 바로 쓸 수 있는 링크를 보여 줍니다. 트랜스코딩 없이 전달하므로 카메라 부하가 늘지 않습니다
카메라 소리 플레이어에서 청취: 소리는 주 스트림 안으로 들어가고, go2rtc가 듣는 사람을 위해 G.711을 Opus로 변환합니다. ONVIF Profile T 카메라에서 양방향 소리(브라우저 마이크 → 카메라 스피커)
객체 감지 추적 기능이 있는 YOLOv8. 정확도 필터: 신뢰도 임계값, 객체 최소·최대 크기, 박스 종횡비, 정지 객체 제외. 카메라별로 설정 — docs/DETECTION-QUALITY.md
얼굴 인식 InsightFace(ArcFace): 임베딩, 등록된 얼굴 목록, «등록됨» / «미등록» / «차단» 표시
번호판 인식 번호판 영역 검출, OCR, 설정 가능한 탐색 영역(카메라의 OSD 메뉴를 잡지 않도록), 지역별 형식 검증
PTZ ONVIF를 통한 팬틸트 카메라: 방향, 줌, 정지, 프리셋, 이동 폭 조절
카메라 제어 SSH 없이 펌웨어 HTTP API(Majestic)로 카메라 설정: 영상, 이미지, 야간 모드, OSD, 재시작. API가 없는 카메라는 SSH를 그대로 사용
다른 제조사 카메라 카메라에서 직접 읽는 장치 정보(모델, 펌웨어, 일련번호, MAC), 상태(가동 시간, CPU·메모리 사용량, 시계 편차), 스트림 파라미터와 재부팅 — Hikvision(ISAPI), Beward(자체 HTTP API), 그리고 공통 ONVIF 계층: Vivotek, Dahua, Axis, Uniview, Reolink, Xiongmai. SSH 없이 동작 — docs/CAMERA-VENDORS.md
카메라 상태 감시 1분마다 /metrics와 /api/v1/sources 조회: CPU 사용률, 가용 메모리, 센서 fps, 인코더 끊김. «정상 / 주의 / 문제» 판정과 그 이유 설명
미리보기 카메라 목록에서 영상 스트림 대신 한 프레임: RTSP 세션 대신 HTTP 요청 한 번. Basic·Digest 인증 지원, RTSP에서 예비 수신
이벤트 AI 감지 결과 저장(클래스, 신뢰도, bbox, 트랙), 트리거 연결(객체 / 선 / 얼굴 / 번호판), 필터와 페이지 나누기
녹화 트리거에 의한 녹화 또는 상시 녹화, 메타데이터는 PostgreSQL, 파일은 로컬 또는 MinIO, 브라우저에서 재생, 보관 기간에 따른 자동 정리
출입 통제 Hikvision(ISAPI), Dahua, Promwad, IronLogic Z-5R WEB BT 어댑터: 문 목록, 열기, 출입 이벤트
권한: 인원과 권한 카드 소유자(성명, 직책, 사진, 부서), 권한 그룹, 문, 권한 계산과 출처 표시. 키 종류: 일반, 마스터, 차단. 별도 «권한» 페이지 — docs/SKUD-ACCESS.md
카드 등록 컨트롤러의 Accept 모드(모두에게 문이 열리고 카드가 메모리에 기록됨)와 문 앞 리더기에서 카드 읽기 — 모두에게 문을 열지 않고도 번호가 인터페이스에 들어옵니다. 운영자 자리의 데스크톱 USB 리더기는 키보드처럼 동작합니다
평면도 장비 배치가 표시된 도면: 배경(사진 또는 도면 스캔본) 위에 카메라, 문, 컨트롤러, 리더기 배치. 각 지점에서 장치가 동작하는지 바로 보입니다 — docs/PLANS.md
PoE 스위치 관리형 PoE 스위치 등록: 네트워크에서 검색, 포트 상태(링크, 속도, 소비 전력), 멈춘 카메라를 전원으로 재부팅하고 직접 올라갈 필요 없음. 업링크 포트 차단 방지. 카메라와 포트 연결 — docs/SWITCHES.md
서버 설정 단일 페이지: 녹화와 스냅숏 저장 경로, 로컬 디스크 또는 S3, 인식 파라미터
모니터링 카메라 상태 자동 판정(online/offline), 미디어 서버 재시작 후 경로 복구, «떠 있는» 경로 정리
알림 Telegram과 MAX: 스냅숏이나 짧은 영상이 붙은 감지 이벤트, 카메라와 이벤트 유형 선택, 신뢰도 임계값, 방해 금지 시간, 발송 기록. 별도로 서버 자체 감시: 카메라 소실, CPU 부하, 메모리, 디스크, 과열, 그래픽카드
스냅숏 제조사별 경로로 카메라 프레임, go2rtc에서 예비 프레임, 목록의 이벤트 스냅숏
모바일 앱 Android 휴대폰: 서버 선택, 카메라와 녹화 보기. QR 코드로 연결 — 사용자 카드에서 스캔하면 주소와 비밀번호 없이 연결되며, 푸시 알림(알람과 부재중 전화)을 받습니다. Android TV용 별도 빌드: 1~25분할 그리드, 레이아웃, 소리. 자세한 내용은 아래

인터페이스

카메라와 보기

타일에는 각 카메라의 실시간 화면, 상태, 스트림 주소가 표시됩니다. 타일에서 바로 main/sub를 전환할 수 있고, 삭제 버튼이 따로 있습니다.

카메라

카메라 카드에는 주 스트림이나 보조 스트림을 재생하는 플레이어, 소리 버튼, 그리고 팬틸트 모델을 위한 PTZ 조작판이 있습니다.

카메라 카드

플레이어 옆에는 장치 정보가 있습니다. 장치 정보(모델, 펌웨어, 일련번호, MAC), 상태 (가동 시간, CPU·메모리 사용량, 시계 편차), 스트림 파라미터, 재부팅 버튼입니다. 데이터는 우리 데이터베이스가 아니라 카메라 자체에서 읽습니다.

장치 정보

소리

카메라는 소리를 G.711로 보내는데 브라우저는 이를 그대로 재생하지 못합니다. go2rtc가 듣는 사람을 위해 G.711을 Opus로 실시간 변환하고, 소리는 주 스트림 안으로 들어가므로 별도 오디오 트랙이 필요하지 않습니다. 패널에서 카메라 코덱, 변환이 진행 중인지, 소리 이벤트가 인식되는지를 볼 수 있습니다.

소리 설정

YAMNet 소리 이벤트: 총성, 비명, 유리 깨지는 소리, 개 짖는 소리 등. 클래스별 필터와 경보 이벤트 강조 표시.

소리 이벤트

객체 감지

객체 클래스, 최소 신뢰도, 탐색 영역과 통과선. 설정은 특정 카메라에 적용되고 감지기가 재시작 없이 자동으로 가져갑니다.

감지 설정

찾아낸 객체는 스냅숏, 클래스, 신뢰도, 카메라 정보와 함께 기록에 남습니다.

감지 이벤트

녹화

녹화 항목에는 사유(객체, 얼굴, 번호판, 선, 상시 녹화), 크기, 해상도, 보관 기간이 표시됩니다. 브라우저에서 바로 재생할 수 있습니다.

녹화

인식과 출입 통제

등록된 얼굴과 차량 번호판 목록, 상태는 «등록됨», «미등록», «차단».

인식

스캐너가 서브넷에서 카메라를 찾아 주고, 추가하기 전에 스트림을 확인할 수 있습니다: 코덱, 해상도, 소리 유무.

스캐너

권한: 인원, 그룹, 키

카드 소유자 카드의 권한은 저장된 기록이 아니라 계산 결과로 표시됩니다. 문마다 권한이 어디에서 왔는지 — 그룹에서인지 개인 규칙에서인지 — 를 볼 수 있습니다. 그렇지 않으면 «그 사람은 문이 안 열린다»는 상황에서 무엇을 고쳐야 할지 알 수 없습니다.

키는 세 가지 종류입니다. 일반 통행 키, 마스터 키(컨트롤러를 프로그래밍하며 문을 열지 않음), 차단 키(모드를 전환함).

카드 번호는 두 가지 방법으로 읽을 수 있습니다.

  • 운영자 자리의 데스크톱 USB 리더기 — 키보드처럼 동작하므로 번호를 양식 칸에 바로 입력합니다.
  • 문 앞 리더기 — 운영자가 «카드 대기»를 누르고 카드를 문에 대면 번호가 인터페이스에 나타납니다. 이때 문이 모두에게 열리지는 않습니다. 서버는 컨트롤러의 이벤트를 들을 뿐입니다.

제조사 문서와 실제 장비가 어긋나는 부분을 포함한 자세한 내용 — docs/SKUD-ACCESS.md.

평면도

장비 배치가 표시된 도면: 무엇이 어디에 있고 무엇이 연결을 잃었는지 보입니다.

장치 목록의 탭이 아니라 별도 페이지입니다. 목록과 평면도는 서로 다른 질문에 답하기 때문입니다. 목록은 «어떤 카메라가 있는가», 평면도는 «이 출입구에 무엇이 있는가»에 답합니다. 순찰할 때나 사건을 분석할 때 필요한 것은 후자입니다.

평면도

배경(층 사진이나 도면 스캔본) 위에 카메라, 문, 컨트롤러, 리더기를 올립니다. 장치가 동작하면 아이콘이 초록색, 연결이 끊기면 빨간색, 장치가 삭제되었거나 연결 없는 위치 표시라면 회색입니다. 아이콘 모양으로 장치 종류를 구분합니다.

좌표는 배경 크기에 대한 비율로 저장되므로, 다른 크기의 그림으로 바꿔도 배치가 밀리지 않습니다. 상태는 1분마다 갱신되고, 평면도 상단에서 몇 개 장치가 오프라인인지 볼 수 있습니다. 자세한 내용 — docs/PLANS.md.

PoE 스위치

관리형 PoE 스위치 등록과 카메라 전원 제어.

이 페이지는 카메라 목록이 답하지 못하는 질문에 답합니다. 장치가 왜 사라졌는가입니다. «오프라인»은 원인을 설명하지 못하는데 원인은 세 가지입니다 — 카메라가 멈췄거나, 회선이 끊겼거나, 전원이 차단되었거나 — 그리고 각 경우에 운영자가 해야 할 일이 다릅니다.

스위치

스위치는 브로드캐스트로 찾고 한 번의 클릭으로 추가합니다. 카드에는 포트 표가 있습니다: 링크와 속도, PoE 상태, 소비 전력, 연결된 카메라. 각 포트를 켜고, 끄고, 전원으로 재부팅할 수 있으며, 마지막 것이 멈춘 카메라까지 올라가는 일을 대신합니다.

멈춘 카메라는 카드에서 바로 재부팅합니다. 연결 블록이 포트의 전원, 링크 유무, 카메라 자체의 상태를 함께 보고 무엇이 문제인지 알려 줍니다. 숫자 두 개만 나란히 두고 운영자를 떠나 보내지 않습니다.

카메라 연결

업링크 포트는 끄지 못하도록 보호됩니다. 서버와의 연결이 그리로 지나가고, 전원을 끊으면 스위치 전체를 제어하지 못하게 될 수 있습니다. 모든 조작은 누가 언제 했는지와 함께 기록에 남습니다. 모델 간 차이를 포함한 자세한 내용 — docs/SWITCHES.md.

설정과 개요

단일 설정 페이지: 녹화와 스냅숏 저장 위치(디스크 또는 S3), 인식 파라미터, 보관 기간.

설정

시스템 상태 요약: 온라인 카메라, 24시간 이벤트, 사용 중인 공간.

대시보드

알림

이벤트는 Telegram과 MAX로 갑니다: 스냅숏이나 짧은 영상, 카메라 선택, 이벤트 유형, 신뢰도 임계값, 방해 금지 시간. 각 채널은 따로 설정하고, 하나가 실패해도 다른 하나에 지장을 주지 않습니다.

Telegram 알림

MAX는 러시아에서 프록시 없이 직접 접근할 수 있는 유일한 채널입니다. REST API가 메시지 형식에 눈에 띄게 엄격해서, 텍스트는 Telegram의 것을 재사용하지 않고 따로 만듭니다.

MAX 알림

별도 탭은 서버 자체를 감시합니다: 카메라 소실, CPU 부하 과다, 메모리 부족, 디스크 부족, 과열, 그래픽카드 오류. 온도와 그래픽카드 측정에는 호스트의 에이전트가 필요합니다. 컨테이너 안에서는 보이지 않습니다.

서버 알림

기록에는 무엇이 어디로 갔고 왜 가지 못했는지가 나옵니다. 예를 들어 그 이벤트 유형이 발송 대상으로 선택되지 않았다는 것 등입니다. 메신저를 열지 않고도 전달 상태를 알 수 있습니다.

알림 기록

스크린샷은 실제 시스템에서 촬영했습니다. 서버가 실행 중일 때 node scripts/make-screenshots.js로 다시 만들 수 있습니다. 토큰과 채팅 주소는 스크린샷에서 자동으로 지워집니다.


모바일 앱

하나의 React Native 애플리케이션이 휴대폰용과 TV용 두 가지로 빌드됩니다. 형태는 장치 종류에 따라 자동으로 선택됩니다. TV에서 휴대폰 인터페이스가 동작하기는 하지만, 리모컨으로는 누를 수가 없습니다.

휴대폰

IP 주소, 포트 또는 DNS 이름으로 서버에 연결하고, 카메라를 실시간으로 보고 녹화를 다룹니다.

화면 기능
서버 저장된 서버 목록, 추가, 저장 전 연결 확인, 전환, 삭제. 사용자 카드의 QR 코드로 연결하면 주소와 비밀번호를 입력할 필요가 없습니다
로그인 아이디와 비밀번호. 토큰은 서버별로 따로 저장되므로 서버를 전환해도 다시 로그인할 필요가 없습니다. QR 코드로 연결하면 즉시 로그인됩니다
카메라 미리보기 프레임이 있는 타일(10초마다 갱신), 온라인/오프라인 상태, 아래로 당겨 새로 고침
보기 MSE/WebRTC 실시간 영상, 주 스트림과 보조 스트림 전환, 일시정지, 소리
통화 인터콤: 내선 번호가 계정에 고정되고, 가입자와 호출 그룹에 전화를 걸며, 백그라운드와 잠금 화면에서 받습니다
부재중 서버의 부재중 통화 기록: 누가, 어디로, 언제, 어떻게 끝났는지. 하루 동안의 개수가 메인 화면에 보입니다
녹화 카메라별 필터가 있는 녹화 목록, 탐색이 되는 내장 플레이어, 스크롤 시 추가 로딩. 녹화 사유가 바로 보입니다: 번호판, 얼굴, 객체
서버 카메라 녹화: 번호판 녹화: 객체

서버 이름 옆의 숫자는 전체 카메라 중 온라인인 수를 보여 줍니다. 스크린샷에서는 22대 중 19대입니다. 카메라는 주소로 걸러낼 수 있고, 녹화는 목록에서 바로 열 수 있습니다. 예를 들어 «미등록: P263AT54»처럼요.

QR 코드로 연결

휴대폰을 연결할 때 서버 주소도 비밀번호도 필요하지 않습니다. 관리자가 사용자 페이지에서 해당 직원의 줄에 있는 앱 연결을 누르면 QR 코드 창이 열립니다. 직원은 앱에서 서버 → QR 코드로 연결을 선택하고 카메라를 코드에 맞추면 됩니다.

코드는 한 번만 사용할 수 있고 10분 동안 유효합니다. 코드는 일반 세션 토큰으로 교환되며 비밀번호는 들어 있지 않습니다. 카메라가 화면을 읽지 못하면 서버 직접 추가 양식에서 코드를 직접 입력할 수 있습니다 — 대소문자와 구분자를 가리지 않습니다. 자세한 내용은 docs/MOBILE.md.

알람 알림

서버는 앱으로 푸시 알림을 보냅니다: 인식 이벤트(객체, 얼굴, 번호판, 선 넘기)와 현관기의 부재중 전화입니다. 앱이 닫혀 있어도 알림은 도착하며 시스템이 표시합니다. 앱이 앞에 있을 때는 앱 자체 모듈이 표시합니다. 전화 알림을 누르면 부재중 화면이 열립니다.

설정은 알림 → 모바일 앱 페이지에 있습니다: 어떤 이벤트를, 어느 카메라에서, 확신 임계값, 방해 금지 시간, 반복 간격. 저장 및 테스트 버튼은 등록된 모든 휴대폰으로 시험 알림을 보냅니다. 기기는 로그인할 때 스스로 등록되고 로그아웃할 때 해제되므로 손으로 설정할 것이 없습니다. 앱을 빌드하려면 자신의 Firebase 프로젝트와 google-services.json 파일이 필요합니다 — docs/MOBILE.md.

Android TV

TV와 셋톱박스용 빌드: 최대 25분할 그리드, 레이아웃, 소리. 리모컨으로 조작하며 타일이 큽니다. 소파에서도 보입니다.

TV의 4×4 그리드

그리드: 1, 2, 4, 6, 9, 16, 25분할. 형태는 정사각형이 아니라 TV 화면에 맞춰 정합니다. 6대는 2×3이 아니라 3×2입니다. 그렇지 않으면 타일이 세로로 늘어나고 양옆에 검은 띠가 남습니다.

레이아웃: 카메라 조합, 그리드 크기, 문구, 소리를 이름을 붙여(«입구», «야간 근무») 저장하고 버튼 하나로 전환합니다. 서버마다 자기 레이아웃을 가집니다. 선택한 카메라의 순서가 그리드에서의 위치를 정하므로, 선택 목록에는 체크 표시가 아니라 타일 번호가 나옵니다.

소리: 한 번에 한 카메라만 켭니다. 여러 카메라의 소리가 동시에 나오면 소음으로 섞여 버리는데, 운영자에게 필요한 것은 특정 한 대입니다.

밀집 그리드(16, 25분할)에서는 타일의 문구가 자동으로 숨겨집니다. 그런 타일에는 백여 픽셀밖에 돌아가지 않아, 이름이 화면의 절반을 가려 버립니다. 모서리의 상태 점만 남습니다.

소리는 이제 주 스트림 안으로 들어갑니다. go2rtc가 듣는 사람을 위해 G.711을 Opus로 실시간 변환하므로 별도 오디오 스트림이 필요하지 않습니다. 그리드에서는 주 스트림 대신 보조 스트림을 재생합니다. 4K 주 스트림 네 개면 60 Mbit/s 이상이 필요한데, TV의 Wi-Fi도 디코더도 이를 감당하지 못합니다.

화면 하는 일
그리드 레이아웃대로 카메라 표시, 타일에 초점, «OK»로 전체 화면 열기 또는 소리 켜기
레이아웃 생성, 선택, 삭제. 각각 그리드 크기와 카메라 구성을 가집니다
카메라 레이아웃에 넣을 카메라 선택, 순서가 위치를 정합니다
설정 문구(이름, 상태, 시계), 소리, 볼륨, 소리를 켤 카메라

앱 빌드

cd mobile
npm install
echo "sdk.dir=$HOME/Android/Sdk" > android/local.properties

cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

JDK 17이 필요합니다. JDK 21에서는 빌드가 jlink executable ... does not exist 오류로 실패합니다.

APK는 두 ARM 아키텍처용으로 빌드됩니다. 최신 장치용 arm64-v8a와 조금 오래된 셋톱박스와 TV용 armeabi-v7a입니다. 완성된 파일은 약 76 MB입니다. 장치 아키텍처는 이렇게 확인합니다.

adb shell getprop ro.product.cpu.abi

APK에 맞는 아키텍처가 없으면 설치가 INSTALL_FAILED_NO_MATCHING_ABIS 오류로 실패합니다. app/build.gradle의 abiFilters와 gradle.properties의 reactNativeArchitectures 값이 일치해야 합니다. 전자는 어떤 미리 빌드된 라이브러리가 APK에 들어갈지, 후자는 React Native 자체의 C++가 무엇을 대상으로 컴파일될지를 정합니다.

기술: React Native 0.75, react-native-video(ExoPlayer/Media3), TypeScript. 영상은 서버 API를 통해 MSE/WebRTC로 나가고, 녹화는 완성된 MP4 파일로 제공됩니다.

연결

주소는 아무 형태로나 입력할 수 있고, 앱이 하나의 형태로 정리합니다.

입력 결과
192.168.1.10 http://192.168.1.10:8080
192.168.1.10:9000 http://192.168.1.10:9000
nvr.local http://nvr.local:8080
https://cam.example.com https://cam.example.com

연결 확인 버튼은 서버의 /health를 호출합니다. 주소나 포트의 실수를 저장하기 전에 알 수 있습니다.

웹 인터페이스와의 차이

앱에는 감지, 이벤트, 출입 통제, PTZ, 카메라 설정이 없습니다. 앱은 보고 지켜보는 도구입니다. 제어는 웹 인터페이스에 남아 있습니다.

전체 안내서: docs/MOBILE.md — Android SDK 설치, «Unable to load script» 오류 해결, APK 크기 줄이기, 핫 리로드로 실행하기.


Home Assistant

이 통합은 카메라, 감지 이벤트, 출입 통제 문 열기를 Home Assistant로 가져옵니다. 함께 설치되는 대시보드 카드는 화면이 붙은 이벤트 피드입니다.

Home Assistant의 카메라 이벤트

항목 개수
카메라 카메라당 하나, 스트림은 서버의 RTSP 프록시를 통과
움직임 센서와 이벤트 카메라당 하나
카메라 버튼 스트림 시작, 스트림 재생성, 재부팅
문 버튼 출입 통제 문당 하나
서버 센서 연결된 카메라 수, 24시간 이벤트, 출입 통제, 디스크

이벤트는 즉시 도착합니다. 서버가 다음 폴링을 기다리지 않고 어시스턴트 주소로 요청을 보내 밀어 넣습니다. 요청에는 공용 비밀값으로 서명합니다.

어시스턴트는 외부 RTSP 프록시(9784)를 통해 카메라 스트림을 가져옵니다. 카메라에는 부하가 늘지 않습니다. 추가 독자는 미디어 서버 쪽에 생깁니다.

설치

HACS 사용: HACS → 통합 → 사용자 저장소에서 https://github.com/himik19872/OpenIPC-NRV를 통합 유형으로 추가하고, OpenIPC NVR을 설치한 뒤 Home Assistant를 재시작합니다.

수동 설치: custom_components/openipc_nvr 디렉터리를 어시스턴트 설정 디렉터리로 복사하고 재시작합니다.

설정: 설정 → 장치 및 서비스 → 통합 추가 → OpenIPC NVR. 서버 주소, 관리자 아이디와 비밀번호가 필요하고, 스트림을 위해서는 외부 RTSP 접근 자격 증명이 필요합니다.

전체 안내서: docs/HOME-ASSISTANT.md — 모든 엔티티 설명, 자동화 예제, 카드 설정, 녹화 보기, 그리고 동작하지 않을 때 할 일.


빠른 시작

요구 사항

  • Linux(Ubuntu에서 검증)
  • Docker 24+ 및 Docker Compose v2
  • 최소 2 GB 메모리와 10 GB 디스크
  • 같은 네트워크에 있고 RTSP로 접근 가능한 카메라

설치

git clone https://github.com/himik19872/OpenIPC-NRV.git
cd OpenIPC-NRV

# 설정을 만들고 비밀값을 지정합니다
cp .env.example .env
sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$(openssl rand -hex 32)|" .env

# 실행
./scripts/nvr.sh start

스크립트가 접근 주소를 출력합니다. 웹 인터페이스는 http://<서버 IP>:3001, 로그인은 admin / admin123입니다.

관리자 비밀번호를 즉시 바꾸십시오. 계정은 널리 알려진 자격 증명으로 시드 마이그레이션이 만들어 냅니다.

카메라 추가

  1. 스캐너 섹션을 열고 서브넷을 입력합니다. 예를 들어 192.168.1.0/24.
  2. 스캔을 누르면 찾아낸 카메라가 목록으로 나옵니다.
  3. 필요한 것을 골라 추가합니다. 스트림 경로와 자격 증명이 자동으로 채워집니다.
  4. 또는 카메라 섹션에서 main_stream과 sub_stream을 지정해 직접 추가합니다.

OpenIPC 카메라에는 Majestic에 설정된 자격 증명을 사용하십시오(프로젝트 기본값은 root와 펌웨어의 비밀번호입니다).


부팅 시 자동 시작

sudo ./scripts/install-service.sh

systemd 서비스 nvr이 만들어지고, 시스템이 시작될 때 컨테이너를 올립니다. 컨테이너에는 추가로 restart: unless-stopped 정책이 있어서 실패 후 자동으로 다시 시작합니다.

systemctl status nvr          # 서비스 상태
journalctl -u nvr -f          # 시작 로그
sudo ./scripts/install-service.sh --uninstall   # 자동 시작 제거

관리

./scripts/nvr.sh start      # 시작
./scripts/nvr.sh stop       # 중지
./scripts/nvr.sh restart    # 재시작
./scripts/nvr.sh status     # 상태와 주소
./scripts/nvr.sh logs       # 로그
./scripts/nvr.sh update     # 이미지 다시 빌드 후 갱신
./scripts/nvr.sh backup     # 데이터베이스를 ./backups로 덤프

문서

문서 내용
docs/DEPLOYMENT.md 자세한 배포, 설정, TLS, 원격 접근
docs/EXTERNAL-RTSP.md 외부 시스템에 스트림 제공: 주소, 자격 증명, OSD 출력 원리
docs/API.md 예제가 있는 REST API 전체 설명
docs/DETECTION-QUALITY.md 감지 정확도 조정: 오탐을 줄이고 객체를 다시 녹화에 넣는 방법
docs/PLANS.md 평면도: 도면, 장비 배치, 장치 상태
docs/SWITCHES.md PoE 스위치: 검색, 포트 모니터링, 전원으로 카메라 재부팅, 모델 차이
docs/CAMERA-VENDORS.md 다른 제조사 카메라: 공통 ONVIF 계층, Hikvision 어댑터, 인증, 검증된 기능
docs/SKUD-ACCESS.md 권한: 카드 소유자, 그룹, 문, 키 종류, 컨트롤러 다루기
docs/MOBILE.md Android 앱: 기능, 빌드, 설치, 오류 해결
docs/TRANSLATIONS.md 인터페이스 번역: 고치는 방법, 언어 추가, 한국어 현황
docs/ROADMAP.md 개발 계획: CUDA 감지, Home Assistant
docs/ARCHITECTURE.md 시스템 구조, 데이터베이스 설계, 내린 결정
docs/TROUBLESHOOTING.md 흔한 문제 진단
plans/ 최초 프로젝트 계획

실행 중인 서버에서 대화형 엔드포인트 목록을 볼 수 있습니다: http://<서버 IP>:8080/api/v1/docs


구조

구성 요소 기술 용도
백엔드 Go 1.22, chi, pgx REST API, 비즈니스 로직, 카메라 관리
웹 인터페이스 React 18, TypeScript, Vite MSE/WebRTC 플레이어, PTZ 조작판, 설정이 있는 SPA
미디어 서버 go2rtc RTSP 수신, MSE/WebRTC 배포, 녹화
모바일 앱 React Native, ExoPlayer Android 휴대폰과 TV에서 카메라와 녹화 보기
알림 Telegram Bot API, MAX Bot API 감지 이벤트와 서버 상태 메시지
AI 감지기 Python, YOLOv8, YAMNet, InsightFace 객체, 소리, 얼굴, 번호판(GPU CUDA)
이벤트 버스 NATS JetStream 카메라에서 감지기로 프레임, 이벤트를 데이터베이스로
데이터베이스 PostgreSQL 16 카메라, 이벤트, 녹화, 소리, 얼굴·번호판 목록
스토리지 MinIO(S3) 또는 로컬 디스크 영상 녹화와 스냅숏

저장소 구조

backend/            Go 백엔드
  cmd/server/       진입점
  internal/
    api/            라우터와 HTTP 핸들러
    service/        비즈니스 로직, PTZ, SSH, 스캐너, 소리, 인식
    repository/     PostgreSQL과 MinIO
    media/          미디어 서버 연동
    monitor/        카메라와 하드웨어 감시, 임계값과 알림
    notify/         Telegram과 MAX 발송, 메시지 조립, 기록
    nats/           감지·소리 이벤트 구독
    sysinfo/        CPU, 메모리, 디스크 조회(Linux)
    tunnel/         WireGuard 관리
  migrations/       SQL 마이그레이션(001-007)
webui/              React SPA
mobile/             React Native 앱(휴대폰과 TV가 한 빌드에)
  src/net/          API 클라이언트와 서버 주소 해석
  src/screens/      휴대폰 화면: 서버, 로그인, 카메라, 보기, 녹화
  src/tv/           TV 모드: 그리드, 레이아웃, 설정
  src/storage/      레이아웃과 서버를 장치에 저장
  src/state/        선택된 서버, 토큰, 클라이언트
  android/          네이티브 부분: APK 빌드, TV 판별
ai-detector/        Python 감지 서비스(객체, 소리, 얼굴, 번호판)
scripts/            관리와 설치 스크립트
docs/               문서
plans/              프로젝트 자료

개발

백엔드

cd backend
go build ./...          # 빌드
go vet ./...            # 정적 분석
go test ./...           # 테스트

로컬 실행에는 localhost:5434의 PostgreSQL이 필요합니다.

docker compose up -d postgres minio go2rtc nats
go run ./cmd/server

웹 인터페이스

cd webui
npm install
npm run dev             # :3000의 개발 서버, :8080으로 프록시
npm run build           # 프로덕션 빌드

AI 감지기

cd ai-detector
pip install -r requirements.txt
python frame_publisher.py --rtsp rtsp://localhost:8554/<camera-id> --camera-id <uuid>
python main.py

yolov8n.pt 모델은 처음 실행할 때 자동으로 내려받습니다.


보안

서버를 인터넷에 노출하기 전에:

  • JWT_SECRET(openssl rand -hex 32)과 DB_PASSWORD 교체
  • 웹 인터페이스에서 관리자 비밀번호 변경
  • 외부 RTSP 접근 비밀번호(EXTERNAL_RTSP_PASS) 변경 — 기본값은 viewer
  • PostgreSQL(5434), MinIO(9000/9001), NATS(4222) 포트를 외부 네트워크에서 차단
  • 웹 인터페이스 앞에 TLS 종단(nginx/Caddy) 구성
  • go2rtc API(1984) 접근 제한
  • 카메라의 기본 자격 증명을 사용하지 않기

더 자세한 내용은 docs/DEPLOYMENT.md.


알려진 제한

제한 원인 대처
카메라 스피커를 통한 양방향 대화가 동작하지 않음 카메라가 역방향 오디오 채널을 지원하지 않습니다. RTSP OPTIONS 응답에 ANNOUNCE와 RECORD 메서드가 없습니다. 파이프라인은 구현되어 있고 이런 지원이 있는 카메라에서는 동작합니다 자동으로 확인합니다. 대화 버튼은 지원할 때만 나타납니다
소리 감지가 CPU를 사용함 YAMNet이 객체 감지를 위해 GPU와 경쟁하지 않도록 CPU에서 실행됩니다 카메라 설정에서 불필요한 카메라나 소리 클래스를 끄십시오
WireGuard 터널이 자동으로 관리되지 않음 internal/tunnel/wg_manager.go는 골격이고 netlink 연동이 없습니다 wg-quick으로 피어를 직접 설정하십시오
온도와 그래픽카드 상태는 에이전트를 통해서만 보임 컨테이너 안에서는 호스트의 센서와 GPU에 접근할 수 없습니다 호스트에 host-agent를 설치하십시오: sudo install -m 0755 agent.py /opt/nvr-agent/agent.py
한국어 인터페이스는 남한 규범인데 국기는 북한 것임 북한 규범은 고유한 규칙과 용어를 가진 별개 언어(ko-KP)이고 원어민이 필요합니다. 반면 국기는 고객이 있는 나라의 것이어야 합니다 임시 조치입니다. 북한 규범을 아는 교정자가 필요합니다: docs/TRANSLATIONS.md
일부 오류 메시지는 러시아어로 남습니다 장치의 응답을 서버가 문장으로 만들기 때문이며 문구가 아닙니다 옮기는 만큼 줄어듭니다. 목록: docs/TRANSLATIONS.md
중국어와 한국어는 한자 글리프가 있는 폰트가 필요함 벡터는 항상 그려지지만 한자는 텍스트이고 시스템 폰트가 그립니다 인터페이스를 여는 기기에 Noto Sans SC / Noto Sans KR을 설치하십시오

새로운 소식

2026-10-04 — 인터페이스 언어: 러시아어, 영어, 중국어, 한국어

인터페이스가 네 가지 언어 사이에서 전환됩니다. 전환기는 서버 설정 페이지에서 시간과 네트워크 설정 옆에 있습니다. 선택은 브라우저에 기억되므로 워크스테이션마다 언어가 달라도 됩니다. 서버는 이와 관련해 아무것도 저장하지 않습니다.

언어는 국기로 표시되고, 그 그림은 코드 안의 벡터입니다. 외부에서 불러오는 것이 없습니다. 서버는 인터넷이 아예 없을 수도 있는 네트워크에 있고, 국기 자리에 빈 공간이 남는 일은 한자 폰트 때 이미 겪었습니다.

인터페이스 문구는 모두 번역되었습니다. 모든 화면을 중국어와 한국어로 돌며 확인했습니다. 러시아어로 남는 것은 사람이 입력한 카메라와 컨트롤러 이름뿐입니다. 러시아어는 대체 언어로 남아 있어, 혹시 빠뜨린 문구가 있어도 키 이름 대신 단어가 보입니다.

한국어에 대해서는 솔직하게. 글은 남한 규범으로 번역되어 있고 전환기에는 북한 국기가 있습니다. 그쪽 고객의 요청에 따른 것입니다. 북한 규범은 고유한 단어와 용어를 가진 별개의 언어이고 원어민이 필요합니다. 지금은 국기와 규범이 일치하지 않으며, 이 사실이 docs/TRANSLATIONS.md에 번역을 고치고 새 언어를 추가하는 방법과 함께 적혀 있습니다.

2026-10-03 — 출입 통제에 Beward 인터폰: 기록이 남는 문 열기

인터폰이 출입 장치로 출입 통제에 등록되었습니다. DS07P-LP에는 잠금 릴레이와 호출 버튼이 있고, 출입 통제 섹션에는 이미 출입 기록, 이벤트 촬영, 영상 녹화가 있습니다. 그래서 인터폰은 카메라 옆이 아니라 그쪽에 설명되어 있습니다.

현장에서 어떻게 연결했는가. 인터폰의 릴레이를 Z5R 컨트롤러의 «출구 버튼» 입력에 연결했습니다.

기록에 어떤 의미가 있는가 — 그리고 이런 구성의 주된 이유입니다. Z5R 입장에서 인터폰 개방은 카드 출입이 아니라 출구 버튼 누름으로 보입니다. 출입 통제 이벤트에는 이미 그런 기록이 있습니다: event_type=exit_button. 이 기록으로는 누가 어디서 문을 열었는지 알 수 없습니다 — 인터폰에서인지, Z5R 웹 인터페이스에서인지, 아니면 문 앞의 진짜 버튼인지. 세 경우가 모두 구별되지 않습니다. 그래서 기록을 우리 쪽에서 남깁니다. 그래야만 문이 인터폰으로 열렸다는 흔적이 남습니다.

한 번의 개방이 두 개의 기록을 만듭니다 — 사유가 적힌 우리 기록과 Z5R의 exit_button 입니다. 두 번 출입한 것이 아니라, 하나의 동작을 양쪽에서 본 것입니다.

문 상태는 «알 수 없음»으로 표시되며, 이것이 정직한 표시입니다. 장치는 경보만 알리고, 검증한 개체에서는 항상 NO Alarm으로 답합니다. 문의 위치는 읽을 수 없습니다. 모르면서 «잠김»이라고 보여 주는 것은, 시간을 읽지 못했는데 «정확»이라고 표시하던 카메라 시계와 같은 실수입니다.

실제 장치에서 검증한 것: 연결(컨트롤러 online)과 문 목록입니다. 문 열기는 검증하지 않았습니다. 검증한다는 것은 실제 문을 여는 일이기 때문입니다. 주소와 명령 파라미터는 인터페이스 설명과 대조해 확인했고, 대역 장치에서 테스트로 덮었습니다.

SIP와 소리는 현장에 이미 설정되어 있고, 우리는 아직 사용하지 않습니다. SIP 서버 등록, 두 번호로의 호출, 통화 중 숫자 입력으로 릴레이 닫기가 설정되어 있습니다. 장치 쪽의 양방향 소리가 켜져 있어(DoubleAudio=on) 마이크와 스피커가 우리가 이미 받고 있는 RTSP 스트림으로 전달됩니다.

자세한 내용 — docs/SKUD-ACCESS.md.

2026-10-03 — Beward 카메라: 자체 HTTP API

제조사가 전체 인터페이스 설명서를 넘겨주었습니다 — 219쪽, 63개의 서로 다른 주소. 장치의 모든 설정을 덮고 있으며, 이는 ONVIF가 주는 것보다 한 자릿수 더 많습니다.

Beward에는 자체 API를 주된 방법으로, ONVIF를 예비로 택했습니다. ONVIF는 이 장치에서 동작하지만 가동 시간을 전혀 알려 주지 않습니다. 자체 API는 알려 주므로, 이제 카드에서 장치가 마지막으로 언제 부팅했는지 볼 수 있습니다. 게다가 장치에서 ONVIF를 꺼도 자체 인터페이스는 그대로 쓸 수 있습니다.

인증은 Digest, Basic을 예비로. 설명서는 둘 다 허용합니다. 예비가 없으면 Basic으로 설정된 장치에 접근할 수 없게 됩니다.

두 개의 오류를 찾았고, 두 번째가 첫 번째보다 위험했습니다. 장치 시간을 세계시로 해석해 지역 시간으로 환산해서, 표시가 시간대만큼 밀렸습니다. 인터폰 시계가 14:24가 아니라 17:24를 보여 주었습니다. 시간은 한 시점을 환산하는 것이 아니라 지역 시간대에서 다시 조립해야 합니다.

두 번째는 스크린샷에서 드러났습니다. 시계를 읽지 못했을 때 카드가 «01.01.1»이라는 지어낸 날짜를 보여 주고 편차를 **«정확»**으로 출력했습니다. 즉 데이터가 없는 것이 «카메라 시계가 정확하다»는 단정으로 바뀌었고, 운영자는 정상이라고 확인받은 것을 확인하러 가지 않게 됩니다. 이제 빠진 데이터는 빠진 대로 보입니다. ONVIF의 비트레이트처럼 그 줄이 아예 없습니다.

두 출처의 대조. 응답에 일련번호 필드가 따로 없지만 DeviceID가 일련번호입니다. 같은 장치가 ONVIF로 정확히 이 값을 보고합니다(293239). 이는 추측이 아니라 검증입니다.

이 모델에는 얼굴 인식이 없습니다. 설명서에 항목은 있지만 facecfg_cgi가 404를 돌려줍니다. 기능 집합은 설명서 버전이 아니라 모델에 달려 있습니다.

덤으로 ONVIF가 Beward에서는 되고 Hikvision에서는 왜 안 되는지 알게 되었습니다. Beward는 공장 출하 시부터 켜져 있고(root.Properties.API.ONVIF.ONVIF=yes), Hikvision 카메라에서는 켠 적도 ONVIF 사용자를 만든 적도 없었습니다.

자세한 내용 — docs/CAMERA-VENDORS.md.

2026-10-03 — 다른 제조사 카메라: 공통 ONVIF 계층과 Hikvision

카메라에서 직접 읽는 장치 정보와 상태. 카메라 카드에 장치 블록이 생겼습니다: 모델, 펌웨어, 일련번호, MAC, 가동 시간, CPU·메모리 사용량, 스트림 파라미터. 데이터는 우리 데이터베이스가 아니라 카메라 자체에서 읽습니다. 기록된 내용과의 차이 자체가 주의를 기울일 이유입니다.

SSH 없이 재부팅. 장치 카드에 확인 창이 있는 버튼이 있습니다. 실제 카메라에서 검증했습니다. 80초(Hikvision)와 60초(Vivotek) 후에 돌아옵니다.

두 단계의 접근. 공통 ONVIF는 모든 브랜드에 하나의 코드이고, 공통 언어가 더 적게 주는 곳에는 Hikvision 자체 어댑터(ISAPI)를 씁니다.

Hikvision이 ONVIF를 쓰지 않는 이유. 현장 카메라에서 ONVIF가 실패했습니다. 준비 호출은 통과하지만 인증이 필요한 호출은 «sender is not authorized»로 거부됩니다. 원인은 코드가 아니라, 카메라에서 ONVIF가 켜져 있지 않고 ONVIF 사용자도 만들어지지 않았기 때문입니다. 웹 사용자는 ONVIF 사용자가 아닙니다. 같은 비밀번호로 HTTP Digest는 즉시 동작합니다.

시계 편차가 인터페이스에 보입니다. 카메라 192.168.1.63의 시계가 139일 느립니다. 카드에서 바로 보입니다. 예전에는 녹화의 밀린 시각표로만 알 수 있었습니다.

세 가지 오류를 찾아 고쳤습니다:

  • 시계 편차의 부호가 뒤바뀌어 있었습니다. 양수가 느림을 뜻하는데 «빠름»으로 표시되었습니다. 139일 느린 카메라에서 검증했습니다.
  • 실패가 확실한 곳에도 재부팅 버튼이 표시되었습니다. 기능 확인이 타입 단언 뒤에 있었고, 어댑터 체인에서는 그 단언이 항상 참입니다.
  • 미디어 서비스 주소를 카메라에서 그대로 가져왔습니다. 터널 뒤의 카메라에는 내부 주소가 적혀 있어서, 장치가 완전히 정상인데도 요청이 허공으로 갔습니다. 이제 호스트만 바꾸고 포트와 경로는 그대로 둡니다.

인증을 공통 패키지 internal/httpdigest로 옮겼습니다. 재시도 전에 요청 본문을 복원하는 기능도 함께 넣었습니다. Hikvision 재부팅은 비어 있지 않은 본문을 요구하는데, 복원하지 않으면 명령이 조용히 실행되지 않습니다.

자세한 내용 — docs/CAMERA-VENDORS.md.

2026-10-02 — MAC 표로 카메라 연결 자동 판별

카메라와 포트의 연결을 자동으로 판별합니다

스위치는 MAC 주소 표를 유지하고 어떤 주소가 어느 포트에서 보이는지 알려 줍니다. 시스템이 이 주소를 카메라 데이터베이스와 맞춰 연결을 스스로 제안합니다. 포트를 손으로 지정할 필요가 없습니다.

  • 스위치 페이지가 포트에 있는 모든 장치를 보여 줍니다. 카메라에 없는 것까지 포함하므로 불필요한 장비를 찾아내는 데 도움이 됩니다
  • «카메라 연결 자동 찾기» 버튼이 일치하는 것을 찾아 무엇이 바뀔지 보여 줍니다
  • 업링크 포트 뒤의 장치는 따로 표시합니다. 이 스위치에 연결된 것이 아니므로 여기에 묶으면 안 됩니다

선별 규칙은 의도적으로 좁습니다. 실수의 대가가 크기 때문입니다. 잘못된 연결은 엉뚱한 카메라의 전원을 재부팅하게 만듭니다. 주소가 정확히 하나의 PoE 포트에서 보이고, 정확히 프로젝트의 카메라 하나에 속할 때만 제안합니다.

모델마다 다르게 동작하고, 이것이 인터페이스에 보입니다

세 경우가 있습니다. 포트를 알려 주는 경우(자동 연결), 표는 있지만 포트가 없는 경우(수동 연결), 명령을 지원하지 않는 경우. 현장 장비에서 세 가지를 모두 만났고, 그중 한 스위치의 펌웨어는 자기 포트에 있는 장치까지 포함해 모든 레코드에 같은 마스크를 돌려주었습니다.

포트 번호 매기기를 고쳤습니다

반전 규칙은 스위치 자체의 웹 인터페이스에서 가져왔고 두 모델에서 확인했습니다. 이전 버전은 포트 목록 전체를 뒤집었는데, 장치는 PoE 포트만 뒤집고 업링크 포트는 끝에 남깁니다. GPS204V3에서는 이것이 1번 포트로 보낸 명령이 업링크 포트로 가는 것을 뜻했습니다. 그 포트로 서버와의 연결이 지나갑니다.

규칙은 추측이 아닙니다. GPS204V3 인터페이스에서 PoE 포트 네 개일 때 portIndex 집합은 [3,2,1,0]이고, PS208GV3에서는 [0..7]입니다.

업데이트할 때: 이 수정은 반전 순서를 쓰는 모델에서 이미 저장된 연결의 의미를 바꿉니다. 이전에 만든 연결을 확인하십시오.

실제 장치에서 검증했습니다

전체 사슬을 확인했습니다. 시스템이 1번 포트의 카메라를 찾아냈고, 그 포트를 전원으로 재부팅하니 바로 그 카메라가 꺼졌고, 전원을 다시 넣은 뒤 카메라가 같은 포트로 표에 돌아왔습니다.

2026-10-02 — PoE 스위치 관리

스위치 등록과 포트 모니터링

별도 페이지: 네트워크에서 스위치 검색, 포트 상태, 조작 기록. 관리형 SSCPOE PoE 스위치를 지원하며 PS204, PS208GV3, GPS204V3에서 검증했습니다.

  • 브로드캐스트 검색: 주소를 미리 알 필요가 없고 일련번호가 식별자입니다
  • 포트 표: 링크와 속도, PoE 상태, 소비 전력, 송수신량, 연결된 카메라
  • 1분마다 백그라운드로 조회하며, 인터페이스를 열지 않아도 상태를 알 수 있습니다

멈춘 카메라를 전원으로 재부팅

응답을 멈춘 카메라는 버튼 한 번으로 재부팅합니다. 올라갈 필요가 없습니다. 전원 순환은 실제 장치에서 검증했습니다. 포트의 전원이 끊기고, 잠시 기다린 뒤 다시 들어옵니다.

카메라와 포트의 연결

카메라 카드에서 어느 스위치의 어느 포트에 연결되었는지 볼 수 있습니다. 블록이 포트의 전원, 링크 유무, 카메라 자체의 상태를 함께 보고 결론을 내줍니다.

  • 전원과 링크는 있는데 카메라가 조용하다 — 멈춘 것이고 재부팅이 도움이 됩니다
  • 전원은 있는데 링크가 없다 — 회선이 끊겼고 재부팅은 도움이 되지 않습니다
  • 전원이 없다 — 카메라가 꺼져 있습니다

위험한 조작에 대한 보호

  • 업링크 포트는 끌 수 없습니다. 서버와의 연결이 그리로 지나가고, 전원을 끊으면 스위치 전체를 제어하지 못하게 될 수 있습니다
  • PoE를 지원하지 않는 포트는 전원 명령을 받지 않습니다
  • 없는 포트로의 연결은 거부됩니다
  • 전원 조작에는 확인이 필요합니다

실제 장비에서 밝혀낸 모델 간 차이

한 제품군이 다르게 동작하고, 이는 반영되어 있습니다.

  • 일부 모델은 인증 없이 상태를 알려 주고, 일부는 비밀번호를 요구합니다. 로그인 요구는 «연결 없음»이 아니라 별도의 상태입니다
  • 온도가 서로 다른 필드에 있고, 그중 한 모델의 «온도» 필드에는 관계없는 숫자가 들어옵니다
  • 포트 수와 PoE 포트 수가 다릅니다. GPS204V3은 포트가 여섯 개인데 전원은 네 개에만 있습니다
  • 일부 모델은 포트 번호 순서가 반대입니다. 그 표시는 설정에 저장되고 손으로 고칠 수 있습니다. 여기서 실수하면 명령이 엉뚱한 포트로 가기 때문입니다

스크린샷에서 카메라 비밀번호 제거

인터페이스 스크린샷에 rtsp://admin:비밀번호@… 형태의 스트림 주소가 찍힌 것을 발견했습니다. 공개 문서에 카메라 자격 증명이 그대로 드러난 것입니다. 이제 스크린샷 스크립트가 스트림 주소의 아이디:비밀번호를 모든 그림에서 지웁니다. 스크린샷 작성자의 선택에 맡기지 않습니다.

이 프로젝트를 공개하신 적이 있다면 카메라 비밀번호를 바꾸십시오. 이전 버전의 스크린샷이 git 이력에 남아 있습니다.

2026-09-26 — 알림, 휴대폰과 Android TV 앱

Telegram과 MAX 알림

이벤트가 스냅숏이나 짧은 영상과 함께 메신저로 갑니다. 실제 사용에서 검증했습니다. 전달, 첨부, 발송 기록.

  • 감지 이벤트: 객체, 번호판, 얼굴, 선 통과, 출입 통제, 소리
  • 채널마다 따로 카메라와 이벤트 유형을 선택
  • 신뢰도 임계값, 반복 사이의 간격, 방해 금지 시간
  • 지정한 시각에 하루 요약 발송
  • 발송 기록: 무엇이 갔고 무엇이 가지 않았으며 왜인지
  • 한 채널의 실패가 다른 채널을 막지 않습니다. Telegram을 못 쓰면 MAX는 계속 동작합니다
  • MAX — 러시아를 위한 두 번째 채널: 프록시 없이 직접 접근할 수 있습니다. 메시지 텍스트는 따로 만듭니다. MAX API는 마크업에 더 엄격해서 Telegram 메시지를 그대로 받지 않습니다

서버와 카메라 감시

별도 탭은 «무엇을 문제로 볼 것인가»에 답합니다. «어디로 보낼 것인가»가 아닙니다. 메시지는 같은 채널로 나갑니다.

  • 카메라가 네트워크에서 사라짐 — 재부팅 중에 놀라지 않도록 약간의 지연을 둡니다
  • 서버 시작 시 카메라에 접근할 수 없음 — 즉시 알립니다
  • 카메라가 다시 연결됨 — 문제가 지나갔음을 알 수 있습니다
  • CPU 부하 과다, 메모리 부족, 디스크 부족
  • 장비 과열과 그래픽카드 문제
  • 문제가 해결될 때까지 반복 알림, 그리고 방해 금지 시간
  • 온도와 그래픽카드는 호스트의 에이전트가 측정합니다. 컨테이너 안에서는 보이지 않습니다

알림의 시간

메시지의 시간이 UTC였습니다. 호스트 머신의 시간대가 이미지로 전달되지 않았습니다. 이제 /etc/localtime이 모든 컨테이너에 마운트되고, PostgreSQL은 추가로 TZ와 PGTZ를 받습니다. 시간대를 데이터 디렉터리에서 가져오는데, 마운트된 시간대 파일 하나로는 부족하기 때문입니다.

휴대폰 앱

  • 미리보기 프레임과 온라인/오프라인 상태로 카메라 보기
  • 녹화 사유와 함께 녹화 보기: 번호판, 얼굴, 객체 — 목록에서 바로 보입니다
  • 카메라 소리
  • 서버마다 별도의 토큰: 서버를 전환해도 다시 로그인할 필요가 없습니다

Android TV 앱

  • 1~25분할 그리드, TV 화면에 맞춘 형태
  • 이름이 있는 레이아웃: 카메라 구성, 그리드 크기, 문구, 소리
  • 한 번에 한 카메라의 소리
  • 리모컨 조작, 화면까지의 거리에 맞춘 큰 요소
  • 밀집 그리드에서는 문구가 스스로 숨습니다. 작은 타일에서는 화면을 가리기 때문입니다

2026-09-22 — 외부 RTSP 접근, SSH 없는 카메라 제어, 프레임 미리보기

외부 RTSP 접근

외부 시스템에 스트림 제공: 영상벽, 녹화기, 외부 분석 장비. 외부 소비자는 NVR에서 스트림을 가져가고 카메라에 직접 연결하지 않습니다. 이는 동시 RTSP 세션 수를 제한하는 장치의 부하를 덜어 줍니다.

  • 주소 /cameras/{N}/streaming/{main|sub}, 별도 포트 9784 (채널 번호는 1을 뺀 값: 채널 1 → cameras/0)
  • 별도 인증: 외부 시스템은 읽기만 가능하고 내부 소비자는 이전과 같습니다
  • 트랜스코딩 없는 스트림 — CPU 부하가 늘지 않습니다
  • 외부 접근 페이지: 연결 파라미터, 복사할 수 있는 바로 쓸 링크, 채널 상태
  • 카메라 추가 시 채널 번호 자동 지정
  • 자세한 내용은 docs/EXTERNAL-RTSP.md

SSH 없는 카메라 제어

  • 펌웨어 HTTP API(Majestic)로 카메라 설정: 영상(fps, 비트레이트, 코덱, 프레임 크기), 이미지(밝기, 대비, 채도, 좌우 반전), 야간 모드, OSD
  • 바뀐 필드만 보냅니다. 카메라의 나머지 설정은 건드리지 않습니다
  • 기록 전 값 검증: 카메라 성능이 낮아서 잘못된 비트레이트는 스트림을 떨어뜨립니다
  • SSH 대신 API로 카메라 재시작
  • 엔드포인트 구성이 다른 두 펌웨어 빌드 지원
  • API가 없는 카메라는 SSH를 그대로 사용

카메라 상태 감시

  • 1분마다 /metrics와 /api/v1/sources 조회
  • 지표: CPU 사용률, 가용 메모리, 센서 fps, 인코더 프레임 끊김, 야간 모드
  • «정상 / 주의 / 문제» 판정과 그 이유 설명
  • 지표는 카메라 목록과 카메라 카드에서 볼 수 있습니다

스트림 대신 프레임 미리보기

  • 카메라 목록에서 HLS 스트림 대신 한 프레임: RTSP 세션 대신 HTTP 요청 한 번이라 카메라가 많은 현장에서 훨씬 저렴합니다
  • 서버에서 프레임 축소: 4K → 480픽셀(470 KB → 17 KB)
  • Basic·Digest 인증 지원, 제조사별 프레임 경로
  • HTTP 엔드포인트가 없으면 영상 스트림에서 프레임을 예비로 가져옵니다
  • 요청 대기열: 동시에 최대 세 대의 카메라만 조회합니다

기타

  • 스트림의 OSD 표시를 문서화했습니다. 표시는 카메라가 넣고 녹화, 브라우저, 외부 시스템에 모두 같습니다
  • 스트림 복구 과정의 임시 디버그 로그를 제거했습니다

2026-09-19 — 소리, 인식, 카메라 제어

카메라 소리

  • 플레이어에서 소리 청취: 영상과 동기화된 별도 오디오 트랙
  • G.711 → AAC 변환: 브라우저는 HLS에서 G.711을 재생하지 못합니다
  • 소리를 별도 go2rtc 스트림으로 보내고 영상은 건드리지 않습니다

소리 감지

  • YAMNet(AudioSet 521개 클래스): 총성, 비명, 절규, 유리 깨짐, 폭발, 개 짖음, 사이렌, 차량 경보, 말소리, 음악
  • 카메라별 임계값, 클래스, 쿨다운 설정
  • 음량으로 무음을 잘라 냄 — 마이크 잡음에 의한 오탐 방지
  • 인터페이스의 소리 섹션: 클래스별 필터, 경보 이벤트 강조

얼굴과 번호판 인식

  • 등록된 얼굴과 번호판 목록, «등록됨» / «미등록» / «차단» 표시
  • 설정 가능한 번호판 탐색 영역 — OCR이 더 이상 카메라 OSD 메뉴를 잡지 않습니다
  • 지역을 고려한 번호판 형식 검증

녹화와 저장

  • 트리거(객체, 선, 얼굴, 번호판)에 의한 녹화 또는 상시 녹화
  • 파일 이름에 트리거 표시 — 녹화가 왜 시작되었는지 알 수 있습니다
  • 브라우저에서 재생, 저장 위치 선택(로컬 디스크 또는 S3)

카메라 제어

  • 웹 인터페이스를 통한 완전한 제어: 데이터는 PostgreSQL, 수작업 설정 파일 없음
  • 저장 전 스트림 검사: 코덱, 해상도, 소리 유무
  • RTSP 주소에서 IP 자동 입력
  • IP와 비밀번호를 고치면 미디어 서버 경로가 자동으로 갱신됩니다
  • 카메라와 스트리머를 재시작하는 버튼
  • 마이크와 스피커가 없는 카메라는 자동으로 판별합니다

GPU 가속

  • CUDA에서 감지(NVIDIA P104-100): 프레임당 약 13 ms, CPU에서는 83 ms

라이선스

이 프로젝트는 영상 감시 용도로 «있는 그대로» 배포됩니다. 서드파티 구성 요소는 각자의 라이선스를 유지합니다(go2rtc — MIT, YOLOv8 — AGPL-3.0, MinIO — AGPL-3.0, PostgreSQL — PostgreSQL License).