
브라우저를 꺼도 알람이 쌓인다 (PLCLink Phase 2 완성기)
PLC 모니터링 툴을 직접 만든다면, 언제 "완성됐다"고 말할 수 있을까요?
기능이 다 들어갔을 때? 아니면 현장에서 아무 설명 없이 돌아갈 때?
오전 8시, 출근해서 가장 먼저 하는 일
공장에서 일하는 엔지니어라면 아마 비슷한 아침을 보내고 있을 겁니다.
사무실 문을 열자마자 현장으로 내려가 HMI(설비 앞 모니터) 앞에 서서, 어젯밤 알람이 뭐가 났는지 확인합니다.
설비가 제대로 돌았는지, 어디서 멈췄는지, 그 시점에 다른 값들은 어땠는지.
어젯밤 혼자 돌아간 설비가 "알아서 잘 됐는지"를 확인하는 루틴이죠.
문제는 이겁니다.
HMI가 없는 설비는요?
설치 초기, 시험 가동 중, 또는 소규모 라인에서 HMI가 아직 붙지 않은 설비는 어떻게 확인하나요?
PLCLink는 그 답을 "브라우저로 열어서 보면 됩니다"로 만들고 있는 프로젝트입니다.
Phase 0에서 통신을 확인했고, Phase 1에서 IO 상태와 데이터 리스트를 만들었고, Phase 1.5에서 알람/트리거/이력 기능을 추가했습니다.
그리고 오늘 이야기할 Phase 2에서, 드디어 현장에서 진짜로 쓸 수 있는 수준이 됐습니다.

Phase 1.5에서 멈춰 있던 문제
Phase 1.5가 끝났을 때, 기능 목록만 보면 꽤 그럴싸했습니다.
- IO 포인트 실시간 모니터링 ✓
- 데이터 리스트 폴링 및 기록 ✓
- 알람 룰 설정 + 이력 저장 ✓
- 트리거 기반 스냅샷 캡처 ✓
하지만 실제로 현장에서 써보니, 치명적인 세 가지 문제가 있었습니다.
첫째, 저장했는지 알 수가 없었습니다.
저장 버튼을 눌러도 화면에 아무 변화가 없으면, 사람은 본능적으로 다시 누릅니다.
그리고 또 누릅니다. 어딘가 등록이 두 번 됐을 수도 있고, 아니면 원래부터 안 됐을 수도 있고.
확신이 없는 인터페이스는 현장에서 신뢰를 잃습니다.
둘째, 새로고침하면 설정이 날아갔습니다.
IO 폴링을 켜놓고 다른 페이지로 이동하거나 새로고침을 하면, 폴링이 꺼진 상태로 초기화됐습니다.
현장에서는 브라우저를 새로고침하는 일이 생각보다 자주 일어납니다.
화면이 이상하게 보이면 일단 새로고침하니까요.
셋째, 그리고 가장 큰 문제로 브라우저 탭을 닫으면 알람 감시가 멈췄습니다.
Phase 1.5의 알람 감시는 브라우저 안에서 돌아가고 있었습니다.
브라우저가 1초마다 PLC에 직접 연결해서 값을 읽고, 알람 조건을 판단하고, DB에 기록하는 구조였죠.
탭을 닫는 순간, 이 모든 게 멈춥니다.
야간에 기계가 돌아가는 동안 브라우저를 열어두지 않으면, 그 사이에 난 알람은 기록되지 않습니다.
아침에 출근해서 알람 이력을 보는 게 의미 있으려면, 밤새 브라우저가 켜져 있어야 한다는 뜻이었습니다.
이건 해결해야 했습니다.
Phase 2의 목표 : "쓸 수 있는"과 "쓰기 편한"의 차이

Phase 2를 시작하면서 목표를 세 가지로 잡았습니다.
- UX 완성도 : 현장 운전자가 화면을 보고 불안해하지 않을 것
- 서버 사이드 안정성 : 브라우저 없이도 알람/트리거 감시가 지속될 것
- 데이터 시각화 : 숫자가 아닌 차트로 추세를 볼 것
요리 비유를 들자면, Phase 1.5는 재료가 다 준비된 상태였습니다.
Phase 2는 그걸 제대로 조리해서 실제로 먹을 수 있게 만드는 과정이었습니다.
작은 배려들이 쌓이면 신뢰가 된다 (UX 8가지 개선)

"저장됐나요?" = Toast 알림 시스템
서비스 업계에는 "피드백 없는 액션은 실패한 UX"라는 말이 있습니다.
Phase 2에서 가장 먼저 넣은 것이 Toast 알림이었습니다.
저장 버튼을 누르면 화면 오른쪽 아래에 3.5초 동안 알림이 뜹니다.
- 초록색: "저장되었습니다"
- 빨간색: "저장 실패: site 1 없음 (HTTP 404)"
- 파란색: 정보성 안내
이제 저장 버튼을 한 번만 누르면 됩니다.
성공했는지 실패했는지, 실패했다면 왜 실패했는지까지 바로 화면에 나옵니다.
장갑을 낀 채로 작업하는 현장에서는 화면을 오래 쳐다볼 시간이 없습니다.
0.5초 안에 결과가 뜨는 것과 안 뜨는 것의 차이는 큽니다.
"왜 아무것도 안 보이지?" : 로딩 상태 통일
데이터를 불러오는 동안 화면이 비어있으면, 현장에서는 "고장났나?"라고 생각합니다.
특히 현장 PC는 사양이 낮아서 데이터 로딩에 2~3초가 걸리는 경우도 있습니다.
모든 페이지에 Spinner(로딩 중 애니메이션)와 EmptyState(데이터 없을 때 안내 메시지)를 통일해서 적용했습니다.
작은 변화처럼 보이지만, "이 프로그램이 지금 뭔가 하고 있구나"를 사용자가 알 수 있게 해주는 중요한 디테일입니다.
"뭔가 잘못됐는데 뭐가 잘못됐는지 모르겠어" : 에러 메시지 구체화
이전: "저장 실패"
이후: "저장 실패: site 1 없음 (HTTP 404)"
에러가 났을 때 원인을 알면 스스로 해결할 수 있습니다.
모르면 담당자를 찾아야 합니다.
현장에서 직접 원인을 파악할 수 있도록, 서버가 보내는 에러 내용을 그대로 Toast에 표시합니다.
키보드 단축키 + 툴팁
모든 입력 폼에서 Enter로 저장, Esc로 취소가 됩니다.
반복 작업이 많은 셋업 과정에서 마우스를 쓰지 않아도 됩니다.
설비를 여러 대 셋업할 때 체감 속도가 눈에 띄게 빨라집니다.
cooldown_ms(연속 트리거 방지 대기시간) 필드에는 ? 아이콘을 달아서 마우스를 올리면 설명이 나오게 했습니다.
"이 숫자가 뭐야"를 설명할 사람이 없어도 혼자 이해할 수 있습니다.
페이지를 이동해도 설정이 남는다 — 상태 지속성
IO 폴링을 켜두고 다른 페이지로 이동해도, 다음에 돌아왔을 때 폴링이 켜진 상태로 유지됩니다.
브라우저를 새로고침해도 마찬가지입니다. localStorage를 활용해서 설정 상태를 브라우저에 기억시켜 두었습니다.
케이블을 뽑아도 5초면 돌아온다 = 자동 재연결

현장에서 PLC 케이블이 빠지는 건 예상치 못한 일이 아닙니다.
기계를 이동시키다가, 청소하다가, 사람이 지나가다 걸려서. 다양한 이유로 연결이 끊길 수 있습니다.
Phase 1.5에서는 케이블이 다시 꽂혀도 브라우저에서 수동으로 "연결 확인" 버튼을 눌러야 했습니다.
Phase 2에서는 자동으로 복구됩니다.
2단계 체크 인터벌 방식:
- PLC가 연결된 상태: 30초마다 상태 확인 (부하 최소)
- PLC 연결이 끊긴 상태: 5초마다 재시도 (빠른 재감지)
케이블을 다시 꽂으면 최대 5초 이내에 자동으로 연결 상태로 복구됩니다.
수동 개입이 필요 없습니다.
기능 개선들 : 현장에서 실제로 요청이 오는 것들
알람 이력 날짜 필터
납품 후 한 달이 지나면 알람 이력이 수백 건 쌓입니다.
"어제 알람만 보고 싶다"는 건 당연한 요구입니다.
Phase 2에서는 오늘 / 어제 / 7일 / 전체 버튼으로 필터링할 수 있고, "더 보기 (+50)" 버튼으로 추가 로드가 가능합니다.
확인 모드에서도 쓰기 이력을 볼 수 있다
PLCLink에는 두 가지 모드가 있습니다.
엔지니어 모드(값 쓰기, 설정 변경 가능)와 확인 모드(읽기 전용). 현장 운전자는 주로 확인 모드를 씁니다.
설비 이상이 생겼을 때, 운전자 입장에서 가장 먼저 확인하고 싶은 게 있습니다.
"최근에 누가 뭔가 바꿨나?" 하는 것입니다.
Phase 2에서는 확인 모드에서도 쓰기 이력 탭을 볼 수 있습니다.
읽기 전용으로, 삭제 버튼은 숨겨진 채로.
설비 문제가 생겼을 때 원인을 스스로 추적할 수 있는 수단이 생긴 겁니다.
삭제하면 즉시 사라진다
트리거 룰을 삭제했는데 목록에서 안 사라지고 페이지를 나갔다 들어와야 했던 버그가 있었습니다.
원인은 브라우저 캐시였습니다.
삭제 API가 완료되고 나서 바로 목록을 다시 불러오면, 브라우저가 캐시된 이전 응답을 반환하는 현상이었습니다.
모든 API 호출에 cache: 'no-store'를 전역 적용해서 해결했습니다.
이제 삭제하면 즉시 목록에서 사라집니다.
이 글에서 가장 중요한 내용 = 서버가 알람을 감시한다
이제 Phase 2에서 가장 크게 바뀐 부분을 이야기할 차례입니다.
기술적으로는 가장 복잡했고, 결과적으로는 가장 의미 있는 변화입니다.
"경비원"이 사무실 안에 있었던 문제
Phase 1.5의 알람 감시 구조를 비유로 설명하면 이렇습니다.
건물에 침입자를 감지하는 경비원이 있는데, 이 경비원이 건물 안에서 일하는 방식입니다.
경비원이 퇴근하면(브라우저 탭이 닫히면) 경보가 꺼집니다.
밤새 아무도 없는 동안에는 어떤 일이 일어나도 기록이 남지 않습니다.
Phase 2에서는 경비원이 서버로 이사했습니다. 서버는 브라우저와 무관하게 항상 켜져 있습니다.
브라우저를 닫아도, PC 앞을 떠나도, 밤새 아무도 화면을 보지 않아도, 서버의 경비원은 1초마다 PLC를 감시하고, 조건이 맞으면 DB에 이력을 기록합니다.
Before vs After (구조가 이렇게 바뀌었다)

Phase 1.5 (이전):
브라우저 (App.jsx)
↓ 1초마다 PLC에 직접 TCP 연결
↓ 알람 조건 평가
↓ 알람 이력 DB에 저장
↓ 브라우저 알림 + 헤더 뱃지 표시
← 탭을 닫으면 전부 중단
Phase 2 (이후):
서버 (항상 실행 중)
↓ AlarmPoller가 1초마다 PLC에 TCP 연결
↓ 알람 조건 평가
↓ 알람 이력 DB에 저장 (active 상태 포함)
브라우저 (탭이 열려 있을 때만)
↓ 1초마다 서버 API만 조회 (PLC 직접 연결 X)
↓ 새 이력 감지 → 팝업 + 브라우저 알림
↓ active=1 항목 → 헤더 뱃지 표시
브라우저는 이제 PLC에 직접 연결하지 않습니다.
서버가 이미 읽어놓은 이력을 조회하는 역할만 합니다.
PLC를 바라보는 주체가 브라우저에서 서버로 옮겨간 겁니다.
알람 타입에 따라 다르게 처리한다

이번 구조 설계에서 한 가지 중요한 결정이 있었습니다.
알람 타입에 따라 "활성 상태(active)"를 다르게 처리하는 것입니다.
| 알람 조건 | 예시 | 활성 표시 | 자동 해제 |
|---|---|---|---|
| ON / OFF / 크다 / 작다 | 온도가 80도 초과 | ✓ (배너 계속 표시) | 조건 사라지면 자동 해제 |
| CHANGED (변화 감지) | 어떤 값이 바뀌었다 | ✗ (이력만 기록) | 해당 없음 |
왜 이렇게 나눴을까요?
"온도가 80도를 초과했다"는 알람은 온도가 80도 아래로 내려갈 때까지 지금도 문제가 있는 상태입니다.
배너에 계속 표시되어야 합니다.
반면 "어떤 값이 바뀌었다"는 그 순간에 일어난 이벤트입니다.
값이 바뀐 건 이미 과거고, 지금 문제가 지속되고 있는 건 아닙니다.
이걸 배너에 쌓으면 "알람 23건 활성"이 끊임없이 늘어납니다.
의미 없는 숫자가 됩니다.
배너 = 현재 지속 중인 이상 상태 (레벨 알람)
팝업 = 새로운 이벤트가 발생했을 때 알림 (레벨 + CHANGED 모두)
이 둘을 분리하는 것이 알람 시스템의 핵심 설계 원칙이었습니다.
개발 중 발견한 버그들
이론적으로 완성된 것처럼 보여도 실제 PLC를 연결해보면 예상치 못한 문제가 나옵니다.
fetchoneimport 누락으로 서버 로그에 매초 경고가 발생하고 DB 정합성 체크가 동작하지 않는 문제가 있었습니다.datetime.utcnow()로 저장하면 한국(UTC+9) 기준으로 이력 시각이 9시간 빠르게 표시됐습니다.datetime.now()로 변경해서 해결했습니다.
배포 후에도 실제 환경에서 돌려봐야 발견되는 종류의 버그들입니다.
테스트 환경과 현장의 차이가 여기서 나옵니다.
숫자로는 모르던 걸 차트가 보여준다 = 트렌드 차트

DataList에서 값을 폴링해서 숫자로만 보면 "지금 값이 얼마다"는 알 수 있습니다.
하지만 "지난 2분 동안 값이 어떤 방향으로 움직이고 있었나"는 알 수 없습니다.
온도가 천천히 올라가고 있는지, 갑자기 튀었다가 내려온 건지, 주기적으로 진동하고 있는지.
이런 패턴을 파악하려면 시간 축 위의 차트가 필요합니다.
Phase 2에서 Recharts 라이브러리를 활용한 트렌드 차트를 추가했습니다.
주요 기능:
- Word(수치값)와 Bit(0/1 신호) 모두 표시 가능
- 이중 Y축: 왼쪽(수치값), 오른쪽(0~1 고정)
- Bit 신호는 계단형 라인으로 표시 : ON/OFF 전환 타이밍이 명확하게 보임
- 등록된 주소별 색상 버튼으로 개별 ON/OFF
- 최근 120개 데이터 포인트 표시
- 기록 시작 즉시 "▲ 차트" 버튼 활성화
별도 DB 저장 없이 기존 실시간 버퍼를 재활용해서, 새 API 엔드포인트 없이 구현했습니다.
알람이 났을 때 모를 수 없게 (알람 팝업 모달)

Phase 1.5에서는 알람이 발생하면 헤더의 빨간 뱃지만 올라갔습니다.
다른 화면을 보고 있거나 모니터 앞에 없으면 알람을 놓칠 수 있었습니다.
Phase 2에서는 알람이 발화하면 화면 중앙에 모달 팝업이 뜹니다.
팝업에 표시되는 것:
- 알람 라벨
- 조건 (어떤 조건이 맞았는지)
- 당시 값
- 발생 시각
팝업에서 할 수 있는 것:
- "✓ 조치 완료" = DB에 active=0으로 기록, 배너에서 제거
- "확인" = 팝업만 닫기, DB는 유지 (배너에 계속 표시)
- 배경 클릭으로 닫기
브라우저를 새로 열 때 과거 알람이 쏟아지지 않게
서버는 브라우저가 없어도 알람을 감지합니다.
그러면 브라우저를 새로 열었을 때, 그 사이에 쌓인 이력을 모두 "신규"로 처리하면 팝업이 한꺼번에 쏟아질 겁니다.
이걸 막기 위해 initializedRef를 사용합니다.
브라우저가 처음 열릴 때 현재 최대 id를 기준선으로 설정합니다.
이 기준선 이전 이력은 팝업 없이 통과하고, 이후에 새로 추가되는 이력부터만 팝업이 발동됩니다.
아침에 출근해서 브라우저를 열면 "밤새 이런 알람이 있었습니다"를 이력 페이지에서 확인하고, 이후에 새로 발생하는 알람은 실시간 팝업으로 받을 수 있습니다.
Phase 1.5 → Phase 2 변화 요약
| 항목 | Phase 1.5 | Phase 2 |
|---|---|---|
| 알람 감지 주체 | 브라우저 | 서버 |
| 탭 닫힌 상태에서 알람 | 감지 안 됨 | 서버가 계속 감지 |
| 알람 팝업 | 없음 | 있음 (화면 중앙 모달) |
| 트렌드 차트 | 없음 | 있음 (이중 Y축) |
| 저장 피드백 | 없음 | Toast 알림 |
| 에러 메시지 | "저장 실패" | "저장 실패: site 1 없음 (HTTP 404)" |
| 삭제 후 목록 갱신 | 페이지 이동 필요 | 즉시 반영 |
| 날짜 필터 | 없음 | 오늘/어제/7일/전체 |
| 확인 모드 쓰기 이력 | 접근 불가 | 읽기 전용 탭 접근 |
| PLC 재연결 | 수동 버튼 | 최대 5초 자동 복구 |
| 폴링 상태 유지 | 새로고침 시 초기화 | localStorage로 유지 |
Keyence KV-8000으로 직접 테스트한 결과
실제 Keyence KV-8000(IP 192.168.0.10:5000)으로 테스트한 항목들입니다.
| 테스트 항목 | 결과 |
|---|---|
| PLC 케이블 탈착 후 재연결 → 자동 복구 | 5초 이내 복구 확인 |
| Y3921 ON 조건 알람 발화 및 OFF 시 자동 해제 | 정상 |
| Y3920 CHANGED 조건 팝업 + 이력 기록 (배너 X) | 정상 |
| "확인" 후 동일 조건 재발생 → 팝업 재표시 | 정상 |
| "✓ 모두 조치 완료" → 배너 소멸 | 정상 |
| 날짜 필터 전환 | 정상 |
| 트렌드 차트 실시간 갱신 | 정상 |
아직 확인이 필요한 항목도 있습니다.
브라우저 탭을 완전히 닫고 밤새 서버만 실행 후 알람이 계속 쌓이는지는 별도 장기 테스트가 필요합니다.
다중 클라이언트 동시 접속, 모바일 브라우저에서의 차트 렌더링도 추가로 확인할 예정입니다.
Phase 3에서 할 것들

가까운 계획
알람 매뉴얼 이미지 연동:
알람이 발화했을 때 팝업에 관련 매뉴얼 이미지가 슬라이드로 표시되는 기능입니다.
PPT를 PNG로 일괄 변환해서 등록해두면, 현장에서 알람 원인 파악과 대응 방법을 팝업 안에서 바로 확인할 수 있습니다.
IO Monitor에서 직접 OUT 신호 쓰기:
현재는 DataList 페이지에서만 값을 쓸 수 있습니다.
IO Monitor 화면에서 직접 OUT 신호를 ON/OFF할 수 있게 추가할 예정입니다.
중기 계획
타이밍 차트:
"A 신호가 ON 됐을 때, 0.3초 후에 B가 ON 됐는가"를 시각적으로 확인할 수 있는 다채널 Gantt형 차트입니다.
여러 신호의 타이밍 관계를 분석하는 데 씁니다.
알람 통계:
시간대별 발생 빈도, 가장 자주 발생하는 알람 TOP N, 주간/월간 요약 리포트.
장기 계획
Windows 서비스 등록:
설비 PC가 재부팅될 때 PLCLink 서버가 자동으로 시작되도록 합니다.
이렇게 되면 서버를 직접 실행할 필요 없이, PC를 켜면 바로 감시 상태가 됩니다.
Modbus TCP 지원:
인버터, 온도조절기 등 SLMP 외의 장비도 연결할 수 있게 됩니다.
마치며
PLCLink를 만들면서 계속 생각하는 기준이 하나 있습니다.
"현장 엔지니어가 이 화면을 보고 설명 없이 쓸 수 있는가."
설명서가 없어도, 옆에 개발자가 없어도, 처음 보는 사람도 화면만 보고 이해할 수 있어야 한다는 것입니다.
Phase 2가 끝난 지금, 그 기준에 조금 더 가까워졌다고 생각합니다.
Phase 1.5에서 기능이 완성됐다면, Phase 2에서는 그 기능이 현장에서 신뢰받을 수 있게 됐습니다.
'(개인Project)_개발 > PLC-PC 연결' 카테고리의 다른 글
| [Program][보족] 알람이 났는데 왜 배너에는 안 뜨나요? (레벨 알람과 이벤트 알람의 차이) (0) | 2026.05.21 |
|---|---|
| [Program][보족] 브라우저를 닫았더니 알람이 사라졌다 (0) | 2026.05.20 |
| [Program][보족] bat 파일 더블클릭했는데 창이 바로 닫힌다 (줄바꿈 문자 문제일 수 있습니다) (0) | 2026.05.16 |
| [Program][보족] 설정을 마쳤는데 화면은 그대로였다 (커스텀 훅을 두 곳에서 쓰면 생기는 일) (1) | 2026.05.15 |
| [Program][보족] Keyence KV 자동 IO 구성 읽기 시도 (5가지 방법, 1가지 결론) (0) | 2026.05.14 |