Navigation

Samsung Sensor Monitor

센서 데이터를 차트로 보고 운영자가 직접 위젯을 배치하는 Electron 데스크톱 앱의 초기 PoC를 만들었습니다. 위젯을 추가하고 옮기는 화면과 저장 구조를 구현했고 다른 프로젝트로 이동한 뒤에는 후속 구현을 위한 구조 문서와 기술 자문을 맡았습니다.

핵심 기여

과제본인 기여코드에서 확인한 내용
운영자가 구성하는 대시보드위젯 추가와 삭제, 드래그와 반응형 배치 저장위젯 id로 item과 화면 너비별 layout을 연결
센서별 차트 구성ECharts 래퍼와 차트별 데이터 연결sensor key를 dataset 차원으로 사용
후속 구현의 인수인계프로세스와 모듈, 상호작용 도식 작성Renderer, Main Process와 하드웨어 경계 구분

개요

항목내용
기간2024.12 ~ 2025.06. 초기 PoC는 2024.12, 후속 구조 문서는 2025.06에 남겼습니다
역할초기 대시보드와 위젯 PoC 구현, 개발 환경 구성, 후속 구조 문서화와 기술 자문
기술Electron, React, TypeScript, Vite, Zustand, ECharts, react-grid-layout
협업 범위초기 구현 이후 후임의 제품화 작업을 구조 관점에서 지원했습니다

맡은 범위

위젯 레이아웃, 차트 래퍼와 메뉴, 드래그 헤더, localStorage 저장을 구현했습니다. Biome, Stylelint, Husky와 lint-staged를 구성했습니다. 제품화 단계의 센서 통신과 카메라 연동은 후속 구현자가 맡은 영역과 구분했습니다.

문제와 해결

위젯 데이터와 배치를 함께 유지하기

문제. 차트의 종류와 연결 센서, 화면상의 위치는 서로 다른 데이터입니다. 차트를 지웠는데 배치만 남거나 화면 너비가 바뀔 때 위젯의 식별자가 달라지면 대시보드 구성이 어긋납니다.

판단. 위젯의 id를 item과 각 breakpoint의 layout이 공유하도록 두고 추가와 삭제 때 두 데이터를 함께 갱신했습니다. 드래그로 바뀐 배치는 별도로 저장했습니다.

flowchart LR
    Action["위젯 추가와 삭제"] --> Id["공통 위젯 id"]
    Id --> Items["차트 종류와 sensor key"]
    Id --> Layouts["breakpoint별 위치와 크기"]
    Items --> Save["localStorage 저장"]
    Layouts --> Save
    Save --> Restore["대시보드 초기 상태"]

구현. 삭제할 위젯의 id로 item과 모든 breakpoint의 layout을 걸렀습니다. 새 위젯의 위치는 마지막 배치 항목의 오른쪽을 기준으로 계산하고 열 범위를 넘으면 다음 행으로 옮겼습니다.

구현 코드: 위젯과 레이아웃을 함께 삭제

초기 PoC의 본인 변경 코드에서 저장 흐름만 발췌했습니다.

setItems((prevItems) => {
  const newItems = _.filter(prevItems, chart => chart.id !== chartId);
  const currentLayouts = loadLayoutsFromLS();
  const newLayouts = _.mapValues(currentLayouts, layout =>
    _.filter(layout, item => item.i !== chartId)
  );

  saveItemsToLS(newItems);
  saveLayoutsToLS(newLayouts);
  return newItems;
});

결과. 차트의 식별 정보와 화면별 배치가 같은 id로 연결됐습니다. 다음 실행에서 저장된 구성으로 시작할 수 있는 PoC를 만들었습니다.

차트의 표현과 센서 샘플 갱신을 나누기

문제. 같은 센서 데이터를 선, 막대와 다른 차트로 표현할 수 있어야 했고 위젯을 옮기는 동작과 샘플 갱신도 함께 다뤄야 했습니다.

판단. 차트가 요구하는 데이터 형태는 store 성격의 컴포넌트에서 만들고 ECharts 래퍼는 표현 옵션을 받도록 나눴습니다. 막대 차트는 sensor key를 dataset의 차원으로 사용했습니다.

구현. 선택된 센서의 값을 시간과 함께 행으로 만들고 최근 샘플만 유지했습니다. 드래그 중인 차트에서는 샘플 갱신 effect를 시작하지 않도록 처리했습니다.

구현 코드: 선택 센서를 dataset으로 연결
const [dataset, setDataset] = useState(() => ({
  dimensions: ["time", ...sensorKeys],
  source: [],
}));

useEffect(() => {
  if (isDragging) return;
  const intervalId = setInterval(updateSensorData, 1000);
  return () => clearInterval(intervalId);
}, [sensorKeys, isDragging]);

타입과 데이터 행을 만드는 부분은 생략했습니다. 당시 샘플은 최근 300행을 유지했습니다.

결과. 센서 샘플을 만드는 경로와 차트 옵션을 구분했습니다. 드래그 상태에 따른 갱신 제어는 차트 표시용 샘플에 적용했습니다.

후임이 이어받을 프로세스 경계를 남기기

문제. Electron UI는 Main Process와 센서, 저장소와 카메라에 연결됩니다. 초기 PoC 이후 담당 프로젝트가 바뀌어 후속 구현자가 구조를 이어받을 수 있어야 했습니다.

판단. 후속 단계에서는 직접 구현한 UI와 제품화할 하드웨어 연동의 책임을 구분해 문서로 남겼습니다.

flowchart LR
    UI["Renderer의 위젯 UI"] --> IPC["IPC 브릿지와 MessagePort"]
    IPC --> Main["Main Process"]
    Main --> Sensor["센서 통신과 저장소"]
    Main --> Camera["카메라 연동"]

구현. 소프트웨어 레이어, 모듈, 프로세스와 상호작용 도식을 작성하고 구조 관점의 기술 자문을 했습니다.

결과. 초기 UI 구현 이후의 제품화는 구조 문서와 기술 자문으로 지원했습니다. 직접 구현과 후속 지원의 범위를 문서로 남겼습니다.

결과와 확인 범위

초기 PoC의 위젯과 데이터 연결 코드, 이후 작성한 구조 문서를 근거로 정리했습니다. 실제 현장 적용 범위와 운영 지표는 확인된 값이 없어 추가하지 않았습니다. 이 경험에서 대시보드의 데이터 식별자와 배치 상태, Electron의 프로세스 경계를 따로 설명하는 기준을 얻었습니다.