import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:serial_port_win32/serial_port_win32.dart'; /// Seri port iletişim servisi - Arduino kart okuyucusu (v1.4.2) class SerialService { VoidCallback? onConnected; void Function(String)? onDataReceived; void Function(String)? onError; String? _connectedPort; bool _isConnected = false; Timer? _pollTimer; String _lastUid = ''; bool _waitingForUid = false; SerialPort? _serialPort; // '\n' olmadan veri yollayan firmware için birikinti buffer'ı. // Sabit uzunluklu listeye addAll yapılamayacagi için growable olmalı. final List _rx = []; final StreamController _dataStream = StreamController.broadcast(); List getAvailablePorts() { try { return SerialPort.getAvailablePorts(); } catch (e) { debugPrint('Port listeleme hatasi: $e'); return []; } } Future connectAuto() async { final ports = getAvailablePorts(); debugPrint('Bulunan portlar: $ports'); if (ports.isEmpty) { onError?.call('Seri port bulunamadi.'); return; } for (final port in ports) { debugPrint('Deniyor: $port'); try { final success = await _tryConnect(port); if (success) { onConnected?.call(); return; } } catch (e) { debugPrint('$port baglantisi hatasi: $e'); } } onError?.call('Hicbir porta baglanamadi.'); } bool _mounted = true; bool mounted() => _mounted && _isConnected; Future connect(String portName) async { return await _tryConnect(portName); } bool _reading = false; void _startReading() { if (_serialPort == null || !_serialPort!.isOpened) { debugPrint('OKUMA: Port acik degil'); return; } if (_reading) { debugPrint('OKUMA: Zaten calisiyor'); return; } _reading = true; debugPrint('OKUMA BASLATILIYOR (async loop)'); unawaited(_readLoop()); } /// Native thread callback yerine isolate üzerinde okuma dongusu. /// readBytesUntil '\n' bekledigi için '\n' olmadan veri yollayan firmware'de /// sonsuza kadar donar. Bu yüzden readBytes + kisa timeout ile kuyruktaki /// veriyi okuyup satirlara bölüyoruz. Future _readLoop() async { while (_reading && (_serialPort?.isOpened ?? false)) { try { // Kuyruktaki mevcut byte'i beklemeksizin oku, '\n' gelmesini bekleme. final chunk = await _serialPort!.readBytes( 64, timeout: const Duration(milliseconds: 30), dataPollingInterval: const Duration(milliseconds: 1), ); debugPrint('OKUMA SONUCU: ${chunk.length} byte'); if (chunk.isEmpty) continue; // '\r' temizle, biriktir. '\n' gelene kadar satirlara bol. _rx.addAll(String.fromCharCodes(chunk).replaceAll('\r', '').codeUnits); while (_rx.isNotEmpty) { final newlineIdx = _rx.indexOf(10); if (newlineIdx == -1) break; // '\n' yok, veri tamamlanmadi final lineBytes = _rx.sublist(0, newlineIdx); _rx.removeRange(0, newlineIdx + 1); final line = String.fromCharCodes(lineBytes).trim(); if (line.isEmpty) continue; processIncomingData(line); } } catch (e, stackTrace) { debugPrint('VERI ISLEME HATASI: $e\nStack: $stackTrace'); await Future.delayed(const Duration(milliseconds: 100)); if (!_reading || _serialPort?.isOpened != true) break; } } _reading = false; debugPrint('OKUMA DONGUSU Bitti'); } Future _tryConnect(String portName) async { try { debugPrint('=== ${portName}YE BAGLANMA DENEMESI BASLADI ==='); _serialPort = SerialPort(portName, BaudRate: 115200, openNow: false); debugPrint('SerialPort nesnesi olusturuldu'); debugPrint('BaudRate: 115200'); _serialPort!.open(); final isOpen = _serialPort?.isOpened ?? false; debugPrint('Port acik mi? $isOpen'); if (isOpen) { _startReading(); _connectedPort = portName; _isConnected = true; debugPrint('=== $portName BAGLANTISI BASARILI ==='); return true; } else { onError?.call('$portName aciklamadi'); debugPrint('=== $portName BAGLANTISI BASARISIZ (acik degil) ==='); return false; } } catch (e, stackTrace) { debugPrint('BAGLANMA HATASI ($portName): $e\nStack: $stackTrace'); onError?.call('Baglantı hatasi ($portName): $e'); return false; } } void processIncomingData(String data) { final str = data.trim(); debugPrint('=== VERI ISLENDI ==='); debugPrint('Veri: "$str"'); debugPrint('Uzunluk: ${str.length}'); debugPrint('ASCII kodlari: ${str.codeUnits.map((c) => c.toString()).join(",")}'); // Kart çıkarıldı / kaldırıldı sinyali (çoklu varyant) if (str.toLowerCase().contains('silindi') || str.toLowerCase().contains('cikti') || str.toLowerCase().contains('cikis') || str.toLowerCase().contains('removed') || str.toLowerCase().contains('cikar')) { final uid = _lastUid; _lastUid = ''; _waitingForUid = false; debugPrint('✓ KART_SILINDI alindi, UID: $uid'); onDataReceived?.call('KART_CIKTI:$uid'); return; } // "Kart koyuldu" / kart takıldı / okundu sinyallerini çoklu varyantla yakala final lower = str.toLowerCase(); if (lower.contains('kart') && (lower.contains('koyul') || lower.contains('tak') || lower.contains('okul') || lower.contains('read') || lower.contains('placed') || lower.contains('detected'))) { _waitingForUid = true; debugPrint('✓ KART_KOYULDU sinyal, UID bekleniyor...'); return; } // El sıkışma yoksa da UID doğrudan geldiyse yakala // (UID genelde 8 veya 10 hex karakterli, boşluk/okunamaz karakter yok) if (str.isNotEmpty && _isPlausibleUid(str)) { _lastUid = str; _waitingForUid = false; debugPrint('✓ UID alindi (dorudan): $_lastUid (uzunluk: ${str.length})'); onDataReceived?.call('KART_TAKILDI:$_lastUid'); return; } if (str == 'Kart Koyuldu') { _waitingForUid = true; debugPrint('✓ KART_KOYULDU alindi, UID bekleniyor...'); } else if (_waitingForUid && str.isNotEmpty) { _lastUid = str; _waitingForUid = false; debugPrint('✓ UID alindi: $_lastUid (uzunluk: ${str.length})'); onDataReceived?.call('KART_TAKILDI:$_lastUid'); } else if (str.isEmpty) { debugPrint('Bos veri geldi'); } else { debugPrint('Seri veri: "$str"'); if (str.toLowerCase().contains('kart')) { debugPrint('*** KART ICEREN VERI ***'); } } } /// UID'ye benzer mi? 6-16 karakter, altınlı/hex benzeri, okunabilir. bool _isPlausibleUid(String s) { if (s.length < 6 || s.length > 16) return false; final clean = s.replaceAll(RegExp(r'[\s\-:]'), ''); if (clean.isEmpty) return false; return clean.codeUnits.where((c) => c == 32 || (c >= 48 && c <= 57) // 0-9 || (c >= 65 && c <= 70) // A-F || (c >= 97 && c <= 102)) // a-f .length >= clean.length * 0.8; } void send(String data) { if (!isConnected || _serialPort == null) return; try { _serialPort!.writeBytesFromString(data, includeZeroTerminator: false); debugPrint('Gonderildi: $data'); } catch (e) { onError?.call('Gönderme hatası: $e'); } } void disconnect() { _isConnected = false; try { _serialPort?.close(); _dataStream.close(); } catch (e) {} _connectedPort = null; debugPrint('Seri port baglantisi kesildi'); } bool get isConnected => _isConnected; String? get connectedPort => _connectedPort; Stream get onData => _dataStream.stream; }