..
This commit is contained in:
88
App.tsx
88
App.tsx
@@ -8,7 +8,7 @@ import { QuickTestPanel } from './components/QuickTestPanel';
|
|||||||
import { ConnectionStatus, TagData, ReaderCommand, FrequencyBand, MemoryBank, LogEntry, SerialPort, ReaderInfo, QuickTestConfig } from './types';
|
import { ConnectionStatus, TagData, ReaderCommand, FrequencyBand, MemoryBank, LogEntry, SerialPort, ReaderInfo, QuickTestConfig } from './types';
|
||||||
import { RfidProtocol } from './services/rfidService';
|
import { RfidProtocol } from './services/rfidService';
|
||||||
import { calculateCRC16, bytesToHexString, hexStringToBytes, hexToAscii, asciiToHex } from './utils/crc16';
|
import { calculateCRC16, bytesToHexString, hexStringToBytes, hexToAscii, asciiToHex } from './utils/crc16';
|
||||||
import { Terminal, Settings, LayoutDashboard, Database, Zap, ChevronUp, ChevronDown } from 'lucide-react';
|
import { Terminal, Settings, LayoutDashboard, Database, Zap, ChevronUp, ChevronDown, Wifi, WifiOff } from 'lucide-react';
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
// Serial State
|
// Serial State
|
||||||
@@ -54,7 +54,7 @@ const App: React.FC = () => {
|
|||||||
const currentTidReadEpc = useRef<string | null>(null);
|
const currentTidReadEpc = useRef<string | null>(null);
|
||||||
|
|
||||||
// Stale Closure Fix: Ref to hold the latest processFrame function
|
// Stale Closure Fix: Ref to hold the latest processFrame function
|
||||||
const processFrameRef = useRef<(frame: Uint8Array) => void>(() => {});
|
const processFrameRef = useRef<(frame: Uint8Array) => void>(() => { });
|
||||||
|
|
||||||
const addLog = (type: LogEntry['type'], message: string, data?: string) => {
|
const addLog = (type: LogEntry['type'], message: string, data?: string) => {
|
||||||
setLogs(prev => [{
|
setLogs(prev => [{
|
||||||
@@ -163,7 +163,7 @@ const App: React.FC = () => {
|
|||||||
isConnectedRef.current = false;
|
isConnectedRef.current = false;
|
||||||
try {
|
try {
|
||||||
if (readerRef.current) {
|
if (readerRef.current) {
|
||||||
await readerRef.current.cancel().catch(() => {});
|
await readerRef.current.cancel().catch(() => { });
|
||||||
readerRef.current = null;
|
readerRef.current = null;
|
||||||
}
|
}
|
||||||
if (writerRef.current) {
|
if (writerRef.current) {
|
||||||
@@ -559,7 +559,7 @@ const App: React.FC = () => {
|
|||||||
if (epcBytes.length === 0) continue;
|
if (epcBytes.length === 0) continue;
|
||||||
|
|
||||||
const payload = [
|
const payload = [
|
||||||
epcWords, ...epcBytes, MemoryBank.TID, 0, 6, 0,0,0,0, 0, 0
|
epcWords, ...epcBytes, MemoryBank.TID, 0, 6, 0, 0, 0, 0, 0, 0
|
||||||
];
|
];
|
||||||
addLog('INFO', `Reading TID for ${tag.epc}...`);
|
addLog('INFO', `Reading TID for ${tag.epc}...`);
|
||||||
await sendCommand(ReaderCommand.READ_DATA_G2, payload);
|
await sendCommand(ReaderCommand.READ_DATA_G2, payload);
|
||||||
@@ -571,7 +571,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const handleMemoryRead = async (bank: MemoryBank, ptr: number, len: number, pwd: string, targetEpc: string) => {
|
const handleMemoryRead = async (bank: MemoryBank, ptr: number, len: number, pwd: string, targetEpc: string) => {
|
||||||
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
||||||
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0,0,0,0]);
|
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0, 0, 0, 0]);
|
||||||
|
|
||||||
const payload = [
|
const payload = [
|
||||||
epcWords, ...epcBytes, bank, ptr & 0xFF, len & 0xFF, ...Array.from(pwdBytes), 0, 0
|
epcWords, ...epcBytes, bank, ptr & 0xFF, len & 0xFF, ...Array.from(pwdBytes), 0, 0
|
||||||
@@ -583,7 +583,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const handleMemoryWrite = async (bank: MemoryBank, ptr: number, dataStr: string, pwd: string, targetEpc: string) => {
|
const handleMemoryWrite = async (bank: MemoryBank, ptr: number, dataStr: string, pwd: string, targetEpc: string) => {
|
||||||
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
const { bytes: epcBytes, words: epcWords } = getEpcData(targetEpc);
|
||||||
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0,0,0,0]);
|
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0, 0, 0, 0]);
|
||||||
const dataBytes = hexStringToBytes(dataStr);
|
const dataBytes = hexStringToBytes(dataStr);
|
||||||
|
|
||||||
if (!dataBytes) {
|
if (!dataBytes) {
|
||||||
@@ -606,7 +606,7 @@ const App: React.FC = () => {
|
|||||||
addLog('ERROR', 'Invalid EPC Data');
|
addLog('ERROR', 'Invalid EPC Data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0,0,0,0]);
|
let pwdBytes = hexStringToBytes(pwd) || new Uint8Array([0, 0, 0, 0]);
|
||||||
const payload = [epcWords, ...Array.from(pwdBytes), ...epcBytes];
|
const payload = [epcWords, ...Array.from(pwdBytes), ...epcBytes];
|
||||||
|
|
||||||
addLog('INFO', `Write EPC (0x04): ${newEpc}`);
|
addLog('INFO', `Write EPC (0x04): ${newEpc}`);
|
||||||
@@ -622,7 +622,7 @@ const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-slate-100">
|
<div className="flex h-screen bg-slate-100">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="w-64 bg-white border-r border-slate-200 flex flex-col z-10">
|
<div className="hidden md:flex w-64 bg-white border-r border-slate-200 flex-col z-10">
|
||||||
<div className="p-6 border-b border-slate-100">
|
<div className="p-6 border-b border-slate-100">
|
||||||
<h1 className="text-xl font-bold text-slate-800 flex items-center gap-2">
|
<h1 className="text-xl font-bold text-slate-800 flex items-center gap-2">
|
||||||
<Database className="text-blue-600" />
|
<Database className="text-blue-600" />
|
||||||
@@ -634,32 +634,28 @@ const App: React.FC = () => {
|
|||||||
<div className="p-4 space-y-2 flex-1">
|
<div className="p-4 space-y-2 flex-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('quicktest')}
|
onClick={() => setActiveTab('quicktest')}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${activeTab === 'quicktest' ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
activeTab === 'quicktest' ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Zap className="w-5 h-5" /> Quick Test
|
<Zap className="w-5 h-5" /> Quick Test
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('inventory')}
|
onClick={() => setActiveTab('inventory')}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${activeTab === 'inventory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
activeTab === 'inventory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<LayoutDashboard className="w-5 h-5" /> Inventory
|
<LayoutDashboard className="w-5 h-5" /> Inventory
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('memory')}
|
onClick={() => setActiveTab('memory')}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${activeTab === 'memory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
activeTab === 'memory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Database className="w-5 h-5" /> Read / Write
|
<Database className="w-5 h-5" /> Read / Write
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('settings')}
|
onClick={() => setActiveTab('settings')}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${activeTab === 'settings' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
activeTab === 'settings' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Settings className="w-5 h-5" /> Settings
|
<Settings className="w-5 h-5" /> Settings
|
||||||
@@ -681,6 +677,61 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 flex flex-col overflow-hidden relative">
|
<div className="flex-1 flex flex-col overflow-hidden relative">
|
||||||
|
{/* Mobile Top Navigation Bar */}
|
||||||
|
<div className="md:hidden bg-white border-b border-slate-200 p-2 flex items-center justify-between shrink-0 overflow-x-auto">
|
||||||
|
<div className="flex items-center gap-3 pr-4 border-r border-slate-100 mr-2 shrink-0">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Database className="text-blue-600 w-5 h-5" />
|
||||||
|
<span className="font-bold text-slate-800 text-sm">UHF</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Connection Status Indicator */}
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('settings')}
|
||||||
|
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${status === ConnectionStatus.CONNECTED
|
||||||
|
? 'bg-emerald-50 text-emerald-600 border border-emerald-100'
|
||||||
|
: 'bg-red-50 text-red-600 border border-red-100 animate-pulse'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{status === ConnectionStatus.CONNECTED ? <Wifi className="w-3 h-3" /> : <WifiOff className="w-3 h-3" />}
|
||||||
|
<span>{status === ConnectionStatus.CONNECTED ? 'On' : 'Connect'}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 flex-1 justify-around">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('quicktest')}
|
||||||
|
className={`p-2 rounded-lg transition-colors flex flex-col items-center justify-center gap-1 ${activeTab === 'quicktest' ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Zap className="w-5 h-5" />
|
||||||
|
<span className="text-[10px] font-medium leading-none">Quick</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('inventory')}
|
||||||
|
className={`p-2 rounded-lg transition-colors flex flex-col items-center justify-center gap-1 ${activeTab === 'inventory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<LayoutDashboard className="w-5 h-5" />
|
||||||
|
<span className="text-[10px] font-medium leading-none">Inv.</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('memory')}
|
||||||
|
className={`p-2 rounded-lg transition-colors flex flex-col items-center justify-center gap-1 ${activeTab === 'memory' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Database className="w-5 h-5" />
|
||||||
|
<span className="text-[10px] font-medium leading-none">R/W</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('settings')}
|
||||||
|
className={`p-2 rounded-lg transition-colors flex flex-col items-center justify-center gap-1 ${activeTab === 'settings' ? 'bg-blue-50 text-blue-700' : 'text-slate-600 hover:bg-slate-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Settings className="w-5 h-5" />
|
||||||
|
<span className="text-[10px] font-medium leading-none">Set</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<main className="flex-1 p-6 overflow-auto">
|
<main className="flex-1 p-6 overflow-auto">
|
||||||
{activeTab === 'inventory' && (
|
{activeTab === 'inventory' && (
|
||||||
<InventoryPanel
|
<InventoryPanel
|
||||||
@@ -738,7 +789,7 @@ const App: React.FC = () => {
|
|||||||
<Terminal className="w-3 h-3" /> System Logs
|
<Terminal className="w-3 h-3" /> System Logs
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{isLogExpanded ? <ChevronDown className="w-4 h-4 text-slate-400"/> : <ChevronUp className="w-4 h-4 text-slate-400"/>}
|
{isLogExpanded ? <ChevronDown className="w-4 h-4 text-slate-400" /> : <ChevronUp className="w-4 h-4 text-slate-400" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -747,8 +798,7 @@ const App: React.FC = () => {
|
|||||||
{logs.map(log => (
|
{logs.map(log => (
|
||||||
<div key={log.id} className="flex gap-2">
|
<div key={log.id} className="flex gap-2">
|
||||||
<span className="text-slate-400">[{log.timestamp.toLocaleTimeString()}]</span>
|
<span className="text-slate-400">[{log.timestamp.toLocaleTimeString()}]</span>
|
||||||
<span className={`font-bold ${
|
<span className={`font-bold ${log.type === 'TX' ? 'text-blue-600' :
|
||||||
log.type === 'TX' ? 'text-blue-600' :
|
|
||||||
log.type.includes('RX') ? 'text-emerald-600' :
|
log.type.includes('RX') ? 'text-emerald-600' :
|
||||||
log.type === 'ERROR' ? 'text-red-600' :
|
log.type === 'ERROR' ? 'text-red-600' :
|
||||||
log.type === 'WARN' ? 'text-amber-600' : 'text-slate-700'
|
log.type === 'WARN' ? 'text-amber-600' : 'text-slate-700'
|
||||||
|
|||||||
1816
package-lock.json
generated
Normal file
1816
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user