Recent

Author Topic: [SOLVED] Bluetooth using Winsock  (Read 3027 times)

Graham1

  • Jr. Member
  • **
  • Posts: 57
[SOLVED] Bluetooth using Winsock
« on: March 25, 2021, 04:45:58 am »
I'm trying to send data to and read data from a Bluetooth device that doesn't have a Serial Port Profile set up. I've been told that 'all' I need to do is open it at a specific address on channel 10. Looking into this I find that the Bluetooth package for Lazarus was Beta 8 years ago and even then only for Linux, so I suspect there is no need for a package now. I looked at the documentation here:

https://docs.microsoft.com/en-us/windows/win32/bluetooth/bluetooth-programming-with-windows-sockets

which helped a bit, and then I found this post:

https://forum.lazarus.freepascal.org/index.php?topic=28913.0

which seems to come even closer. I've tried amending the code by Thaddy to this:

Code: Pascal  [Select][+][-]
  1. program sendtest;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses   SysUtils, windows, winsock2;
  6.  
  7. const  AF_BTH          = 32;
  8.        BTHPROTO_RFCOMM = 3;
  9.  
  10. var    MyData        : WSADATA;
  11.        Rslt          : Integer;
  12.        s             : TSocket;
  13.        SendBuf       : Array[0..31] of AnsiChar;
  14.        clientservice : sockaddr_in;
  15.        BytesSent     : Integer;
  16.  
  17. begin
  18.  
  19.    try
  20.       Rslt := WSAStartup(MAKEWORD(2,2), MyData);
  21.  
  22.       if Rslt = NO_ERROR then begin
  23.          s := socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
  24.  
  25.          if s <> INVALID_SOCKET then begin
  26.             clientservice.sin_family := AF_BTH;
  27.             clientservice.sin_addr.s_addr := bth_addr('11:22:33:44:55:66');  // FAILS!
  28.             clientservice.sin_port := 10;
  29.  
  30.             if connect(s,clientservice,sizeof(clientservice)) <> SOCKET_ERROR then begin
  31.                sendbuf := 'la la la la la la la :)';
  32.                bytesSent := send(s,sendbuf,Length(sendbuf),0);
  33.                writeln('Bytes sent: ',bytesSent);
  34.  
  35.             end else
  36.                writeln('Failed to connect');
  37.          end else
  38.             writeln('Error at Socket: ',WSAGetLastError);
  39.       end else
  40.            writeln('Error at WSAStartup');
  41.  
  42.    finally
  43.       Writeln(SysErrorMessage(GetLastError));
  44.       WSACleanUp;
  45.    end;
  46.  
  47. end.

The immediate problem I have is that Winsock2.pp in FPC doesn't seem to know anything about Bluetooth. It doesn't have AF_BTH for example. Then the next problem is how to use the address? In the original code Thaddy used inet_addr because he was accessing a php server but of course that won't work here.

There must be somebody that has already done this as BT has been around now for so long. Or is there a package I can use? Any help really appreciated! If you do reply though please don't assume I know anything about this as I've never worked with sockets or BT at a programming level before.

Thanks,
Graham
« Last Edit: March 29, 2021, 02:45:11 am by Graham1 »
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

jamie

  • Hero Member
  • *****
  • Posts: 6128
Re: Bluetooth using Winsock
« Reply #1 on: March 25, 2021, 09:59:08 pm »
I didn't know blue tooth devices cam IP active? maybe they do but what ever...

also I thought V6Ip had 8 indexes ?

also, shouldn't they be separated via "." ?
The only true wisdom is knowing you know nothing

trev

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2020
  • Former Delphi 1-7, 10.2 user
Re: Bluetooth using Winsock
« Reply #2 on: March 25, 2021, 10:33:21 pm »
also, shouldn't they be separated via "." ?

Bluetooth addresses are usually 6 bytes in hexadecimal separated by colons.

Graham1

  • Jr. Member
  • **
  • Posts: 57
Re: Bluetooth using Winsock
« Reply #3 on: March 25, 2021, 11:17:59 pm »
also, shouldn't they be separated via "." ?

Bluetooth addresses are usually 6 bytes in hexadecimal separated by colons.

Yes, Bluetooth addresses are different to IP addresses:

https://macaddresschanger.com/what-is-bluetooth-address-BD_ADDR

Winsock handles a lot more protocols than just IP!
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: Bluetooth using Winsock
« Reply #4 on: March 26, 2021, 02:03:52 am »
Bluetooth addresses are usually 6 bytes in hexadecimal separated by colons.

That is because Bluetooth uses MAC addresses, not IP addresses.  SOCKADDR_IN is meant for IPv4 IP addresses only.  You have to use SOCKADDR_BTH for Bluetooth.  This is even stated as much in Microsoft's Bluetooth and connect documentation, which is linked to by the 1st document linked to by Graham1's initial question.

WinSock does not have a function like inet_addr() for Bluetooth, but it is not hard to write such a function, eg:

Code: Pascal  [Select][+][-]
  1. program sendtest;
  2.      
  3. {$APPTYPE CONSOLE}
  4.      
  5. uses
  6.   SysUtils, Windows, Winsock2;
  7.      
  8. const
  9.   AF_BTH          = 32;
  10.   BTHPROTO_RFCOMM = 3;
  11.  
  12. type
  13.   BTH_ADDR = ULONGLONG;
  14.   SOCKADDR_BTH = record
  15.     addressFamily: USHORT;
  16.     btAddr: BTH_ADDR;
  17.     serviceClassId: TGUID;
  18.     port: ULONG;
  19.   end;
  20.      
  21. function str2ba(const str_bt_addr: string): BTH_ADDR;
  22. var
  23.   addr: array[1..6] of Integer;
  24.   i, first, last: Integer;
  25.   bt_addr: BTH_ADDR;
  26. begin
  27.   Result := 0;
  28.   bt_addr := 0;
  29.   first := 1;
  30.   for i := 1 to 5 do
  31.   begin
  32.     last := Pos(':', str_bt_addr, first);
  33.     if last = 0 then Exit;
  34.     if not TryStrToInt('$'+Copy(str_bt_addr, first, last-first), addr[i]) then Exit;
  35.     first := last + 1;
  36.   end;
  37.   if not TryStrToInt('$'+Copy(str_bt_addr, first, MaxInt), addr[6]) then Exit;
  38.   for i := 1 to 6 do bt_addr := (bt_addr shl 8) or BTH_ADDR(Byte(addr[i]));
  39.   Result := bt_addr;
  40. end;
  41.  
  42. var
  43.   MyData: WSADATA;
  44.   Rslt: Integer;
  45.   s: TSocket;
  46.   SendBuf: Array[0..31] of AnsiChar;
  47.   clientservice: sockaddr_bth;
  48.   BytesSent: Integer;
  49.      
  50. begin
  51.   try
  52.     Rslt := WSAStartup(MAKEWORD(2,2), MyData);
  53.     if Rslt = NO_ERROR then begin
  54.       s := socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);    
  55.       if s <> INVALID_SOCKET then begin
  56.         clientservice.addressFamily := AF_BTH;
  57.         clientservice.btAddr := str2ba('11:22:33:44:55:66');
  58.         clientservice.serviceClassId = ...;
  59.         clientservice.port := 10;
  60.      
  61.         if connect(s, PSockAddr(@clientservice), SizeOf(clientservice)) <> SOCKET_ERROR then begin
  62.           sendbuf := 'la la la la la la la :)';
  63.           bytesSent := send(s, sendbuf, Length(sendbuf), 0);
  64.           writeln('Bytes sent: ', bytesSent);    
  65.           closesocket(s);
  66.         end else
  67.           writeln('Failed to connect');
  68.       end else
  69.         writeln('Error at Socket: ', WSAGetLastError);
  70.     end else
  71.       writeln('Error at WSAStartup');
  72.   finally
  73.     Writeln(SysErrorMessage(GetLastError));
  74.     WSACleanUp;
  75.   end;
  76.      
  77. end.
  78.  
« Last Edit: March 26, 2021, 05:05:05 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Graham1

  • Jr. Member
  • **
  • Posts: 57
Re: Bluetooth using Winsock
« Reply #5 on: March 26, 2021, 03:59:41 am »
Bluetooth addresses are usually 6 bytes in hexadecimal separated by colons.

That is because Bluetooth uses MAC addresses, not IP addresses.  SOCKADDR_IN is meant for IPv4 IP addresses only.  You have to use SOCKADDR_BTH for Bluetooth.  This is even stated as much in Microsoft's Bluetooth and connect documentation, which is linked to by the 1st document linked to by Graham1's initial question.

WinSock does not have a function like inet_addr() for Bluetooth, but it is not hard to write such a function, eg:


I suppose 'not hard' is relative! Thank you so much for your information and code. Of course I still have a question about the line:

Code: Pascal  [Select][+][-]
  1. clientservice.serviceClassId = ...;

Am I right in thinking that the service class id can be found in the Properties of the device manager? If so I have some choices:

Code: Text  [Select][+][-]
  1. Class GUID: {e0cbf06c-cd8b-4647-bb8a-263b43f0f974}
  2. Bluetooth class of device: 00000000
  3. Bluetooth service GUID: {00000000-0000-0000-0000-000000000000}

or there are 50 or so other settings. And if it's one of the ones with '{...}' how do I represent that in Pascal in the line required? And if it isn't found there where do I find it? Your linked description says it is ignored anyway if the port is specified so I tried:

Code: Pascal  [Select][+][-]
  1. const      MyGUID : TGUID = '{00000000-0000-0000-0000-000000000000}';
  2.  :
  3.  :
  4. clientservice.serviceClassId := MyGUID;

which compiles OK. But when I run it I'm getting the error:

Failed to connect
The specified system semaphore name was not found.

I have no idea what that means though. I'm sorry if these are stupid questions but I did warn you that I know nothing!!

Once again, thank you so much for your help,
Graham


EDIT: I found an error in the function:

Code: Pascal  [Select][+][-]
  1. if not TryStrToInt('$'+Copy(str_bt_addr, first, last-1), addr[i]) then Exit;
should be:
Code: Pascal  [Select][+][-]
  1. if not TryStrToInt('$'+Copy(str_bt_addr, first, last-first), addr[i]) then Exit;

Unfortunately that doesn't fix the semaphore error.
« Last Edit: March 26, 2021, 05:22:08 am by Graham1 »
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: Bluetooth using Winsock
« Reply #6 on: March 26, 2021, 05:23:39 pm »
Am I right in thinking that the service class id can be found in the Properties of the device manager?

I don't know.  One would hope.  Otherwise, you can use  WSALookupServiceBegin() and WSALookupServiceNext() to discover service GUIDs.

If so I have some choices:

Code: Text  [Select][+][-]
  1. Class GUID: {e0cbf06c-cd8b-4647-bb8a-263b43f0f974}
  2. Bluetooth class of device: 00000000
  3. Bluetooth service GUID: {00000000-0000-0000-0000-000000000000}

That Class GUID is the general ID for Bluetooth devices.  So I think you are looking for the service GUID, which is just NULL in that example.

And if it's one of the ones with '{...}' how do I represent that in Pascal in the line required?

You already know the answer to that - as a TGUID, which you can assign a string literal to and the compiler will convert it for you.  Or you can use a function like SysUtils.StringToGUID().

Your linked description says it is ignored anyway if the port is specified so I tried:

Code: Pascal  [Select][+][-]
  1. const      MyGUID : TGUID = '{00000000-0000-0000-0000-000000000000}';
  2.  :
  3.  :
  4. clientservice.serviceClassId := MyGUID;

which compiles OK.

That is what I would have done.

But when I run it I'm getting the error:

Failed to connect
The specified system semaphore name was not found.

I have no idea what that means though.

Neither do I.  I don't ever code for Bluetooth.

First thing I would check is the alignment settings of the SOCKADDR_BTH record, make sure all of the fields are at the expected offsets in memory.  Maybe the record needs to be byte-aligned or quad-aligned (probably the latter).

EDIT: I found an error in the function:

Fixed.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Graham1

  • Jr. Member
  • **
  • Posts: 57
Re: Bluetooth using Winsock
« Reply #7 on: March 27, 2021, 05:52:15 am »
Well, this is about as far as I can get:

Code: Pascal  [Select][+][-]
  1. program sendtest;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   SysUtils, windows, winsock2;
  7.  
  8. const
  9.   AF_BTH          = 32;
  10.   BTHPROTO_RFCOMM = 3;
  11.   BT_MAC          = 'B8:27:EB:F1:38:CC';
  12.   BT_PORT         = 10;
  13.   BT_CLASS        = '{00000000-0000-0000-0000-000000000000}';
  14.  
  15. type
  16.   BTH_ADDR = UINT64;
  17.  
  18.   SOCKADDR_BTH = record
  19.     addressFamily  : USHORT;
  20.     btAddr         : BTH_ADDR;
  21.     serviceClassId : TGUID;
  22.     port           : ULONG;
  23.   end;
  24.  
  25. function connectBT(const s:TSocket; const name:SOCKADDR_BTH; namelen:Longint): Longint; stdcall;external WINSOCK2_DLL name 'connect';
  26.  
  27. function str2ba(const str_bt_addr: string): BTH_ADDR;
  28. var
  29.   addr           : array[1..6] of Integer;
  30.   i, first, last : Integer;
  31.   bt_addr        : BTH_ADDR;
  32. begin
  33.   Result := 0;
  34.   bt_addr := 0;
  35.   first := 1;
  36.   for i := 1 to 5 do begin
  37.     last := Pos(':', str_bt_addr, first);
  38.     if last = 0 then Exit;
  39.     if not TryStrToInt('$'+Copy(str_bt_addr, first, last-first), addr[i]) then Exit;
  40.     if addr[i]>255 then Exit;
  41.     first := last+1;
  42.   end;
  43.   if not TryStrToInt('$'+Copy(str_bt_addr, first, MaxInt), addr[6]) then Exit;
  44.   if addr[6]>255 then Exit;
  45.   for i := 1 to 6 do bt_addr := (bt_addr shl 8) or BTH_ADDR(Byte(addr[i]));
  46.   Result := bt_addr;
  47. end;
  48.  
  49.  
  50. var
  51.   MyData         : WSADATA;
  52.   Rslt, i        : Integer;
  53.   s              : TSocket;
  54.   clientservice  : SOCKADDR_BTH;
  55.   SendBuf        : Array[0..5] of Byte;
  56.   BytesSent      : Integer;
  57.  
  58.  
  59. begin
  60.  
  61.   try
  62.     Rslt := WSAStartup(MAKEWORD(2,2), MyData);
  63.  
  64.     if Rslt = NO_ERROR then begin
  65.       s := socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
  66.  
  67.       if s <> INVALID_SOCKET then begin
  68.         clientservice.addressFamily := AF_BTH;
  69.         clientservice.btAddr := str2ba(BT_MAC);
  70.         clientservice.serviceClassId := StringtoGUID(BT_CLASS);
  71.         clientservice.port := BT_PORT;
  72.  
  73.         if connectBT(s, clientservice, SizeOf(clientservice)) <> SOCKET_ERROR then begin
  74.           for i:=0 to 5 do SendBuf[i]:=0;
  75.           bytesSent := send(s, SendBuf, 6, 0);
  76.           writeln('Bytes sent: ', bytesSent);
  77.           closesocket(s);
  78.         end else
  79.           writeln('Failed to connect');
  80.                                        
  81.       end else
  82.         writeln('Error at Socket: ', WSAGetLastError);
  83.                                
  84.     end else
  85.       writeln('Error at WSAStartup');
  86.  
  87.   finally
  88.     if WSAGetLastError<>0 then
  89.       Writeln(SysErrorMessage(WSAGetLastError)+' [Error code '+Inttostr(WSAGetLastError)+']');
  90.     WSACleanUp;
  91.   end;
  92.  
  93. end.

I'm sure the problem is related to the layout of the SOCKADDR_BTH but I can't see what's wrong. All I get is the error 187 about the semaphore name.  :(
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Bluetooth using Winsock
« Reply #8 on: March 27, 2021, 09:30:32 pm »
Try using "packed record"

jamie

  • Hero Member
  • *****
  • Posts: 6128
Re: Bluetooth using Winsock
« Reply #9 on: March 27, 2021, 10:29:47 pm »
on the CONNECTBT function that got imported,,..

Try to remove the first CONST and for the second const use CONSTREF instead so that the compile will always pass a pointer to it.

removing the first CONST will stop the compiler from making its own choice of how to pass the parameter.

the Second CONST is really not useful if you ask me, just change the it a pointer only so the compiler.. you don't care about the CONST definition here because that code is outside of your code space.
 
 So try to reconfigure the protocol header with no constants and use a Pointer to the ClientService entry...
The only true wisdom is knowing you know nothing

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Bluetooth using Winsock
« Reply #10 on: March 28, 2021, 06:58:33 pm »
FYI, SOCKADDR_BTH is already defined in JwaWindows (more precisely  in unit JwaWs2Bth) and it uses packed record. The record size is 30 bytes, the same size I saw in its C counterpart in header file ws2bth.h:
Code: C  [Select][+][-]
  1. #include <winsock2.h>
  2. #include <ws2bth.h>
  3. #include <stdio.h>
  4.  
  5. int main()
  6. {
  7.   printf("Size of SOCKADDR_BTH: %d", sizeof(SOCKADDR_BTH));  //<--- gives 30
  8.   return 0;
  9. }

Without "packed" your record is 40 bytes on a 32bit system.
« Last Edit: March 28, 2021, 07:21:14 pm by engkin »

Graham1

  • Jr. Member
  • **
  • Posts: 57
Re: Bluetooth using Winsock
« Reply #11 on: March 29, 2021, 02:44:41 am »
Jamie and Engkin, you were both right! Although the const on the socket wasn't a problem I rechecked the Microsoft documentation and the socket address does have to be set as a pointer not raw data. And the record did need to be packed to ensure that all the bytes were in the correct place, not realigned by the compiler. I didn't even know there was a JwaWs2Bth file so it looks like I might have just reinvented the wheel, but I'll stick with what I have now it works.

The finished code has been rearranged to use an ugly 'while true do' but it makes it easier to follow what is going on as the nested Ifs were getting a bit too deep. I also found I had to be more careful with closing the socket. Because my device keeps sending data I must shutdown the connection properly before closing the socket. So the final version:

Code: Pascal  [Select][+][-]
  1. program sendtest;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   SysUtils, windows, winsock2;
  7.  
  8. const
  9.   AF_BTH          = 32;
  10.   BTHPROTO_RFCOMM = 3;
  11.   BT_MAC          = 'B8:27:EB:F1:38:CC';
  12.   BT_PORT         = 10;
  13.   BT_CLASS        = '{00000000-0000-0000-0000-000000000000}';
  14.  
  15. type
  16.   BTH_ADDR = UInt64;
  17.  
  18.   PSockAddrBth = ^TSockAddrBth;
  19.   TSockAddrBth = packed record
  20.     addressFamily  : USHORT;
  21.     btAddr         : BTH_ADDR;
  22.     serviceClassId : TGUID;
  23.     port           : ULONG;
  24.   end;
  25.  
  26. function connectBT(const s: TSocket; name: PSockAddrBth; namelen: Longint): Longint; stdcall;external WINSOCK2_DLL name 'connect';
  27.  
  28. function str2ba(const str_bt_addr: string): BTH_ADDR;
  29. var
  30.   addr           : array[1..6] of Integer;
  31.   i, first, last : Integer;
  32.   bt_addr        : BTH_ADDR;
  33. begin
  34.   Result := 0;
  35.   bt_addr := 0;
  36.   first := 1;
  37.   for i := 1 to 5 do begin
  38.     last := Pos(':', str_bt_addr, first);
  39.     if last = 0 then Exit;
  40.     if not TryStrToInt('$'+Copy(str_bt_addr, first, last-first), addr[i]) then Exit;
  41.     if addr[i] > 255 then Exit;
  42.     first := last+1;
  43.   end;
  44.   if not TryStrToInt('$'+Copy(str_bt_addr, first, MaxInt), addr[6]) then Exit;
  45.   if addr[6] > 255 then Exit;
  46.   for i := 1 to 6 do bt_addr := (bt_addr shl 8) or BTH_ADDR(Byte(addr[i]));
  47.   Result := bt_addr;
  48. end;
  49.  
  50.  
  51. var
  52.   WSinfo         : WSADATA;
  53.   Rslt, i, ErrNo : Integer;
  54.   s              : TSocket;
  55.   clientservice  : TSockAddrBth;
  56.   clientservptr  : PSockAddrBth;
  57.   SendBuf        : Array[0..5] of Byte;
  58.   BytesSent      : Integer;
  59.   RecvBuf        : Array[0..1023] of Byte;
  60.   BytesRecv      : Integer;
  61.  
  62.  
  63. BEGIN
  64.  
  65.   for i := 0 to 5    do SendBuf[i] := 0;
  66.   for i := 0 to 1023 do RecvBuf[i] := 0;
  67.   ErrNo := 0;
  68.  
  69.   while true do begin
  70.  
  71.     Rslt := WSAStartup(MAKEWORD(2,2), WSinfo);
  72.     if Rslt <> NO_ERROR then begin
  73.       ErrNo := WSAGetLastError;
  74.       writeln('Error at WSAStartup');
  75.       break;
  76.     end;
  77.  
  78.     s := socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
  79.     if s = INVALID_SOCKET then begin
  80.       ErrNo := WSAGetLastError;
  81.       writeln('Error at Socket');
  82.       break;
  83.     end;
  84.  
  85.     clientservice.addressFamily := AF_BTH;
  86.     clientservice.btAddr := str2ba(BT_MAC);
  87.     clientservice.serviceClassId := StringtoGUID(BT_CLASS);
  88.     clientservice.port := BT_PORT;
  89.     clientservptr := @clientservice;
  90.  
  91.     if connectBT(s, clientservptr, SizeOf(clientservice)) = SOCKET_ERROR then begin
  92.       ErrNo := WSAGetLastError;
  93.       writeln('Error at Connect');
  94.       break;
  95.     end;
  96.  
  97.     BytesSent := send(s, SendBuf, 6, 0);
  98.     writeln('Bytes sent: ', BytesSent);
  99.     if BytesSent <> 6 then begin
  100.       ErrNo := WSAGetLastError;
  101.       writeln('Error at Send');
  102.       break;
  103.     end;
  104.  
  105.     BytesRecv := recv(s, RecvBuf, 1024, 0);
  106.     writeln('Bytes received A: ', BytesRecv);
  107.  
  108.     if s <> INVALID_SOCKET then begin
  109.  
  110.       Rslt := shutdown(s, SD_SEND);
  111.       if Rslt <> 0 then begin
  112.         ErrNo := WSAGetLastError;
  113.         writeln('Error at Shutdown Send');
  114.         break;
  115.       end;
  116.  
  117.       repeat
  118.         BytesRecv := recv(s, RecvBuf, 1024, 0);
  119.         writeln('Bytes received B: ', BytesRecv);
  120.       until BytesRecv = 0;
  121.  
  122.       Rslt := shutdown(s, SD_RECEIVE);
  123.       if Rslt <> 0 then begin
  124.         ErrNo := WSAGetLastError;
  125.         writeln('Error at Shutdown Receive');
  126.         break;
  127.       end;
  128.  
  129.       Rslt := closesocket(s);
  130.       if Rslt <> 0 then begin
  131.         ErrNo := WSAGetLastError;
  132.         writeln('Error at Close Socket');
  133.         break;
  134.       end;
  135.  
  136.     end;
  137.  
  138.     break;
  139.  
  140.   end;
  141.  
  142.   if ErrNo <> 0 then
  143.      writeln(SysErrorMessage(ErrNo)+' [Error code '+Inttostr(ErrNo)+']')
  144.   else
  145.      writeln('Success!');
  146.  
  147.   WSACleanUp;
  148.  
  149. END.

Thank you everybody for your input. I honestly didn't think I was going to get it working and without your help I never would have!  :)

« Last Edit: April 09, 2021, 06:49:29 am by Graham1 »
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

Graham1

  • Jr. Member
  • **
  • Posts: 57
Re: [SOLVED] Bluetooth using Winsock
« Reply #12 on: April 09, 2021, 06:30:25 am »
My program above connects to a Bluetooth device and sends/receives data but relies on already knowing the BT address. I thought I'd post here how I do my searches for addresses since although there are C examples online there don't seem to be many in Pascal. Maybe this will be useful to somebody.

I first thought I'd try the JWA package mentioned above and it worked fine on my 64 bit Windows PC until I compiled my program as 32 bit. Then it didn't find any devices any more. I think this is because it uses the procedures in a 64 bit Control Panel program (irprops.cpl) rather than the Windows DLL file (BluetoothApis.dll). Perhaps the DLL file was added in a later update to Windows. So I extracted the parts I needed using https://docs.microsoft.com/en-us/windows/win32/bluetooth/bluetooth-functions and linked them to the DLL file giving me:

Code: Pascal  [Select][+][-]
  1. program finddev;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   SysUtils, crt, LazUTF8;
  7.    
  8. const
  9.   WINBT_DLL               = 'BluetoothApis.dll';
  10.   BLUETOOTH_MAX_NAME_SIZE = 248;
  11.  
  12.  
  13. type
  14.   BTH_ADDR = UInt64;
  15.   BOOL = Longbool;
  16.  
  17.   SYSTEMTIME = record
  18.     wYear          : Word;
  19.     wMonth         : Word;
  20.     wDayOfWeek     : Word;
  21.     wDay           : Word;
  22.     wHour          : Word;
  23.     wMinute        : Word;
  24.     wSecond        : Word;
  25.     wMilliseconds  : Word;
  26.   end;
  27.  
  28.   BLUETOOTH_ADDRESS = record
  29.     case Integer of
  30.       0: (ullLong: BTH_ADDR);
  31.       1: (rgBytes: array [0..5] of Byte);
  32.   end;
  33.  
  34.   BLUETOOTH_DEVICE_SEARCH_PARAMS = record
  35.     dwSize               : DWORD;
  36.     fReturnAuthenticated : BOOL;
  37.     fReturnRemembered    : BOOL;
  38.     fReturnUnknown       : BOOL;
  39.     fReturnConnected     : BOOL;
  40.     fIssueInquiry        : BOOL;
  41.     cTimeoutMultiplier   : Byte;
  42.     hRadio               : THandle;
  43.   end;
  44.  
  45.   BLUETOOTH_DEVICE_INFO = record
  46.     dwSize          : DWORD;
  47.     Address         : BLUETOOTH_ADDRESS;
  48.     ulClassofDevice : DWORD;
  49.     fConnected      : BOOL;
  50.     fRemembered     : BOOL;
  51.     fAuthenticated  : BOOL;
  52.     stLastSeen      : SYSTEMTIME;
  53.     stLastUsed      : SYSTEMTIME;
  54.     szName          : array[0..BLUETOOTH_MAX_NAME_SIZE-1] of WideChar;
  55.   end;
  56.  
  57.   HBLUETOOTH_DEVICE_FIND = THandle;
  58.  
  59.  
  60. function BluetoothFindFirstDevice(const pbtsp: BLUETOOTH_DEVICE_SEARCH_PARAMS; var pbtdi: BLUETOOTH_DEVICE_INFO): HBLUETOOTH_DEVICE_FIND; stdcall; external WINBT_DLL name 'BluetoothFindFirstDevice';
  61. function BluetoothFindNextDevice(hFind: HBLUETOOTH_DEVICE_FIND; var pbtdi: BLUETOOTH_DEVICE_INFO): BOOL; stdcall; external WINBT_DLL name 'BluetoothFindNextDevice';
  62. function BluetoothFindDeviceClose(hFind: HBLUETOOTH_DEVICE_FIND): BOOL; stdcall; external WINBT_DLL name 'BluetoothFindDeviceClose';
  63.  
  64.  
  65. var
  66.   DeviceFindHandle   : THandle;
  67.   DeviceInfo         : BLUETOOTH_DEVICE_INFO;
  68.   DeviceSearchParams : BLUETOOTH_DEVICE_SEARCH_PARAMS;
  69.   devcnt             : Integer = 0;
  70.   devnam             : string;
  71.  
  72.  
  73. BEGIN
  74.  
  75.   DeviceInfo.dwSize:=sizeof(DeviceInfo);
  76.  
  77.   DeviceSearchParams.dwSize := sizeof(DeviceSearchParams);
  78.   DeviceSearchParams.fReturnAuthenticated := False;
  79.   DeviceSearchParams.fReturnRemembered := True;
  80.   DeviceSearchParams.fReturnUnknown := True;
  81.   DeviceSearchParams.fReturnConnected := False;
  82.   DeviceSearchParams.fIssueInquiry := True;
  83.   DeviceSearchParams.cTimeoutMultiplier := 1;
  84.  
  85.   DeviceFindHandle := BluetoothFindFirstDevice(DeviceSearchParams, DeviceInfo);
  86.  
  87.   if DeviceFindHandle <> 0 then begin
  88.  
  89.     repeat
  90.       devnam:=InttoHex(DeviceInfo.Address.ullLong,12);
  91.       devnam:=devnam+' is '+UTF16ToUTF8(StrPas(DeviceInfo.szName));
  92.       if DeviceInfo.fConnected then
  93.         devnam:=devnam+'  (Connected)';
  94.       writeln(devnam);
  95.       inc(devcnt);
  96.     until not BluetoothFindNextDevice(DeviceFindHandle, DeviceInfo);
  97.  
  98.     BluetoothFindDeviceClose(DeviceFindHandle);
  99.  
  100.   end;
  101.  
  102.   writeln;
  103.   if devcnt = 0 then
  104.      writeln('No devices found')
  105.   else
  106.      writeln(inttostr(devcnt)+' device(s) found!');
  107.  
  108.   writeln;
  109.   writeln('Press any key');
  110.   readkey;
  111.  
  112. END.

Then I thought I'd also try doing the searches using Winsock based on this https://docs.microsoft.com/en-us/windows/win32/bluetooth/bluetooth-and-wsalookupservicebegin and ended up with this:

Code: Pascal  [Select][+][-]
  1. program findwsa;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   SysUtils, crt, windows, winsock2;
  7.    
  8. const
  9.   NS_BTH = 16;
  10.  
  11.   BTHNS_RESULT_DEVICE_CONNECTED     = $00010000;
  12.   BTHNS_RESULT_DEVICE_REMEMBERED    = $00020000;
  13.   BTHNS_RESULT_DEVICE_AUTHENTICATED = $00040000;
  14.  
  15.  
  16. type
  17.   BTH_ADDR = UInt64;
  18.  
  19.   PSockAddrBth = ^TSockAddrBth;
  20.   TSockAddrBth = packed record
  21.     addressFamily  : Word;
  22.     btAddr         : BTH_ADDR;
  23.     serviceClassId : TGUID;
  24.     port           : Longword;
  25.   end;
  26.  
  27.   SOCKET_ADDRESSB = record
  28.     lpSockaddr      : PSockAddrBth;
  29.     iSockaddrLength : Longint;
  30.   end;
  31.  
  32.   CSADDR_INFOB = record
  33.     LocalAddr       : SOCKET_ADDRESSB;
  34.     RemoteAddr      : SOCKET_ADDRESSB;
  35.     iSocketType     : LongInt;
  36.     iProtocol       : LongInt;
  37.   end;
  38.   PCSADDR_INFOB = ^CSADDR_INFOB;
  39.  
  40.   TWSAQuerySetB = record
  41.     dwSize                  : LongInt;
  42.     lpszServiceInstanceName : PWideChar;
  43.     lpServiceClassId        : PGUID;
  44.     lpVersion               : PWSAVERSION;
  45.     lpszComment             : PWideChar;
  46.     dwNameSpace             : LongInt;
  47.     lpNSProviderId          : PGUID;
  48.     lpszContext             : PWideChar;
  49.     dwNumberOfProtocols     : LongInt;
  50.     lpafpProtocols          : PAFProtocols;
  51.     lpszQueryString         : PWideChar;
  52.     dwNumberOfCsAddrs       : LongInt;
  53.     lpcsaBuffer             : PCSADDR_INFOB;
  54.     dwOutputFlags           : LongInt;
  55.     lpBlob                  : PBLOB;
  56.   end;
  57.   PWSAQuerySetB = ^TWSAQuerySetB;
  58.  
  59.  
  60.   TQueryResult = record
  61.     QrySet          : TWSAQuerySetB;
  62.     buffer          : array[0..2047] of byte;
  63.   end;
  64.  
  65.  
  66. function WSALookupServiceBeginB(const lpqsRestrictions:PWSAQuerySetB; const dwControlFlags:DWORD; lphLookup:PHANDLE):Longint; stdcall; external WINSOCK2_DLL name 'WSALookupServiceBeginW';
  67. function WSALookupServiceNextB(const hLookup:THandle; const dwControlFlags:DWORD; var lpdwBufferLength:DWORD; lpqsResults:PWSAQuerySetB):Longint; stdcall; external WINSOCK2_DLL name 'WSALookupServiceNextW';
  68.  
  69.  
  70. var
  71.   WSinfo            : WSADATA;
  72.   WSquery           : TWSAQuerySetB;
  73.   WSresult          : TQueryResult;
  74.   hLookUp           : THandle;
  75.   len               : DWORD;
  76.   flags             : DWORD;
  77.  
  78.   Rslt, ErrNo       : Integer;
  79.   devcnt            : Integer = 0;
  80.   devnam            : string;
  81.  
  82.  
  83. BEGIN
  84.  
  85.   while true do begin
  86.  
  87.     WSinfo.wHighVersion:=0;
  88.     Rslt := WSAStartup(MAKEWORD(2,2), WSinfo);
  89.     if Rslt <> NO_ERROR then begin
  90.       ErrNo := WSAGetLastError;
  91.       writeln('Error at WSAStartup');
  92.       break;
  93.     end;
  94.     writeln('WinSock '+inttostr(lobyte(WSinfo.wVersion))+'.'+inttostr(hibyte(WSinfo.wVersion)));
  95.     writeln;
  96.  
  97.     zeromemory(@WSquery, sizeof(WSquery));
  98.     WSquery.dwSize := sizeof(WSquery);
  99.     WSquery.dwNameSpace := NS_BTH;
  100.     flags := LUP_RETURN_ADDR OR LUP_RETURN_NAME OR LUP_CONTAINERS OR LUP_FLUSHCACHE;
  101.     hLookup := 0;
  102.  
  103.     if WSALookupServiceBeginB(@WSquery, flags, @hLookup) = SOCKET_ERROR then begin
  104.       ErrNo := WSAGetLastError;
  105.       writeln('Error at WSABegin');
  106.       break;
  107.     end;
  108.  
  109.     len := sizeof(WSresult);
  110.     zeromemory(@WSresult, len);
  111.     WSresult.QrySet.dwSize := len;
  112.     WSresult.QrySet.dwNameSpace := NS_BTH;
  113.  
  114.     while WSALookupServiceNextB(hLookup, flags, len, @WSresult) = NO_ERROR do begin
  115.        devnam:=InttoHex(WSresult.QrySet.lpcsaBuffer^.RemoteAddr.lpSockaddr^.btAddr,12);
  116.        devnam:=devnam+' is '+string(WSresult.QrySet.lpszServiceInstanceName);
  117.        if (WSresult.QrySet.dwOutputFlags AND BTHNS_RESULT_DEVICE_CONNECTED) <> 0 then
  118.          devnam:=devnam+'  (Connected)';
  119.        writeln(devnam);
  120.        inc(devcnt);
  121.     end;
  122.     ErrNo := WSAGetLastError;
  123.     if (ErrNo <> WSAENOMORE) and (ErrNo <> WSA_E_NO_MORE) then begin
  124.        writeln('Error at WSANext');
  125.        break;
  126.     end else
  127.        ErrNo := 0;
  128.  
  129.     if WSALookupServiceEnd(hLookup) = SOCKET_ERROR then begin
  130.        ErrNo := WSAGetLastError;
  131.        writeln('Error at WSAEnd');
  132.        break;
  133.     end;
  134.  
  135.     break;
  136.  
  137.   end;
  138.  
  139.   WSACleanUp;
  140.  
  141.   writeln;
  142.   if ErrNo <> 0 then
  143.      writeln(SysErrorMessage(ErrNo)+' [Error code '+Inttostr(ErrNo)+']')
  144.   else
  145.   if devcnt = 0 then
  146.      writeln('No devices found')
  147.   else
  148.      writeln(inttostr(devcnt)+' device(s) found!');
  149.  
  150.   writeln;
  151.   writeln('Press any key');
  152.   readkey;
  153.  
  154. END.

In my machine the Winsock version is noticeably slower. Since both of these return addresses as 12 character strings the string-to-BTaddress function in the original program becomes:

Code: Pascal  [Select][+][-]
  1. function StrtoBTaddr(btstr:string):Bth_Addr;
  2. var    addr           : array[1..6] of integer;
  3.        i, first, last : integer;
  4.        bt_addr        : Bth_Addr;
  5. begin
  6.    result:=0;
  7.    bt_addr:=0;
  8.    first:=1;
  9.  
  10.    if (pos(':',btstr)=0) and (length(btstr)=12) then begin
  11.       for i:=1 to 6 do begin
  12.          if not TryStrToInt('$'+Copy(btstr, first, 2), addr[i]) then exit;
  13.          first:=first+2;
  14.       end;
  15.    end else begin
  16.       for i:=1 to 5 do begin
  17.          last:=Pos(':', btstr, first);
  18.          if last=0 then exit;
  19.          if not TryStrToInt('$'+copy(btstr, first, last-first), addr[i]) then exit;
  20.          if addr[i]>255 then exit;
  21.          first:=last+1;
  22.       end;
  23.       if not TryStrToInt('$'+copy(btstr, first, REST), addr[6]) then exit;
  24.       if addr[6]>255 then exit;
  25.    end;
  26.  
  27.    for i:=1 to 6 do bt_addr:= (bt_addr shl 8) OR Bth_Addr(addr[i]);
  28.    result:=bt_addr;
  29.  
  30. end;
« Last Edit: April 10, 2021, 02:40:02 am by Graham1 »
Windows 10/11 Home 64-bit (and Linux because I have to)
Lazarus 2.0.12 / FPC 3.2.0 (because libQt5pas 1.2.6)
Linux Mint 20 (because GLIBC_2.31)

 

TinyPortal © 2005-2018