import React, { useEffect, useState } from 'react'; import { serialService } from '../services/serialService'; import { BMSConfig } from '../types'; import * as JBD from '../services/jbdProtocol'; import { Save, RefreshCw, AlertTriangle, CheckCircle, RotateCw } from 'lucide-react'; const Settings: React.FC = () => { // 초기 상태를 빈 객체로 시작하여 UI는 렌더링되되 값은 비어있도록 함 const [config, setConfig] = useState>({}); const [busyReg, setBusyReg] = useState(null); // 현재 작업 중인 레지스터 (로딩 표시용) const [globalLoading, setGlobalLoading] = useState(false); const [error, setError] = useState(null); const [successMsg, setSuccessMsg] = useState(null); // 전체 읽기 const readAllSettings = async () => { setGlobalLoading(true); setError(null); setSuccessMsg(null); try { await serialService.enterFactoryModeRead(); // Helper with delay const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const p = async (reg: number) => { const val = await serialService.readRegister(reg); await delay(250); return val; }; const pBytes = async (reg: number) => { const val = await serialService.readRegisterBytes(reg); await delay(250); return val; }; // 병렬 처리는 시리얼 통신에서 꼬일 수 있으므로 순차 처리 권장 (await 사용) // 주요 레지스터 읽기 const newConfig: Partial = {}; newConfig.cellOvp = await p(JBD.REG_COVP); newConfig.cellOvpRel = await p(JBD.REG_COVP_REL); newConfig.cellUvp = await p(JBD.REG_CUVP); newConfig.cellUvpRel = await p(JBD.REG_CUVP_REL); newConfig.packOvp = await p(JBD.REG_POVP); newConfig.packOvpRel = await p(JBD.REG_POVP_REL); newConfig.packUvp = await p(JBD.REG_PUVP); newConfig.packUvpRel = await p(JBD.REG_PUVP_REL); newConfig.chgOt = await p(JBD.REG_CHG_OT); newConfig.chgOtRel = await p(JBD.REG_CHG_OT_REL); newConfig.chgUt = await p(JBD.REG_CHG_UT); newConfig.chgUtRel = await p(JBD.REG_CHG_UT_REL); newConfig.dsgOt = await p(JBD.REG_DSG_OT); newConfig.dsgOtRel = await p(JBD.REG_DSG_OT_REL); newConfig.dsgUt = await p(JBD.REG_DSG_UT); newConfig.dsgUtRel = await p(JBD.REG_DSG_UT_REL); newConfig.covpHigh = await p(JBD.REG_COVP_HIGH); newConfig.cuvpHigh = await p(JBD.REG_CUVP_HIGH); newConfig.funcConfig = await p(JBD.REG_FUNC_CONFIG); newConfig.ntcConfig = await p(JBD.REG_NTC_CONFIG); newConfig.balStart = await p(JBD.REG_BAL_START); newConfig.balWindow = await p(JBD.REG_BAL_WINDOW); newConfig.designCapacity = await p(JBD.REG_DESIGN_CAP); newConfig.cycleCapacity = await p(JBD.REG_CYCLE_CAP); newConfig.dsgRate = await p(JBD.REG_DSG_RATE); newConfig.cap100 = await p(JBD.REG_CAP_100); newConfig.cap80 = await p(JBD.REG_CAP_80); newConfig.cap60 = await p(JBD.REG_CAP_60); newConfig.cap40 = await p(JBD.REG_CAP_40); newConfig.cap20 = await p(JBD.REG_CAP_20); newConfig.cap0 = await p(JBD.REG_CAP_0); newConfig.fetCtrl = await p(JBD.REG_FET_CTRL); newConfig.ledTimer = await p(JBD.REG_LED_TIMER); newConfig.shuntRes = await p(JBD.REG_SHUNT_RES); newConfig.cellCnt = await p(JBD.REG_CELL_CNT); newConfig.cycleCnt = await p(JBD.REG_CYCLE_CNT); newConfig.serialNum = await p(JBD.REG_SERIAL_NUM); newConfig.mfgDate = await p(JBD.REG_MFG_DATE); const mfgNameRaw = await pBytes(JBD.REG_MFG_NAME); const deviceNameRaw = await pBytes(JBD.REG_DEVICE_NAME); const barcodeRaw = await pBytes(JBD.REG_BARCODE); newConfig.mfgName = JBD.JBDProtocol.parseString(mfgNameRaw); newConfig.deviceName = JBD.JBDProtocol.parseString(deviceNameRaw); newConfig.barcode = JBD.JBDProtocol.parseString(barcodeRaw); await serialService.exitFactoryMode(); setConfig(prev => ({ ...prev, ...newConfig })); setSuccessMsg("모든 설정을 불러왔습니다."); } catch (e: any) { setError("설정 읽기 실패: " + e.message); } finally { setGlobalLoading(false); } }; // 개별 항목 읽기 const handleReadSingle = async (reg: number, field: keyof BMSConfig, type: 'int' | 'string' | 'bytes' | 'date' = 'int') => { setBusyReg(reg); setError(null); setSuccessMsg(null); try { await serialService.enterFactoryModeRead(); let val: any; if (type === 'int' || type === 'date') { // date reads as int raw val = await serialService.readRegister(reg); } else if (type === 'string' || type === 'bytes') { const bytes = await serialService.readRegisterBytes(reg); if (type === 'string') { val = JBD.JBDProtocol.parseString(bytes); } else { val = bytes; // bytes not fully supported in single row yet unless custom } } await serialService.exitFactoryMode(); setConfig(prev => ({ ...prev, [field]: val })); setSuccessMsg("항목을 불러왔습니다."); } catch (e: any) { setError("읽기 실패: " + e.message); } finally { setBusyReg(null); } }; // 개별 항목 저장 const handleSaveSingle = async (reg: number, value: number | string | Uint8Array, type: 'int' | 'string' | 'bytes' | 'date' = 'int') => { setBusyReg(reg); setError(null); setSuccessMsg(null); try { await serialService.enterFactoryModeWrite(); if (type === 'int' || type === 'date') { await serialService.writeRegister(reg, value as number); } else if (type === 'string') { const bytes = JBD.JBDProtocol.encodeString(value as string); await serialService.writeRegisterBytes(reg, bytes); } else if (type === 'bytes') { await serialService.writeRegisterBytes(reg, value as Uint8Array); } await serialService.exitFactoryMode(); setSuccessMsg("설정이 저장되었습니다."); setTimeout(() => setSuccessMsg(null), 2000); } catch (e: any) { setError("저장 실패: " + e.message); } finally { setBusyReg(null); } }; // 그룹 컴포넌트 const Group: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => (

{title}

{children}
); // 개별 설정 행 컴포넌트 const SettingRow: React.FC<{ label: string; val: number | string | undefined; unit?: string; reg: number; field: keyof BMSConfig; scale?: number; type?: 'int' | 'string' | 'bytes' | 'date'; }> = ({ label, val, unit, reg, field, scale = 1, type = 'int' }) => { const [localVal, setLocalVal] = useState(''); const [isDirty, setIsDirty] = useState(false); const isBusy = busyReg === reg; // config 값이 업데이트되면 로컬 상태 동기화 useEffect(() => { if (val === undefined || val === null) { setLocalVal(''); setIsDirty(false); } else { if (type === 'date') { setLocalVal(JBD.JBDProtocol.parseDate(val as number)); } else if (type === 'int') { setLocalVal(Number(val) * scale); } else { setLocalVal(val as string); } setIsDirty(false); } }, [val, scale, type]); const handleChange = (e: React.ChangeEvent) => { setLocalVal(e.target.value); setIsDirty(true); }; const onRead = () => { handleReadSingle(reg, field, type as 'int' | 'string' | 'bytes' | 'date'); }; const onSave = () => { let payload: any = localVal; if (type === 'int') { payload = Number(localVal) / scale; } else if (type === 'date') { payload = JBD.JBDProtocol.encodeDate(localVal as string); } handleSaveSingle(reg, payload, type as 'int' | 'string' | 'bytes' | 'date'); setIsDirty(false); }; return (
{label}
ADDR: 0x{reg.toString(16).toUpperCase().padStart(2, '0')}
{unit && {type !== 'date' ? unit : ''}}
); }; return (
{/* 상단 툴바 */}

EEPROM 설정

BMS 내부 파라미터를 개별적으로 읽거나 수정할 수 있습니다.

{/* 메시지 영역 */}
{error && (
{error}
)} {successMsg && (
{successMsg}
)}
{/* 설정 그리드 */}
); }; export default Settings;