한글화
This commit is contained in:
82
App.tsx
82
App.tsx
@@ -8,7 +8,7 @@ import { QuickTestPanel } from './components/QuickTestPanel';
|
||||
import { ConnectionStatus, TagData, ReaderCommand, FrequencyBand, MemoryBank, LogEntry, SerialPort, ReaderInfo, QuickTestConfig } from './types';
|
||||
import { RfidProtocol } from './services/rfidService';
|
||||
import { calculateCRC16, bytesToHexString, hexStringToBytes, hexToAscii, asciiToHex } from './utils/crc16';
|
||||
import { Terminal, Settings, LayoutDashboard, Database, Zap, ChevronUp, ChevronDown, Wifi, WifiOff } from 'lucide-react';
|
||||
import { Terminal, Settings, LayoutDashboard, Database, Zap, ChevronUp, ChevronDown, Wifi, WifiOff, AlertCircle, AlertTriangle } from 'lucide-react';
|
||||
|
||||
const App: React.FC = () => {
|
||||
// Serial State
|
||||
@@ -18,7 +18,7 @@ const App: React.FC = () => {
|
||||
const [address, setAddress] = useState(0xFF); // Broadcast address default
|
||||
|
||||
// App State - Set Quick Test as Default
|
||||
const [activeTab, setActiveTab] = useState<'connect' | 'inventory' | 'settings' | 'memory' | 'quicktest'>('quicktest');
|
||||
const [activeTab, setActiveTab] = useState<'connect' | 'inventory' | 'settings' | 'memory' | 'quicktest'>('connect');
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
const [tags, setTags] = useState<TagData[]>([]);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
@@ -28,6 +28,21 @@ const App: React.FC = () => {
|
||||
// Log Visibility State
|
||||
const [isLogExpanded, setIsLogExpanded] = useState(false);
|
||||
|
||||
// Global Error Message State
|
||||
const [globalMessage, setGlobalMessage] = useState<{ text: string, type: 'error' | 'info' } | null>(null);
|
||||
const messageTimerRef = useRef<number | null>(null);
|
||||
|
||||
const showTemporaryMessage = (text: string, type: 'error' | 'info' = 'error') => {
|
||||
if (messageTimerRef.current) {
|
||||
window.clearTimeout(messageTimerRef.current);
|
||||
}
|
||||
setGlobalMessage({ text, type });
|
||||
messageTimerRef.current = window.setTimeout(() => {
|
||||
setGlobalMessage(null);
|
||||
messageTimerRef.current = null;
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// Quick Action Config (Persisted in localStorage)
|
||||
const [quickTestConfig, setQuickTestConfig] = useState<QuickTestConfig>(() => {
|
||||
const saved = localStorage.getItem('quickTestConfig');
|
||||
@@ -357,20 +372,26 @@ const App: React.FC = () => {
|
||||
if (status === 0x01 || status === 0x02 || status === 0x03 || status === 0x04) {
|
||||
handleInventoryResponse(data);
|
||||
} else if (status === 0xFB) {
|
||||
addLog('INFO', RfidProtocol.getStatusDescription(status));
|
||||
const msg = RfidProtocol.getStatusDescription(status);
|
||||
addLog('INFO', msg);
|
||||
showTemporaryMessage(msg, 'info'); // Show as Info
|
||||
} else {
|
||||
let errorDetail = RfidProtocol.getStatusDescription(status);
|
||||
addLog('WARN', `Inventory Failed: ${errorDetail}`, `Status: 0x${status.toString(16).toUpperCase()}`);
|
||||
showTemporaryMessage(`Inventory Failed: ${errorDetail}`, 'error');
|
||||
}
|
||||
} else {
|
||||
if (status === 0xFB) {
|
||||
addLog('INFO', RfidProtocol.getStatusDescription(status));
|
||||
const msg = RfidProtocol.getStatusDescription(status);
|
||||
addLog('INFO', msg);
|
||||
showTemporaryMessage(msg, 'info');
|
||||
} else {
|
||||
let errorDetail = RfidProtocol.getStatusDescription(status);
|
||||
if (status === 0xFC && data.length > 0) {
|
||||
errorDetail += ` - ${RfidProtocol.getTagErrorDescription(data[0])}`;
|
||||
}
|
||||
addLog('ERROR', `Command 0x${reCmd.toString(16).toUpperCase()} Failed`, errorDetail);
|
||||
showTemporaryMessage(`Command Failed: ${errorDetail}`, 'error');
|
||||
|
||||
if (reCmd === ReaderCommand.WRITE_DATA_G2 && performingQuickWriteRef.current) {
|
||||
performingQuickWriteRef.current = false;
|
||||
@@ -455,6 +476,10 @@ const App: React.FC = () => {
|
||||
// --- Operations ---
|
||||
|
||||
const toggleScanning = useCallback(() => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
if (isScanning) {
|
||||
if (scanIntervalRef.current) clearInterval(scanIntervalRef.current);
|
||||
scanIntervalRef.current = null;
|
||||
@@ -465,13 +490,11 @@ const App: React.FC = () => {
|
||||
runScan();
|
||||
scanIntervalRef.current = window.setInterval(runScan, 500);
|
||||
}
|
||||
}, [isScanning, address]);
|
||||
}, [isScanning, address, status]);
|
||||
|
||||
const handleQuickRead = () => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
if (!performingQuickWriteRef.current) {
|
||||
alert("Reader is not connected! Please connect via serial port first.");
|
||||
}
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -485,7 +508,7 @@ const App: React.FC = () => {
|
||||
|
||||
const handleQuickWrite = (inputValue: string) => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("Reader is not connected! Please connect via serial port first.");
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -515,12 +538,16 @@ const App: React.FC = () => {
|
||||
setPendingQuickAction(null);
|
||||
performingQuickWriteRef.current = false;
|
||||
verificationRetriesRef.current = 0;
|
||||
addLog('INFO', 'Quick Test Cancelled by user');
|
||||
addLog('INFO', '사용자에 의해 퀵 테스트 취소됨');
|
||||
};
|
||||
|
||||
const clearTags = () => setTags([]);
|
||||
|
||||
const handleGetInfo = async () => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
addLog('INFO', 'Requesting Reader Info...');
|
||||
await sendCommand(ReaderCommand.GET_INFO);
|
||||
};
|
||||
@@ -533,6 +560,10 @@ const App: React.FC = () => {
|
||||
maxFreq: number;
|
||||
band: FrequencyBand;
|
||||
}) => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
await sendCommand(ReaderCommand.SET_POWER, [settings.power]);
|
||||
if (settings.address !== address) {
|
||||
await sendCommand(ReaderCommand.SET_ADDRESS, [settings.address]);
|
||||
@@ -542,6 +573,10 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleFactoryReset = async () => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
addLog('INFO', 'Sending Factory Reset...');
|
||||
};
|
||||
|
||||
@@ -555,6 +590,10 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleFetchTids = async () => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("리더기가 연결되지 않았습니다! 먼저 시리얼 포트를 통해 연결해주세요.");
|
||||
return;
|
||||
}
|
||||
if (isScanning) {
|
||||
if (scanIntervalRef.current) clearInterval(scanIntervalRef.current);
|
||||
scanIntervalRef.current = null;
|
||||
@@ -580,6 +619,10 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleMemoryRead = async (bank: MemoryBank, ptr: number, len: number, pwd: string, targetEpc: string) => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("Reader is not connected! Please connect via serial port first.");
|
||||
return;
|
||||
}
|
||||
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
||||
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0, 0, 0, 0]);
|
||||
|
||||
@@ -592,6 +635,10 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleMemoryWrite = async (bank: MemoryBank, ptr: number, dataStr: string, pwd: string, targetEpc: string) => {
|
||||
if (status !== ConnectionStatus.CONNECTED) {
|
||||
alert("Reader is not connected! Please connect via serial port first.");
|
||||
return;
|
||||
}
|
||||
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
||||
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0, 0, 0, 0]);
|
||||
const dataBytes = hexStringToBytes(dataStr);
|
||||
@@ -637,7 +684,7 @@ const App: React.FC = () => {
|
||||
<Database className="text-blue-600" />
|
||||
UHF Reader
|
||||
</h1>
|
||||
<p className="text-xs text-slate-400 mt-1">Web Serial Controller</p>
|
||||
<p className="text-xs text-slate-400 mt-1">RFID Read & Write Tester</p>
|
||||
</div>
|
||||
|
||||
<div className="p-4 space-y-2 flex-1">
|
||||
@@ -732,7 +779,18 @@ const App: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
<main className="flex-1 p-6 overflow-auto relative">
|
||||
{/* Global Status/Error Banner */}
|
||||
{globalMessage && (
|
||||
<div className={`mb-4 px-4 py-3 rounded-lg shadow-sm border flex items-center gap-3 animate-in fade-in slide-in-from-top-2 duration-300 ${globalMessage.type === 'error'
|
||||
? 'bg-red-50 border-red-200 text-red-700'
|
||||
: 'bg-amber-50 border-amber-200 text-amber-800'
|
||||
}`}>
|
||||
{globalMessage.type === 'error' ? <AlertCircle className="w-5 h-5 shrink-0" /> : <AlertTriangle className="w-5 h-5 shrink-0" />}
|
||||
<span className="font-medium text-sm">{globalMessage.text}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'connect' && (
|
||||
<div className="space-y-4 max-w-2xl mx-auto">
|
||||
<h2 className="text-lg font-bold text-slate-800">Connection Manager</h2>
|
||||
|
||||
@@ -19,41 +19,41 @@ export class RfidProtocol {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to interpret status codes from the reader
|
||||
* Helper to interpret status codes from the reader (Translated to Korean)
|
||||
*/
|
||||
static getStatusDescription(status: number): string {
|
||||
switch (status) {
|
||||
case 0x00: return "Success";
|
||||
case 0x01: return "Return before Inventory finished";
|
||||
case 0x02: return "Inventory Timeout";
|
||||
case 0x03: return "More data follows";
|
||||
case 0x04: return "Reader memory full";
|
||||
case 0x05: return "Access Password Error";
|
||||
case 0x09: return "Kill Tag Error";
|
||||
case 0x0A: return "Kill Password cannot be zero";
|
||||
case 0x0B: return "Tag does not support command";
|
||||
case 0x0C: return "Access Password cannot be zero (Locked)";
|
||||
case 0x0D: return "Tag is protected, cannot set again";
|
||||
case 0x0E: return "Tag is unprotected, no need to reset";
|
||||
case 0x10: return "Locked bytes, write fail";
|
||||
case 0x11: return "Cannot lock tag";
|
||||
case 0x12: return "Already locked, cannot lock again";
|
||||
case 0x13: return "Save Parameter Failed";
|
||||
case 0x14: return "Cannot adjust power";
|
||||
case 0x15: return "Return before Inventory finished (6B)";
|
||||
case 0x16: return "Inventory Timeout (6B)";
|
||||
case 0x17: return "More data follows (6B)";
|
||||
case 0x18: return "Reader memory full (6B)";
|
||||
case 0x19: return "Not Support Command or Access Password Error";
|
||||
case 0xF9: return "Command Execution Error";
|
||||
case 0xFA: return "Poor Communication / Tag Inoperable";
|
||||
case 0xFB: return "No Tag Operable";
|
||||
case 0xFC: return "Tag Returned Error Code"; // Check data for specific code
|
||||
case 0xFD: return "Command Length Wrong";
|
||||
case 0xFE: return "Illegal Command / CRC Error";
|
||||
case 0xFF: return "Parameter Error";
|
||||
case 0x30: return "Communication Error";
|
||||
default: return `Unknown Status (0x${status.toString(16).toUpperCase().padStart(2, '0')})`;
|
||||
case 0x00: return "성공";
|
||||
case 0x01: return "인벤토리 완료 전 반환됨";
|
||||
case 0x02: return "인벤토리 시간 초과";
|
||||
case 0x03: return "추가 데이터 있음";
|
||||
case 0x04: return "리더 메모리 가득 참";
|
||||
case 0x05: return "액세스 비밀번호 오류";
|
||||
case 0x09: return "태그 킬(Kill) 오류";
|
||||
case 0x0A: return "킬(Kill) 비밀번호는 0일 수 없음";
|
||||
case 0x0B: return "태그가 명령을 지원하지 않음";
|
||||
case 0x0C: return "액세스 비밀번호는 0일 수 없음 (잠김)";
|
||||
case 0x0D: return "태그가 보호되어 있어 다시 설정 불가";
|
||||
case 0x0E: return "태그가 보호되지 않아 재설정 필요 없음";
|
||||
case 0x10: return "잠긴 바이트 존재, 쓰기 실패";
|
||||
case 0x11: return "태그를 잠글 수 없음";
|
||||
case 0x12: return "이미 잠겨 있어 다시 잠글 수 없음";
|
||||
case 0x13: return "파라미터 저장 실패";
|
||||
case 0x14: return "전력을 조절할 수 없음";
|
||||
case 0x15: return "인벤토리 완료 전 반환됨 (6B)";
|
||||
case 0x16: return "인벤토리 시간 초과 (6B)";
|
||||
case 0x17: return "추가 데이터 있음 (6B)";
|
||||
case 0x18: return "리더 메모리 가득 참 (6B)";
|
||||
case 0x19: return "명령 미지원 또는 액세스 비밀번호 오류";
|
||||
case 0xF9: return "명령 실행 오류";
|
||||
case 0xFA: return "통신 불량 / 태그 작동 불가";
|
||||
case 0xFB: return "태그 없음 (감지 불가)";
|
||||
case 0xFC: return "태그 오류 코드 반환"; // Check data for specific code
|
||||
case 0xFD: return "명령 길이 오류";
|
||||
case 0xFE: return "잘못된 명령 / CRC 오류";
|
||||
case 0xFF: return "파라미터 오류";
|
||||
case 0x30: return "통신 오류";
|
||||
default: return `알 수 없는 상태 (0x${status.toString(16).toUpperCase().padStart(2, '0')})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,12 @@ export class RfidProtocol {
|
||||
*/
|
||||
static getTagErrorDescription(errorCode: number): string {
|
||||
switch (errorCode) {
|
||||
case 0x00: return "Other error";
|
||||
case 0x03: return "Memory overrun or EPC length not supported";
|
||||
case 0x04: return "Memory locked";
|
||||
case 0x0B: return "Insufficient power";
|
||||
case 0x0F: return "Non-specific error";
|
||||
default: return `Unknown Tag Error (0x${errorCode.toString(16).toUpperCase().padStart(2, '0')})`;
|
||||
case 0x00: return "기타 오류";
|
||||
case 0x03: return "메모리 초과 또는 EPC 길이 미지원";
|
||||
case 0x04: return "메모리 잠김";
|
||||
case 0x0B: return "전력 부족";
|
||||
case 0x0F: return "불특정 오류";
|
||||
default: return `알 수 없는 태그 오류 (0x${errorCode.toString(16).toUpperCase().padStart(2, '0')})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user