Bluetoothlaz - Installation and example comments lazarus / 32-Linux ---------------------------------------------------------------------- TopCat · 2021-01-11 19:38 I've had some issues installing the package and running the examples under Lazarus 2.0.10, fpc 3.0.4 and Raspian OS. Ref: https://wiki.lazarus.freepascal.org/Bluetooth Under the heading "Installation", I think a step may have been left out from the statement "Choose the bluetooth/bluetoothlaz.lpk and open it. That's all. The IDE now knows the package." When the file is opened, a dialog appears. I found I had to Compile the file then Use | Add to project. Once that was done Lazarus recognized the package. This is probably obvious to experienced users but I'm not one of them. I then tried to run the examples. I got numerous errors of clashing types and I can't understand the statement  " c_close(device_sock);" at the end of the FindBlueToothClick event handler, which caused an error. I'm still learning the differences between fpc Pascal and Delphi but Lazarus didn't recognize "C_Close". I wonder if it is a typo? Can anyone guide me towards a working example of code that demonstrates how to identify and connect to a Bluetooth device (it's a serial mini-printer)? I've connected to it via the Raspian UI and sent data successfully using LazSerial methods. The bit I need is how to identify the bluetooth printer and connect to it directly through code without using the Raspbian UI. Many thanks to anyone who can help a newbie at this. ---------------------------------------------------------------------- af0815 · 2021-01-12 05:44 I have a inofficial fork here https://github.com/afriess/bluetoothlaz. I have one sample running with a Bluetooth RFID / Barcode Reader running well. Running on Debian Buster and actual Raspbian. The error on c_close is normaly before. If you call close it popup, have you looked if the handle is valid in the debugger ? One goal is to have all the needed parts for bluetooth must be installed with sudo apt-get install libbluetooth-dev to run the sample code correct. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses Bluetooth, unixtype, Sockets; { TForm1 } procedure TForm1.BuFindBluetoothClick(Sender: TObject);var device_id, device_sock: cint; scan_info: array[0..127] of inquiry_info; scan_info_ptr: Pinquiry_info; found_devices: cint; DevName: array[0..255] of Char; PDevName: PCChar; RemoteName: array[0..255] of Char; PRemoteName: PCChar; i: Integer; timeout1: Integer = 5; timeout2: Integer = 5000;begin // get the id of the first bluetooth device. device_id := hci_get_route(nil); if (device_id < 0) then   raise Exception.Create('FindBlueTooth: hci_get_route') else   writeln('device_id = ',device_id);  // create a socket to the device device_sock := hci_open_dev(device_id); if (device_sock < 0) then   raise Exception.Create('FindBlueTooth: hci_open_dev') else   writeln('device_sock = ',device_sock);  // scan for bluetooth devices for 'timeout1' seconds scan_info_ptr:=@scan_info[0]; FillByte(scan_inf o[0],SizeOf(inquiry_info)*128,0); found_devices := hci_inquiry_1(device_id, timeout1, 128, nil, @scan_info_ptr, IREQ_CACHE_FLUSH);  writeln('found_devices (if any) = ',found_devices);  if (found_devices < 0) then   raise Exception.Create('FindBlueTooth: hci_inquiry') else     begin       PDevName:=@DevName[0];       ba2str(@scan_info[0].bdaddr, PDevName);       writeln('Bluetooth Device Address (bdaddr) DevName = ',PChar(PDevName));        PRemoteName:=@RemoteName[0];       // Read the remote name for 'timeout2' milliseconds       if (hci_read_remote_ name(device_sock,@scan_info[0].bdaddr,255,PRemoteName,timeout2) < 0) then         writeln('No remote name found, check timeout.')       else         writeln('RemoteName = ',PChar(RemoteName));     end;  hci_close_dev(device_sock);end;  But the code in  http://sourceforge.net/projects/lazarus- ccr/files/Bluetooth/ is imho more complete than the wiki code (and the code in my inoffcial fork is actual running on some Raspis) ---------------------------------------------------------------------- TopCat · 2021-01-12 14:08 Thanks af0815, your code worked.d The original code worked after I changed the last statement of btnFindBluetoothClick from  to your and added to the uses clause. That appears to be missing from the original samples at: https://wiki.lazarus.freepascal.org/Bluetooth. My next challenge is to figure out how to connect to the Bluetooth printer so I can send text to it. I've tried the code in procedure ConnectRFCOMMClick taken from the original sample.  Although this runs, I haven't figured out how to relate the output from BuFindBluetoothClick to the settings needed in ConnectRFCOMMClick to output text to my printer. I've changed the value assigned to bt_addr, the device address, to the address of my printer. If I set to the value of the device_sock generated by BuFindBluetoothClick (12) , the procedure is unable to connect. By experiment I found that setting to 1 executed the code to send a message but nothing got through to the printer (which is switched on!). I was hoping that ConnectRFCOMM would make a serial connection to the bluetooth printer and enable me to send further text by outputting to e.g. /dev/rfcomm0 or similar. I'd be grateful for any insights or words of wisdom you can offer. ---------------------------------------------------------------------- af0815 · 2021-01-12 14:57 In the complete sample in the git you have a line with cnt:= fprecv(s,@bt_message, 1024, 0) if you change this to fpwrite you can send the messagedata to the device. In my application i have put all together in a thread to send command to the Scanner and read the data depending if i adressed the rfid reader or the barcode part of the scanner. You have no device in /dev/rfcomm. All the communication must be done by fprecv and fpwrite, no com emulation by rfcom-driver is made. ---------------------------------------------------------------------- TopCat · 2021-01-13 17:21 Andreas, Thanks for your help. I made use of your code from https://github.com/afriess/bluetoothlaz and changed the line cnt:= fprecv(s,@bt_message, 1024, 0); to cnt:= fpsend(s,@bt_message, 1024, 0); as fpwrite does not appear to be supported. The good news is that it sent data to the printer  . Unfortunately, there was a lot of it, all in hex  I've tried code like this to send a string to the printer but without success. Just lots of hex.   if status = 0 then     begin      msg := 'Here is my test output' + #13#10;      cnt := fpsend(s, @msg, sizeof(msg), 0);      if cnt = -1 then        Memo1.Lines.Add('Error occured');      // close socket after message is sent      fpshutdown(s, 0);      Memo1.Lines.Add('end');     end;  I've obviously misunderstood the (limited) explanation of the fpsend function and / or handling the mapping of the string to the buffer. I can achieve what I want by using the Raspbian Bluetooth Manager to create a rfcomm serial connection to the printer, then simply run this code to print out a string: lsPrintr is a TLazSerial component. procedure TMainForm.btnBluetoothClick(Sender: TObject); begin   if lsPrinter.Active = True then     lsPrinter.Close;   lsPrinter.BaudRate:= br__9600;   lsPrinter.Device := '/dev/rfcomm0';   lsPrinter.Open;   lsPrinter.WriteData('Test output' + #13#10); end; I was hoping to avoid needing (others) to use the Bluetooth Manager to create a connection to the printer before running my code but it looks like the simplest way to do it, unless you can offer any additional thoughts? Many thanks for your support with this. It is appreciated. Terry ---------------------------------------------------------------------- af0815 · 2021-01-15 11:41 This a complete threaded code from a running sample, which is reading and writing to/from a RFID Reader. The reason for this is, rfcomm is not built automatic if the communication direktion is not the default. I have tested this with 2 RasPis. On one the rfcomm is built on the other not. One must be the initator and one the responder. This is only the logical sight, it have nothing to do with the transferdirection of the data later. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i const  FIONREAD = $541B;  FIONBIO  = $5421;  FIOASYNC = $5452; procedure TBTReadThread.Execute;var readlen:cint32; writelen:cint32; bt_msg: string; FDS : Tfdset; watchdog: integer; TimeV: TTimeVal; timeout:integer;begin  FInLoop:=false;  if DummyCmd = '' then    DummyCmd:= '.ec -x'#13; // Set echo default als Dummycommando opt := SizeOf(loc_addr); //remote device address bt_addr := FDevice; //allocate socket s := fpsocket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); //channel that destination device is listening on channel := 1; loc_addr.rc_family := AF_BLUETOOTH; loc_addr.rc_bdaddr := bd_addr; loc_addr.rc_channel := channel; str2ba(@bt_addr, @bd_addr); loc_addr.rc_bdaddr := bd_addr; Debugln('Trying connection to ', bt_addr);  status := fpconnect(s, @loc_addr, opt); if status <> 0 then begin   res:=4;   MustDie:= true; end; Debugln('channel: ', IntToStr(channel), '  result: ', IntToStr(status)); try   bt_msg:='';   Owner.AktData:='';   watchdog:= 0;   while not MustDie do begin     FInLoop:=true;     try       res:= FpIOCtl(s,FIONREAD,@readlen);       //Debugln('FpIOCtl=', IntToStr(res), ' Cnt=', IntToStr(readlen));     except       MustDie:= true;       res:=1;       continue;       //Synchronize(@CallStatus);     end;     if (res <> 0) then begin      Synchronize(@CallStatus);     end;     if (res = 0) and (readlen > 0)  then begin       SetLength(bt_msg,readlen);       cnt:= fprecv(s,Pchar(bt_msg), readlen, 0);       {$ifdef DEBUG}DebugLn('after fprec', ' cnt=',IntToStr(cnt));{$endif}       if cnt <> 0 then begin         Owner.AktData:= Owner.AktData + bt_msg;       Synchronize(@CallEvent);       end;     end;     if Length(FData) > 0 then begin       writelen:= FpWrite(s,pchar(FData),length(FData));       if writelen=Length(FData) then begin         FData:='';         {$ifdef DEBUG}DebugLn('FpWrite ok=', IntToStr(writelen));{$endif}       end       else         if writelen= -1 then begin           DebugLn('Error after FpWrite');         MustDie:= true;           res:=3;           continue;         end         else begin           DebugLn('Problem after FpWrite =',IntToStr(writelen));         end;     end     else begin       inc(watchdog);       if watchdog > (FCheckTime*10) then begin         watchdog:=0;         writelen:= FpWrite(s,pchar(DummyCmd),Length(DummyCmd));         if writelen= -1 then begin           DebugLn('Watchdog Error after FpWrite');         MustDie:= true;           res:=4;           continue;         end       else begin           {$ifdef DEBUG}DebugLn('Watchdog FpWrite ok=', IntToStr(writelen)); {$endif}         end;       end;     end;     Sleep(100);   end;   Synchronize(@CallStatus); finally   FInLoop:=False;   // close socket   fpshutdown(s, 0);   CloseSocket(s);   Terminate; end; Debugln('end');end; ---------------------------------------------------------------------- TopCat · 2021-01-16 15:32 PROBLEM SOLVED  Andreas, I'd almost given up, particularly with the thought of getting into threads that I have little experience of. However, I extracted and modified a section of your TBTReadThread.Execute code and inserted that to replace part of the code in your example ConnectRFClick. In case it is of use to others, I've shown the modified ConnectRFClick method below. With Buetooth turned on, running the ConnectRFClick procedure printed the string FData directly to the printer. No other steps were needed. A big THANK YOU for all your support and your patience. TerryCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.ConnectRFClick(Sender: TObject);var  loc_addr: sockaddr_rc;  opt: longint;  s, status: unixtype.cint;  bd_addr: bdaddr_t;  channel: byte;  bt_addr: array[0..17] of char;  //bt_message: array[0..1023] of char; //Variable not used  //cnt: ssize_t; //Variable not used  {Additional declarations}  FData: string;  writelen: integer;begin  opt := SizeOf(loc_addr);  //remote device address  //bt_addr := '00:07:80:66:0A:5E';  bt_addr := '00:15:0E:E4:03:49'; {my printer address}  //allocate socket  s := fpsocket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);  //channel that destination device is listening on  channel := 1;  loc_addr.rc_family := AF_BLUETOOTH;  //loc_addr.rc_bdaddr := bd_addr; //Not needed. Same code 3 lines down.  loc_addr.rc_channel := channel;  str2ba(@bt_addr, @bd_addr);  loc_addr.rc_bdaddr := bd_addr;  writeln('Trying connection to ', bt_addr);   status := fpconnect(s, @loc_addr, opt);  writeln('channel: ', channel, '  result: ', status);   FData := 'Lets try printing this...';  FData := FData + #13#10 + 'and one more line...';  FData := FData + #13#10; {Add CRLF to get output}  if status = 0 then    begin      {Code extracted and adapted from procedure TBTReadThread.Execute;       Successfully sends FData string to the Bluetooth printer}      if Length(FData) > 0 then        begin          //writelen:= FpWrite(s,pchar(FData),length(FData));          writelen:= fpsend(s,pchar(FData),length(FData), 0);          if writelen=Length(FData) then            begin              FData:='';            writeln{DebugLn}('FpWrite ok=', IntToStr(writelen));          end          else            if writelen= -1 then              begin                writeln{DebugLn}('Error after FpWrite');              //MustDie:= true;                //res:=3;                //continue;              end            else              begin              writeln{DebugLn}('Problem after FpWrite =',IntToStr(writelen));              end;        end;  (*    cnt:=0;  while cnt >= 0 do    begin      bt_message := '';      //cnt:= fprecv(s,@bt_message, 1024, 0);      cnt:= fpsend(s,@bt_message, 1024, 0);      if cnt = 0 then      begin        writeln('leer');      end    else        Write(bt_message);    end;  *)    // close socket after message is sent    fpshutdown(s, 0);    writeln('end');  end;end; ---------------------------------------------------------------------- af0815 · 2021-01-16 16:22 The threaded version is not so complicaded. The execute is the major part, allother is only tranfer the data thread safe. The execute can send data and receive and do a alive with the dummy command to detect if the connection is broken. This was one of my troubles to detect safe, if the connection is broken. If the connection is detected as broken, the not shown code handle tis, look for a device and reconnect it, show the user that the connection is broken and ... This is the reason for the more complex code. ---------------------------------------------------------------------- TopCat · 2021-01-16 16:47 Andreas, Understood. I don't need to deal with two-way data transfer by Bluetooth, so I hope that my code will be adequate. We'll see I've learnt quite a lot about Linux, Lazarus and Free Pascal while trying to solve this problem, in addition to the specifics that you have helped with. Thank you for your time.