test
import { useState, useCallback, useRef } from 'react'; import { ArrowLeft, Usb, Database, Copy, RefreshCw, Calendar, CheckCircle2, AlertCircle, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { useToast } from '@/hooks/use-toast'; import * as XLSX from 'xlsx'; interface VaisalaReadProps { onBack: () => void; } interface DatabaseRecord { serienr: string; ref85: string; datum: string; } // Check if Web Serial API is available const isWebSerialSupported = 'serial' in navigator; export function VaisalaRead({ onBack }: VaisalaReadProps) { const { toast } = useToast(); const portRef = useRef(null); const readerRef = useRef | null>(null); // State const [isConnected, setIsConnected] = useState(false); const [isConnecting, setIsConnecting] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [serialNumber, setSerialNumber] = useState(''); const [calibrationText, setCalibrationText] = useState(''); const [calibrationDate, setCalibrationDate] = useState(() => { const today = new Date(); return today.toISOString().split('T')[0]; }); const [ref85, setRef85] = useState(''); const [ref85Date, setRef85Date] = useState(''); const [databaseName, setDatabaseName] = useState(null); const [databaseRecords, setDatabaseRecords] = useState([]); const [error, setError] = useState(null); // Connect to serial port const connectToSensor = useCallback(async () => { if (!isWebSerialSupported) { setError('Web Serial API stöds inte i denna webbläsare. Använd Chrome eller Edge.'); return; } // Make sure any existing connection is closed first if (portRef.current) { try { await portRef.current.close(); } catch { // Ignore close errors } portRef.current = null; } setIsConnecting(true); setError(null); try { // Request port from user const port = await navigator.serial.requestPort(); // Open with Vaisala settings: 19200 baud, 8 data bits, no parity, 1 stop bit await port.open({ baudRate: 19200, dataBits: 8, parity: 'none', stopBits: 1, }); portRef.current = port; setIsConnected(true); toast({ title: 'Ansluten', description: 'Ansluten till givare', }); } catch (err) { if (err instanceof Error && err.name === 'NotFoundError') { // User cancelled port selection setError('Ingen port vald'); } else if (err instanceof Error && err.message.includes('Failed to open')) { setError('Kunde inte öppna porten. Stäng eventuella andra program som använder porten (t.ex. Python, Arduino IDE) och försök igen.'); } else { setError(`Kunde inte ansluta: ${err instanceof Error ? err.message : 'Okänt fel'}`); } } finally { setIsConnecting(false); } }, [toast]); // Disconnect from serial port const disconnect = useCallback(async () => { try { if (readerRef.current) { await readerRef.current.cancel(); readerRef.current = null; } if (portRef.current) { await portRef.current.close(); portRef.current = null; } setIsConnected(false); setSerialNumber(''); setCalibrationText(''); setRef85(''); setRef85Date(''); } catch (err) { console.error('Disconnect error:', err); } }, []); // Send command and read response const sendCommand = useCallback(async (command: string): Promise => { if (!portRef.current || !portRef.current.writable || !portRef.current.readable) { throw new Error('Port not available'); } const encoder = new TextEncoder(); const decoder = new TextDecoder(); // Write command const writer = portRef.current.writable.getWriter(); await writer.write(encoder.encode(command + '\r')); writer.releaseLock(); // Read response with timeout const reader = portRef.current.readable.getReader(); readerRef.current = reader; let response = ''; const timeout = setTimeout(() => { reader.cancel(); }, 2000); try { while (true) { const { value, done } = await reader.read(); if (done) break; response += decoder.decode(value); if (response.includes('\r') || response.includes('\n')) break; } } finally { clearTimeout(timeout); reader.releaseLock(); readerRef.current = null; } return response.trim(); }, []); // Update sensor (read serial, set calibration text and date) const updateSensor = useCallback(async () => { if (!portRef.current) { setError('Ingen givare ansluten'); return; } setIsUpdating(true); setError(null); try { // Read serial number const snumResponse = await sendCommand('SNUM '); // Parse serial number from response like "SNUM: ABC123" const snMatch = snumResponse.match(/[^(: )]+$/); const sn = snMatch ? snMatch[0].replace(/[\r\n]/g, '') : ''; if (!sn) { throw new Error('Kunde inte läsa serienummer'); } setSerialNumber(sn); // Set calibration text await sendCommand('CTEXT FuktCom AB / Lund'); setCalibrationText('FuktCom AB / Lund'); // Wait a bit await new Promise(resolve => setTimeout(resolve, 500)); // Set calibration date (format YYYYMMDD) if (calibrationDate) { const dateFormatted = calibrationDate.replace(/-/g, ''); await sendCommand(`CDATE ${dateFormatted}`); } // Copy serial number to clipboard await navigator.clipboard.writeText(sn); // Look up 85% REF from database const record = databaseRecords.find(r => r.serienr === sn); if (record) { setRef85(record.ref85); setRef85Date(record.datum); } else { setRef85('Ej hittad'); setRef85Date('Ej hittad'); } toast({ title: 'Givare uppdaterad', description: `Serienummer ${sn} kopierat till urklipp`, }); } catch (err) { setError(`Fel vid uppdatering: ${err instanceof Error ? err.message : 'Okänt fel'}`); } finally { setIsUpdating(false); } }, [calibrationDate, databaseRecords, sendCommand, toast]); // Copy serial number to clipboard const copySerialNumber = useCallback(async () => { if (serialNumber) { await navigator.clipboard.writeText(serialNumber); toast({ title: 'Kopierad', description: `Serienummer ${serialNumber} kopierat till urklipp`, }); } }, [serialNumber, toast]); // Load database Excel file const handleDatabaseUpload = useCallback((e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const data = new Uint8Array(event.target?.result as ArrayBuffer); const workbook = XLSX.read(data, { type: 'array' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); // Parse records const records: DatabaseRecord[] = []; for (const row of jsonData as Record[]) { const serienr = String(row['Serienr'] || '').trim(); if (!serienr) continue; const ref85Val = row['85% Ref.'] || row['85% REF'] || row['85 Ref'] || ''; let datumVal = row['Datum'] || ''; // Format date if it's a number (Excel serial date) if (typeof datumVal === 'number') { const date = XLSX.SSF.parse_date_code(datumVal); datumVal = `${date.y}-${String(date.m).padStart(2, '0')}-${String(date.d).padStart(2, '0')}`; } else if (datumVal instanceof Date) { datumVal = datumVal.toISOString().split('T')[0]; } records.push({ serienr, ref85: String(ref85Val), datum: String(datumVal), }); } setDatabaseRecords(records); setDatabaseName(file.name); toast({ title: 'Databas laddad', description: `${records.length} poster hittades`, }); } catch (err) { setError(`Fel vid läsning av databas: ${err instanceof Error ? err.message : 'Okänt fel'}`); } }; reader.readAsArrayBuffer(file); }, [toast]); return (
{/* Header with back button */}

Vaisala Read

Givare GUI

{isConnected ? (
Ansluten
) : null}
{/* Info about iframe limitations */} Viktigt: För att ansluta till givare måste du öppna appen i ett nytt fönster (inte i förhandsvisningen). Klicka på "Publish" och öppna den publicerade länken i Chrome eller Edge. {/* Web Serial API warning */} {!isWebSerialSupported && ( Web Serial API stöds inte i denna webbläsare. Använd Chrome eller Edge för att ansluta till givare. )} {/* Error alert */} {error && ( {error} )}
{/* Database selection */} Databas
{databaseName ? (

{databaseName} ({databaseRecords.length} poster)

) : (

Ingen databas vald

)}
{/* Connection */} Anslutning {isConnected ? ( ) : ( )} {/* Sensor info */} Givare
setCalibrationDate(e.target.value)} />
{/* Update button - styled like the Python green button */} {/* Copy button */}
{/* 85% REF info */} 85% REF från databas
); }