본문 바로가기

delphi

firemonkey에서 블루투스 시리얼 송수신 코드. 클래식 블루투스

반응형
SMALL

Delphi와 FireMonkey를 사용하여 클래식 블루투스를 통해 데이터를 송수신하는 방법을 알려드릴게요. 이 작업은 디바이스에서 블루투스를 지원해야하며, 추가로 Delphi에서 TBluetoothManager와 TBluetoothDeviceList를 사용해야 합니다.

Delphi에서 블루투스 기능을 사용하기 위해 아래의 코드를 참고해 주세요:

uses
  System.Bluetooth;

var
  FManager: TBluetoothManager;
  FPairedDevices: TBluetoothDeviceList;
  FAdapter: TBluetoothAdapter;
  FDevice: TBluetoothDevice;
  FService: TBluetoothService;
  FSocket: TBluetoothServerSocket;

begin
  // 블루투스 매니저 인스턴스를 얻음
  FManager := TBluetoothManager.Current;
  
  // 블루투스 어댑터를 얻음
  FAdapter := FManager.CurrentAdapter;
  
  // 페어링된 디바이스들의 목록을 얻음
  FPairedDevices := FAdapter.PairedDevices;
  
  if FPairedDevices.Count > 0 then
  begin
    // 첫 번째 페어링된 디바이스를 선택
    FDevice := FPairedDevices[0];
    
    // 해당 디바이스의 서비스 목록을 얻음
    FService := FDevice.GetServices[0];
    
    // 서버 소켓을 열어 클라이언트로부터의 연결을 대기
    FSocket := FService.CreateServerSocket('MyServer', False);
  end;
end;

이 코드는 블루투스 매니저를 사용하여 현재의 블루투스 어댑터를 찾아내고, 어댑터를 통해 페어링된 디바이스의 목록을 찾아냅니다. 그 후, 첫 번째 페어링된 디바이스와 그 디바이스의 첫 번째 서비스를 사용하여 서버 소켓을 생성합니다. 이를 통해 클라이언트로부터의 연결 요청을 기다릴 수 있습니다.

데이터 송신 및 수신에 대한 자세한 내용은 디바이스의 서비스와 소켓에 따라 다르며, 해당 디바이스의 문서나 API 참조를 통해 알아봐야 할 것입니다.

위의 코드는 간단한 예제로, 실제 사용시에는 블루투스 디바이스와 서비스를 사용자가 선택할 수 있도록 UI를 추가하는 것이 좋습니다. 또한, 블루투스 연결과 데이터 송수신 과정에서 발생할 수 있는 예외 상황을 처리하는 코드를 추가하는것이 중요합니다.

데이터를 송수신하는 코드 예시는 다음과 같습니다.

var
  LData: TBytes;
begin
  // 송신
  LData := TEncoding.UTF8.GetBytes('Hello, Bluetooth!');
  FSocket.SendData(LData);
  
  // 수신
  LData := FSocket.ReceiveData();
  ShowMessage(TEncoding.UTF8.GetString(LData));
end;

위의 코드는 먼저 'Hello, Bluetooth!'라는 메시지를 UTF-8 인코딩으로 바이트 배열로 변환하고, 이를 SendData 메서드를 사용하여 송신합니다. 그 다음, ReceiveData 메서드를 사용하여 데이터를 수신하고, 이를 다시 문자열로 변환하여 메시지 박스에 표시합니다.

이 방법은 매우 기본적인 방법이며 실제 사용 시에는 데이터 송수신을 비동기적으로 처리해야 합니다. 즉, 데이터를 송수신하는 동안에도 사용자 인터페이스가 응답을 유지하게끔 해야 합니다. 이를 위해 별도의 스레드를 사용하거나, 비동기 패턴을 사용하는 것을 고려해 볼 수 있습니다.

그럼 송수신을 스레드로 만들어줘

type
  TBluetoothCommThread = class(TThread)
  private
    FSocket: TBluetoothServerSocket;
    FOnReceived: TProc<string>;
  protected
    procedure Execute; override;
  public
    constructor Create(ASocket: TBluetoothServerSocket; AOnReceived: TProc<string>);
  end;

{ TBluetoothCommThread }

constructor TBluetoothCommThread.Create(ASocket: TBluetoothServerSocket; AOnReceived: TProc<string>);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FSocket := ASocket;
  FOnReceived := AOnReceived;
end;

procedure TBluetoothCommThread.Execute;
var
  LData: TBytes;
begin
  while not Terminated do
  begin
    // 송신 예시
    LData := TEncoding.UTF8.GetBytes('Hello, Bluetooth!');
    FSocket.SendData(LData);
  
    // 수신 예시
    LData := FSocket.ReceiveData();
    if Length(LData) > 0 then
    begin
      if Assigned(FOnReceived) then
        TThread.Queue(nil,
          procedure
          begin
            FOnReceived(TEncoding.UTF8.GetString(LData));
          end
        );
    end;
    
    Sleep(100); // adjust as needed
  end;
end;

이 스레드는 생성자에 블루투스 소켓과 수신된 데이터를 처리하는 콜백을 전달받습니다. Execute 메서드에서는 루프를 돌며 데이터를 송수신하고, 수신된 데이터가 있으면 콜백을 호출합니다. 이 때, 콜백은 TThread.Queue 메서드를 사용하여 메인 스레드에서 실행되도록 합니다. 이는 VCL나 FMX 등의 GUI 요소를 안전하게 업데이트하기 위해서입니다.

이 스레드를 사용하는 방법은 다음과 같습니다.

var
  LThread: TBluetoothCommThread;
begin
  LThread := TBluetoothCommThread.Create(FSocket,
    procedure (AReceived: string)
    begin
      ShowMessage(AReceived);
    end);
end;

이 코드는 새 TBluetoothCommThread 인스턴스를 생성하고, 블루투스 소켓과 콜백을 전달합니다. 콜백에서는 수신된 문자열을 메시지 박스에 표시합니다.

위의 코드는 간단한 예시이며 실제 사용 시에는 송수신 로직, 에러 처리, 스레드 종료 등을 적절히 구현해야 합니다. 또한 이 스레드는 소켓이 열려 있는 동안 계속 실행되므로, 소켓이 더 이상 필요하지 않을 때는 스레드를 안전하게 종료해야합니다.

var
  LThread: TBluetoothCommThread;
begin
  // Create Thread
  LThread := TBluetoothCommThread.Create(FSocket,
    procedure (AReceived: string)
    begin
      ShowMessage(AReceived);
    end);
  
  // At some point when you're done with the socket
  LThread.Terminate;
  LThread.WaitFor;
  LThread.Free;
end;

이 코드는 TBluetoothCommThread 인스턴스를 생성하고 나중에 종료합니다. Terminate 메서드를 호출하면 Terminated 프로퍼티가 True로 설정되고, 이는 스레드가 종료해야 함을 나타냅니다. WaitFor 메서드는 스레드가 종료될 때까지 현재 스레드를 차단하며, 이는 스레드가 완전히 종료된 후에만 리소스를 정리하도록 보장합니다.

이렇게 스레드를 사용하면 데이터 송수신이 메인 스레드를 차단하지 않으므로 사용자 인터페이스가 더 반응적이게 됩니다. 그러나 스레드를 사용하면 복잡성이 증가하므로 주의가 필요합니다. 특히 여러 스레드에서 동시에 공유 데이터에 접근하는 경우에는 동기화를 신경 써야 합니다.

지금까지 내용을 아래 내용을 참고하여 전체 코드를 만들어줘 폼이름 : fMain 버튼을 클릭하면 페이링된 블루투스 리스트박스에 넣기 리스트박스에서 블루투스 선택하연 연결하기 Send 버튼을 클릭하면 byte 배열값 전송하기 수신된 값 Memo1에 표시하기

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation,
  FMX.StdCtrls, FMX.Layouts, FMX.ListBox, FMX.ScrollBox, FMX.Memo,
  System.Bluetooth, FMX.Edit;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Button3: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    FManager: TBluetoothManager;
    FAdapter: TBluetoothAdapter;
    FPairedDevices: TBluetoothDeviceList;
    FSocket: TBluetoothSocket;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  // Initialize Bluetooth Manager
  FManager := TBluetoothManager.Current;
  FAdapter := FManager.CurrentAdapter;

  // Get paired devices and add to ListBox
  FPairedDevices := FAdapter.PairedDevices;
  for i := 0 to FPairedDevices.Count - 1 do
    ListBox1.Items.Add(FPairedDevices[i].DeviceName);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LDevice: TBluetoothDevice;
  LService: TGUID;
begin
  // Get selected device and connect
  LDevice := FPairedDevices[ListBox1.ItemIndex];
  LService := LDevice.GetServices[0].UUID;

  FSocket := LDevice.CreateClientSocket(LService, False);
  if FSocket <> nil then
    FSocket.Connect;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  LData: TBytes;
begin
  // Send data
  LData := TEncoding.UTF8.GetBytes(Edit1.Text);
  FSocket.SendData(LData);
end;

end.

Button1은 페어링된 블루투스 디바이스를 검색하고 이름을 리스트박스에 추가하는 기능을 수행합니다. Button2는 리스트박스에서 선택한 디바이스와 연결하는 기능을 수행합니다. 마지막으로 Button3은 사용자가 입력한 텍스트를 UTF-8로 인코딩한 후 블루투스로 보내는 기능을 수행합니다.

이 코드는 간단한 예제이며, 실제 사용시에는 각 버튼 클릭 이벤트에 에러 처리 코드를 추가해야 합니다. 또한 수신된 데이터를 처리하려면 별도의 스레드를 생성하여 데이터를 비동기적으로 수신하도록 하는 것이 좋습니다.

수신 스레드는 이전에 제시했던 TBluetoothCommThread를 참고하시면 되며, 그 안의 수신부분을 아래처럼 변경하여 수신 데이터를 Memo1에 표시하도록 할 수 있습니다.

procedure TBluetoothCommThread.Execute;
var
  LData: TBytes;
begin
  while not Terminated do
  begin
    // 수신 예시
    LData := FSocket.ReceiveData();
    if Length(LData) > 0 then
    begin
      if Assigned(FOnReceived) then
        TThread.Queue(nil,
          procedure
          begin
            FOnReceived(TEncoding.UTF8.GetString(LData));
          end
        );
    end;

    Sleep(100); // adjust as needed
  end;
end;

이 코드를 사용하면 수신된 데이터가 Memo1에 자동으로 추가됩니다.

마지막으로, 이런 블루투스 통신을 다룰 때는 예외 처리와 에러 체크가 중요하므로 적절히 추가해 주어야 합니다. 예를 들어 블루투스 소켓이 유효한지 확인하거나, 데이터를 수신하기 전에 블루투스가 연결되어 있는지 확인하는 등의 작업이 필요합니다. 이러한 예외 처리가 없으면 앱이 예상치 못한 방식으로 작동하거나 충돌할 수 있습니다.

스레드와 예외처리까지 들어간 코드로 수정해줘

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation,
  FMX.StdCtrls, FMX.Layouts, FMX.ListBox, FMX.ScrollBox, FMX.Memo,
  System.Bluetooth, FMX.Edit;

type
  TBluetoothCommThread = class(TThread)
  private
    FSocket: TBluetoothSocket;
    FOnReceived: TProc<string>;
  protected
    procedure Execute; override;
  public
    constructor Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>);
  end;
  
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Button3: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    FManager: TBluetoothManager;
    FAdapter: TBluetoothAdapter;
    FPairedDevices: TBluetoothDeviceList;
    FSocket: TBluetoothSocket;
    FCommThread: TBluetoothCommThread;
  public
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TBluetoothCommThread }

constructor TBluetoothCommThread.Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>);
begin
  inherited Create(False);
  FreeOnTerminate := False;
  FSocket := ASocket;
  FOnReceived := AOnReceived;
end;

procedure TBluetoothCommThread.Execute;
var
  LData: TBytes;
begin
  while not Terminated do
  begin
    // 수신 예시
    LData := FSocket.ReceiveData();
    if Length(LData) > 0 then
    begin
      if Assigned(FOnReceived) then
        TThread.Queue(nil,
          procedure
          begin
            FOnReceived(TEncoding.UTF8.GetString(LData));
          end
        );
    end;

    Sleep(100); // adjust as needed
  end;
end;

{ TForm1 }

destructor TForm1.Destroy;
begin
  if Assigned(FCommThread) then
  begin
    FCommThread.Terminate;
    FCommThread.WaitFor;
    FreeAndNil(FCommThread);
  end;

  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  // Initialize Bluetooth Manager
  FManager := TBluetoothManager.Current;
  FAdapter := FManager.CurrentAdapter;

  // Get paired devices and add to ListBox
  FPairedDevices := FAdapter.PairedDevices;
  for i := 0 to FPairedDevices.Count - 1 do
    ListBox1.Items.Add(FPairedDevices[i].DeviceName);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LDevice: TBluetoothDevice;
  LService: TGUID;
begin
  if ListBox1.ItemIndex < 0 then
  begin
    ShowMessage('Please select a device.');
    Exit;
  end;

  // Get selected device and connect
  LDevice := FPairedDevices[ListBox1.ItemIndex];
  LService := LDevice.GetServices[0].UUID;

  try
    FSocket := LDevice.CreateClientSocket(LService, False);
    if FSocket <> nil then
    begin
      FSocket.Connect;
      FCommThread := TBluetoothCommThread.Create(FSocket,
        procedure (AReceived: string)
        begin
          TThread.Queue(nil,
            procedure
            begin
              Memo1.Lines.Add(AReceived);
            end
          );
        end);
    end;
  except
    on E: Exception do
    begin
      ShowMessage('Failed to connect: ' + E.Message);
      FSocket := nil;
    end;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  LData: TBytes;
begin
  if not Assigned(FSocket) then
  begin
    ShowMessage('Please connect to a device first.');
    Exit;
  end;

  try
    // Send data
    LData := TEncoding.UTF8.GetBytes(Edit1.Text);
    FSocket.SendData(LData);
  except
    on E: Exception do
    begin
      ShowMessage('Failed to send data: ' + E.Message);
    end;
  end;
end;

end.

위의 코드는 아래와 같은 기능을 수행합니다:

  1. "Button1"을 클릭하면 페어링된 블루투스 디바이스를 검색하고, 이름을 리스트박스에 추가합니다.
  2. "Button2"를 클릭하면 리스트박스에서 선택한 디바이스와 연결하며, 연결 성공 시 수신을 위한 스레드를 생성합니다.
  3. "Button3"를 클릭하면 입력된 텍스트를 UTF-8로 인코딩한 후 블루투스로 전송합니다.
  4. 데이터 수신은 별도의 스레드에서 처리하며, 수신된 데이터를 Memo1에 표시합니다.

모든 연결 및 데이터 전송 시에는 예외 처리가 수행되며, 에러 메시지를 표시합니다. 또한 소켓을 사용한 뒤에는 반드시 제거해야 하며, 이를 위해 Form의 소멸자에서 스레드를 종료하고 제거합니다.

안드로이드 기기에서도 작동할까?

이 코드는 Delphi의 FireMonkey 프레임워크를 사용하고 있으므로 Windows, macOS, iOS, 그리고 Android와 같은 여러 플랫폼에서 작동하도록 설계되었습니다.

그러나, 실제로 플랫폼 간에 작동하는지 확인하기 위해서는 테스트가 필요합니다. 특히 블루투스 관련 API는 플랫폼에 따라 다르게 동작할 수 있으므로, Android에서 이 코드를 실행할 때에는 주의가 필요합니다.

또한, Android에서 이 앱을 실행하려면 사용자로부터 블루투스 권한을 받아야 합니다. 이를 위해 AndroidManifest.xml 파일에 아래와 같은 항목을 추가해야 합니다:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

또한 Android 6.0 (API Level 23) 이상에서는 런타임 권한 요청이 필요하며, 이는 Delphi의 TBluetoothManager에서 자동으로 처리해 줍니다. 그러나 사용자가 권한 요청을 거절할 경우, 블루투스 기능은 동작하지 않을 것입니다.

Listbox1에서 선택된 BT에 전송하도록 수정해줘

네, ListBox에서 선택한 블루투스 디바이스에 데이터를 전송하도록 코드를 수정하겠습니다. 이를 위해 각각의 디바이스에 대한 소켓을 생성하고 저장할 수 있는 컬렉션을 만들어야 합니다.

다음은 이를 반영한 코드입니다:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation,
  FMX.StdCtrls, FMX.Layouts, FMX.ListBox, FMX.ScrollBox, FMX.Memo,
  System.Bluetooth, FMX.Edit, System.Generics.Collections;

type
  TBluetoothCommThread = class(TThread)
  private
    FSocket: TBluetoothSocket;
    FOnReceived: TProc<string>;
  protected
    procedure Execute; override;
  public
    constructor Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>);
  end;
  
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Button3: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    FManager: TBluetoothManager;
    FAdapter: TBluetoothAdapter;
    FPairedDevices: TBluetoothDeviceList;
    FSockets: TObjectDictionary<string, TBluetoothSocket>;
    FCommThreads: TObjectDictionary<string, TBluetoothCommThread>;
  public
    destructor Destroy; override;
    constructor Create(AOwner: TComponent); override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

{ TBluetoothCommThread }

constructor TBluetoothCommThread.Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>);
begin
  inherited Create(False);
  FreeOnTerminate := False;
  FSocket := ASocket;
  FOnReceived := AOnReceived;
end;

procedure TBluetoothCommThread.Execute;
var
  LData: TBytes;
begin
  while not Terminated do
  begin
    // 수신 예시
    LData := FSocket.ReceiveData();
    if Length(LData) > 0 then
    begin
      if Assigned(FOnReceived) then
        TThread.Queue(nil,
          procedure
          begin
            FOnReceived(TEncoding.UTF8.GetString(LData));
          end
        );
    end;

    Sleep(100); // adjust as needed
  end;
end;

{ TForm1 }

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited;

  FSockets := TObjectDictionary<string, TBluetoothSocket>.Create([doOwnsValues]);
  FCommThreads := TObjectDictionary<string, TBluetoothCommThread>.Create([doOwnsValues]);
end;

destructor TForm1.Destroy;
begin
  FCommThreads.Free;
  FSockets.Free;

  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  // Initialize Bluetooth Manager
  FManager := TBluetoothManager.Current;
  FAdapter := FManager.CurrentAdapter;

  // Get paired devices and add to ListBox
  FPairedDevices := FAdapter.PairedDevices;
  for i := 0to FPairedDevices.Count - 1 do
    ListBox1.Items.Add(FPairedDevices[i].DeviceName);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LDevice: TBluetoothDevice;
  LService: TGUID;
  LSocket: TBluetoothSocket;
begin
  if ListBox1.ItemIndex < 0 then
  begin
    ShowMessage('Please select a device.');
    Exit;
  end;

  // Get selected device and connect
  LDevice := FPairedDevices[ListBox1.ItemIndex];
  LService := LDevice.GetServices[0].UUID;

  try
    LSocket := LDevice.CreateClientSocket(LService, False);
    if LSocket <> nil then
    begin
      LSocket.Connect;
      FSockets.AddOrSetValue(LDevice.DeviceName, LSocket);

      FCommThreads.AddOrSetValue(LDevice.DeviceName, TBluetoothCommThread.Create(LSocket,
        procedure (AReceived: string)
        begin
          TThread.Queue(nil,
            procedure
            begin
              Memo1.Lines.Add(AReceived);
            end
          );
        end));
    end;
  except
    on E: Exception do
    begin
      ShowMessage('Failed to connect: ' + E.Message);
      LSocket := nil;
    end;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  LDeviceName: string;
  LSocket: TBluetoothSocket;
  LData: TBytes;
begin
  LDeviceName := ListBox1.Selected.Text;
  if not FSockets.TryGetValue(LDeviceName, LSocket) then
  begin
    ShowMessage('Please connect to a device first.');
    Exit;
  end;

  try
    // Send data
    LData := TEncoding.UTF8.GetBytes(Edit1.Text);
    LSocket.SendData(LData);
  except
    on E: Exception do
    begin
      ShowMessage('Failed to send data: ' + E.Message);
    end;
  end;
end;

end.

이 코드는 ListBox에서 선택한 디바이스와 연결하고 해당 디바이스의 소켓을 저장합니다. 데이터 전송시에는 ListBox에서 선택한 디바이스의 소켓을 사용하여 데이터를 전송합니다. 이와 같은 방식으로 ListBox에서 선택한 디바이스와만 통신하게 됩니다.

BT 연결이 끊어지면 알림을 코드로 만들어줘

블루투스 연결이 끊어질 때 알림을 제공하기 위해선 소켓 연결의 상태를 주기적으로 확인해야 합니다. TBluetoothSocket 클래스에는 직접적인 연결 상태를 확인하는 메서드가 없으므로, 데이터를 전송하거나 수신할 때 발생하는 예외를 통해 연결이 끊어졌음을 감지해야 합니다.

블루투스 통신 스레드 내에서 예외 처리를 추가하여 연결이 끊어진 경우 메인 스레드에 알림을 보낼 수 있습니다.

다음 코드는 이를 적용한 예시입니다:

type
  TBluetoothCommThread = class(TThread)
  private
    FSocket: TBluetoothSocket;
    FOnReceived: TProc<string>;
    FOnDisconnected: TProc;
  protected
    procedure Execute; override;
  public
    constructor Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>; AOnDisconnected: TProc);
  end;

...

constructor TBluetoothCommThread.Create(ASocket: TBluetoothSocket; AOnReceived: TProc<string>; AOnDisconnected: TProc);
begin
  inherited Create(False);
  FreeOnTerminate := False;
  FSocket := ASocket;
  FOnReceived := AOnReceived;
  FOnDisconnected := AOnDisconnected;
end;

procedure TBluetoothCommThread.Execute;
var
  LData: TBytes;
begin
  while not Terminated do
  begin
    try
      // 수신 예시
      LData := FSocket.ReceiveData();
      if Length(LData) > 0 then
      begin
        if Assigned(FOnReceived) then
          TThread.Queue(nil,
            procedure
            begin
              FOnReceived(TEncoding.UTF8.GetString(LData));
            end
          );
      end;

      Sleep(100); // adjust as needed
    except
      on E: Exception do
      begin
        if Assigned(FOnDisconnected) then
          TThread.Queue(nil,
            procedure
            begin
              FOnDisconnected();
            end
          );
          
        Terminate;
      end;
    end;
  end;
end;

...

procedure TForm1.Button2Click(Sender: TObject);
var
  LDevice: TBluetoothDevice;
  LService: TGUID;
  LSocket: TBluetoothSocket;
begin
  if ListBox1.ItemIndex < 0 then
  begin
    ShowMessage('Please select a device.');
    Exit;
  end;

  // Get selected device and connect
  LDevice := FPairedDevices[ListBox1.ItemIndex];
  LService := LDevice.GetServices[0].UUID;

  try
    LSocket := LDevice.CreateClientSocket(LService, False);
    if LSocket <> nil then
    begin
      LSocket.Connect;
      FSockets.AddOrSetValue(LDevice.DeviceName, LSocket);

      FCommThreads.AddOrSetValue(LDevice.DeviceName, TBluetoothCommThread.Create(LSocket,
        procedure (AReceived: string)
        begin
          TThread.Queue(nil,
            procedure
            begin
              Memo1.Lines.Add(AReceived);
            end
          );
        end,
        procedure
        begin
          TThread.Queue(nil,
            procedure
            begin
              ShowMessage('Bluetooth connection lost!');
            end
          );
        end));
    end;
 except
    on E: Exception do
    begin
      ShowMessage('Failed to connect: ' + E.Message);
      LSocket := nil;
    end;
  end;
end;

이 코드에서는 TBluetoothCommThread 클래스의 생성자에 연결 끊김 알림을 처리할 콜백을 추가했습니다. Execute 메서드에서는 ReceiveData 메서드를 호출하면서 예외를 처리하고, 이 예외가 발생하면 연결이 끊어졌다고 판단하고 알림 콜백을 호출하도록 했습니다. 이렇게 함으로써 블루투스 연결이 끊어질 때마다 사용자에게 알림 메시지를 표시할 수 있습니다.

반응형
LIST