├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images ├── insideblue_ble_tool_connect_window.png ├── insideblue_ble_tool_scan_window.png ├── insideblue_ble_tool_scan_window_log.png └── insideblue_ble_tool_vsp_terminal.png ├── src ├── assignednumbers.pas ├── ble.pas ├── connect.lfm ├── connect.pas ├── help.lfm ├── help.pas ├── help │ └── help.rtf ├── insideblue.ico ├── insideblue.lpi ├── insideblue.lpr ├── insideblue.res ├── main.lfm ├── main.pas ├── scan.pas ├── simpleble.pas ├── uartterminal.lfm ├── uartterminal.pas └── util.pas └── utils ├── BtAssignedNumbersToPascalUnit.py ├── README.md └── assignednumbers.pas /.gitignore: -------------------------------------------------------------------------------- 1 | # InsideBlue BLE Tool 2 | **/bin 3 | **/lib 4 | **/release_packages 5 | *.lps 6 | 7 | # Sublime Text 8 | *.tmlanguage.cache 9 | *.tmPreferences.cache 10 | *.stTheme.cache 11 | *.sublime-workspace 12 | *.sublime-project 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | This changelog document (CHANGELOG.md) is a summary of all notable changes to the [InsideBlue BLE Tool](https://github.com/eriklins/InsideBlue-BLE-Tool) project and application. 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and uses the Markdown formatting syntax. You can find the original example and template [here](https://github.com/olivierlacan/keep-a-changelog/blob/main/CHANGELOG.md). 4 | 5 | 6 | ## [0.8] - 2023-08-29 7 | 8 | ### Added 9 | 10 | - None. 11 | 12 | ### Changed 13 | 14 | - Slight optimization for scaling on high DPI desktops. 15 | 16 | ### Fixed 17 | 18 | - None. 19 | 20 | 21 | ## [0.7.2] - 2023-08-23 22 | 23 | ### Added 24 | 25 | - Setting of ModemIn characteristic when starting VSP terminal. 26 | - Handling of ModemOut characteristic notifications and enabling/disabling of Send button accordingly. 27 | 28 | ### Changed 29 | 30 | - None. 31 | 32 | ### Fixed 33 | 34 | - Typo in UartTerminalStart() with ModemIn/Out variable names. 35 | 36 | 37 | ## [0.7.1] - 2023-08-16 38 | 39 | ### Added 40 | 41 | - None. 42 | 43 | ### Changed 44 | 45 | - Improved garbage collection of device form data after disconnect. 46 | 47 | ### Fixed 48 | 49 | - Restore of "Connect" button in scan window when connected to multiple devices. 50 | 51 | 52 | ## [0.7] - 2023-08-10 53 | 54 | ### Added 55 | 56 | - Added BT style application icon and form window icon instead of default Lazarus one. 57 | 58 | ### Changed 59 | 60 | - Welcome message. 61 | - Updated BT assigned numbers list. 62 | - Updated underlying SimpleBLE library to 0.6.2 63 | 64 | ### Fixed 65 | 66 | - Fixed issue with scanning not catching all manufaturer specific data fields in advert payload. 67 | - Fixed restore of main form window when has been resized before. 68 | 69 | 70 | ## [0.6.1] - 2023-03-28 71 | 72 | ### Added 73 | 74 | - Updated simpleble.pas with SimpleBleGetVersion(). 75 | - Added output of SimpleBLE version to log on start. 76 | - Added checkbox in vsp terminal to select write command vs. write request (if exposed by peripheral). 77 | 78 | ### Changed 79 | 80 | - None. 81 | 82 | ### Fixed 83 | 84 | - Reduced flickering in VSP terminal when receiving large chunks of data. 85 | - Fixed bug when searching VSP service which is also an assigned service (didn't show vsp terminal button). 86 | 87 | ### Removed 88 | 89 | - None. 90 | 91 | 92 | ## [0.6] - 2023-02-28 93 | 94 | ### Added 95 | 96 | - Added check for the device connect window height to not exceed desktop in case of a large GATT table. 97 | - Added closing curly bracket as possible line endings on VSP terminal (useful for e.g. JSON data). 98 | - Added support for the u-blox u-connectXpress (formerly uCS) BLE Serial Port Service. 99 | - Added check for the position of next window to not exceed desktop. 100 | 101 | ### Changed 102 | 103 | - If no GATT characteristics are found for ModemIn and ModemOut, VSP terminal will continue without. 104 | - Changed the VSP terminal font to a more monospace-like terminal font. Source Code Pro is now used. 105 | - The VSP terminal can now be resized and automatically closes when a BLE device is disconnected. 106 | 107 | ### Fixed 108 | 109 | - Fixed MAC address on VSP terminal. The MAC address is now displayed correctly in upper case format (instead of lower case). 110 | - Fixed attempt to unsubscribe from notifications and indications when subscription failed before (still not fully working). 111 | - Fixed form issues in the scan window (missing label for manufacturer data, duplicate text box for manufacturer data). 112 | 113 | ### Removed 114 | 115 | - Removed counting of active VSP terminals, not needed. 116 | 117 | 118 | ## [0.5] - 2023-02-24 119 | 120 | ### Added 121 | 122 | - Added MTU size to device connect window and form. Longer data strings are now split into multiple BLE writes. 123 | - Added visual effect (blinking) to the text data field for received notifications via color change function. 124 | 125 | ### Changed 126 | 127 | - VSP terminal now uses MTU size -3 as presumption for the RX characteristic max length. This value can also be set manually. 128 | 129 | ### Fixed 130 | - Fixed bug when subscribing to notifications or indications on non-VSP services / characteristics. 131 | 132 | ### Removed 133 | 134 | - Removed redundant code in tick timer for disconnect. 135 | 136 | 137 | ## [0.4] - 2023-02-23 138 | 139 | ### Added 140 | 141 | - Added parts of the VSP terminal, started implementing the paired device status (not yet supported in the SimpleBLE library). 142 | - Added sanity check to not allow VSP terminal when one of the corresponding characteristics have been notified or indicated. 143 | - Added proprietary UART services and characteristics (Laird VSP plus Nordic NUS) and functions to map their UUIDs to names. 144 | - Added service data to scan output as support for this has been recently added to the SimpleBLE library. 145 | 146 | ### Changed 147 | 148 | - Changed storage and handling of UUIDs (TSimpleBleUuid instead of strings). This affected multiple functions, but it is simpler in the end. 149 | - Aligned code for SimpleBLE v0.6.0 release. Display service data in the scan window and make look more consistent. 150 | - Some UI tweaking for the GNU/Linux OS, also updated the BtAssignedNumbersToPascalUnit.py accordingly. 151 | - Code reorganization and independency of units. Restructured UART terminal functions. 152 | 153 | ### Fixed 154 | - Fixed bug with resizing the connect window, and resolved cross-dependencies between units. 155 | - Fixed position of the next form (still not perfect when opening or closing other forms). 156 | - Fixed crash on exit when UART terminal was open. Fixed crash on help button. 157 | - Fixed bug not finding 16-bit UUIDs after last recent changes. 158 | 159 | 160 | ## [0.3] - 2023-02-15 161 | 162 | Initial release on GitHub under https://github.com/eriklins/InsideBlue-BLE-Tool. 163 | 164 | 165 | 166 | [Unreleased]: https://github.com/eriklins/InsideBlue-BLE-Tool/compare/v0.6...HEAD 167 | [0.6]: https://github.com/eriklins/InsideBlue-BLE-Tool/compare/v0.5...v0.6 168 | [0.5]: https://github.com/eriklins/InsideBlue-BLE-Tool/compare/v0.4...v0.5 169 | [0.4]: https://github.com/eriklins/InsideBlue-BLE-Tool/compare/v0.3...v0.4 170 | [0.3]: https://github.com/eriklins/InsideBlue-BLE-Tool/releases/tag/v0.3 171 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Erik Lins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Due to limitations with using the underlying SimpleBLE library as DLL on Windows 11, I did a rewrite of that tool no longer using Lazarus/Freepascal and switched to C with [NAppGUI](https://github.com/frang75/nappgui_src) as cross-plattform SDK and still SimpleBLE but statically linked. See new project [BLE Scout](https://github.com/eriklins/BLE-Scout). 2 | 3 | # InsideBlue BLE Tool V0.8 4 | Welcome to **InsideBlue BLE Tool**, an easy to use Windows BLE application for Bluetooth Low-Energy (BLE) connectivity leveraging the PC’s integrated Bluetooth adapter. It's actually cross-platform, see below for Linux and Max OS support. 5 | 6 | You can scan for BLE devices that are advertising and discover their GAP services or manufacturer specific data, engage a connection and access the peripheral GATT table. 7 | 8 | ## Main Scan Window 9 | After start the main scan window opens and provides common controls for BLE device discovery. 10 | 11 | ![InsideBLue BLE Tool Scan Window](images/insideblue_ble_tool_scan_window.png) 12 | 13 | ### Start/Stop Scanning 14 | Pressing the **Start Scan** button starts scanning for BLE advertisments. Discovered devices will be shown as a panel list underneath. After scanning has started, the button turns into a **Stop Scan** button and BLE scanning stops when pressed again. The **Clear** button clears the panel list of discovered devices. 15 | 16 | ### Filters 17 | Three filters can be applied to the device scanning. Entering a device name or part of it into the left field will only show devices matching. The device name is not case-sensitive. The middle field filters for BLE mac addresses or part of it. The mac address has to be entered as hex numbers separated by colons (aa:bb:cc:dd:ee:ff). The right field allows filtering based on the RSSI value of received advertisements which – to some extent – represents the distance of a device. Values have to be entered as positive numbers. 18 | 19 | ### Log Output 20 | A more detailed log output can be enabled by ticking the Show Log Output box. The main window will extend to the right and show the log output. 21 | Two additonal buttons allow clearing the log output as well as copying the log output into the clipboard. You can also directly select text in the log output and copy. 22 | 23 | ![InsideBLue BLE Tool Scan Window with Log Output](images/insideblue_ble_tool_scan_window_log.png) 24 | 25 | ### Scan Data Panel 26 | For each discovered device a panel with device information is shown. It contains device name, connectable status, tx power (if available), RSSI value, mac address, advertised services as well as manufacturer specific data. 27 | 28 | If the UUID of a service or the company ID in the manufacturer data is known in the official Bluetooth list of assigned numbers, the official name will be shown. You can hover with the mouse over the name to see the underlying service UUID or full company name with ID. 29 | 30 | Manufacturer data can be displayed either as HEX numbers (default) or ASCII string. For ASCII non-printable characters are displayed as black boxes. 31 | 32 | ### Connect Device 33 | If a device is connectable you can engage a connection with the Connect button. This button is only available if the device does advertise with connectable flags active. 34 | 35 | ## Device Connect Window 36 | After connecting a new device specific window opens and the GATT table of the device is read. Services, characteristics and descriptors are shown in a panel structure grouped by services. 37 | 38 | ![InsideBLue BLE Tool Device Connect Window](images/insideblue_ble_tool_connect_window.png) 39 | 40 | If the UUID of a service, characteristic or descriptor is known in the official Bluetooth list of assigned numbers, the name will be shown. You can hover with the mouse over the name to see the underlying UUID number. 41 | 42 | Depending on the properties of each characteristic different buttons get added to the text field. Those are Read, WriteCommand, WriteRequest, Notify and Indicate. 43 | 44 | Data from/to a characteristic gets displayed or entered into a text field, which is either read/write or read only. You can switch the presentation between HEX numbers or ASCII string. When set to ASCII, non-printable characters are displayed as a black square. 45 | 46 | ### Virtual Serial Port (VSP) 47 | Beside the official Bluetooth list of assigned numbers for services, there exist proprietary services to indicate a virtual serial port service (similar to SPP service from Bluetooth Classic). InsideBlue knows some of these and if recognized it will show their names as well along with a button to fire up a simple VSP terminal window. Please find below a list of manufactures and proprietary GATT services / characteristics which are currently supported. 48 | 49 | 1. **Laird Connectivity** with the [Virtual Serial Port (VSP)](https://www.lairdconnect.com/documentation/application-note-laird-custom-ble-serial-port-service), 50 | 2. **u-blox** with the [u-connectXpress BLE Serial Port Service](https://content.u-blox.com/sites/default/files/u-connectXpress-LowEnergySerialPortService_ProtocolSpec_UBX-16011192.pdf), 51 | 3. **Nordic Semiconductors** with the [Nordic UART Service (NUS)](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.4.0/nrf/include/bluetooth/services/nus.html). 52 | 53 | If a certain VSP service is missing, please raise an [issue](https://github.com/eriklins/InsideBlue-BLE-Tool/issues) to get it added. 54 | 55 | ## UART VSP Terminal 56 | For known VSP services you can open a terminal window and send/receive text. Line endings can be selected for both send and receive individually. Each sent text line will be added to a history drop down menu and can be selected later for sending certain lines again. 57 | 58 | ![InsideBLue BLE Tool VSP Terminal Window](images/insideblue_ble_tool_vsp_terminal.png) 59 | 60 | (Currently only one VSP terminal can be opened at a time.) 61 | 62 | ### Line Endings 63 | You can set line endings for send (`` and/or `` will be appended to the send string) and receive (new line will be added to the receive window on ``, `` or closing curly bracket of e.g., a JSON string). 64 | 65 | ### Modem In/Out Characteristics 66 | Beside the mandatory TX (from peripheral to central) and RX (from central to peripheral) characteristics, some VSP services offer additional Modem In/Out characteristics which are used for handshake signalling much like RTS/CTS signals on a real UART. 67 | 68 | ### Length of RX Characteristic 69 | The length of the peripheral RX characteristic is not known to the central, hence when sending text lines with too many characters, the BLE write operation will fail. By default the characteristic length is set to the used MTU size -3, which is a known good guess. 70 | 71 | ### WriteCommand/WriteRequest 72 | Depending on the write properties of the RX characteristic you can select to use either write request or write command, if both are exposed. 73 | 74 | ## Pairing Devices 75 | On operating systems, initiating a connection procedure will automatically run pairing if necessary. This process is entirely managed by the operating system, there isn't much that we can do from the user side. 76 | 77 | This has the following implications: 78 | - The user will need to manually confirm pairing, until that happens no successful access to protected characteristics is possible. 79 | - Removing a device can only be done from the OS Bluetooth settings page. 80 | - There is no programmatic way of controlling this process. 81 | 82 | Note(s): This applies at least for Windows OS and might be different on other OS platforms. 83 | 84 | ## Limitations 85 | - Since InsideBlue leverages the PC’s integrated Bluetooth Controller, this ultimately limits what’s achievable from a BT hardware perspective (e.g., number of simultaneous connections). 86 | - It is not possible to tweak BLE settings like scan interval and/or scan window or similar low-level BLE parameters. 87 | - Due to a limitation of the underlying BLE library, it’s not possible to properly receive notifications or indications from devices exposing an identcal GATT table (i.e. identical service/characteristic UUIDs). This will be fixed in an upcoming release. 88 | - The underlying BLE library does not yet expose properties of the descriptors and hence it’s not yet possible to directly read/write them. 89 | 90 | ## Releases / Installation 91 | You can download pre-built binaries for Windows 64 bit, 32 bit and Linux 64 bit from the [releases tab](https://github.com/eriklins/InsideBlue-BLE-Tool/releases). 92 | 93 | InsideBlue BLE Tool does not require any installation. Just unzip anywhere and run. The ZIP includes the executable, two shared library files and a help file. 94 | 95 | ## Building InsideBlue BLE Tool 96 | This project is implemented with the [Lazarus IDE](https://www.lazarus-ide.org/) and [FreePascal](https://www.freepascal.org/). Lazarus is a free Delphi compatible cross-platform IDE for Rapid Application Development (RAD). FreePascal is a mature, versatile, open source Pascal compiler. 97 | 98 | InsideBlue BLE Tool was built with Lazarus IDE v3.99 (trunk) and FreePascal 3.3.1 on Windows 10 x86_64 and Ubuntu 22.04.3 LTS. 99 | 100 | Mac OS would be supported from both Lazarus/FreePascal as well as SimpleBLE library perspective. However, I did not yet look into this in detail. Feel free to raise an [issue](https://github.com/eriklins/InsideBlue-BLE-Tool/issues) if interested. 101 | 102 | ## BLE Library 103 | Inside Blue BLE Tool uses the [SimpleBLE library](https://github.com/OpenBluetoothToolbox/SimpleBLE) and corresponding [Pascal bindings](https://github.com/eriklins/Pascal-Bindings-For-SimpleBLE-Library). 104 | 105 | ## Releases 106 | Pre-built releases for Windows 64/32 bit and Linux 64 bit are available on the [releases tab](https://github.com/eriklins/InsideBlue-BLE-Tool/releases). 107 | 108 | ## Contributing/Feedback 109 | Feedback / improvements / pull-requests / etc. are welcome! 110 | 111 | ## License 112 | Copyright (C) 2023 Erik Lins 113 | 114 | This project is released under the MIT License. 115 | -------------------------------------------------------------------------------- /images/insideblue_ble_tool_connect_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/images/insideblue_ble_tool_connect_window.png -------------------------------------------------------------------------------- /images/insideblue_ble_tool_scan_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/images/insideblue_ble_tool_scan_window.png -------------------------------------------------------------------------------- /images/insideblue_ble_tool_scan_window_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/images/insideblue_ble_tool_scan_window_log.png -------------------------------------------------------------------------------- /images/insideblue_ble_tool_vsp_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/images/insideblue_ble_tool_vsp_terminal.png -------------------------------------------------------------------------------- /src/ble.pas: -------------------------------------------------------------------------------- 1 | unit Ble; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Dialogs, 9 | SimpleBle, AssignedNumbers; 10 | 11 | type 12 | TVspServiceUuids = record 13 | Uuid: TSimpleBleUuid; 14 | Name: String; 15 | ChRx: Integer; 16 | ChTx: Integer; 17 | ChModemIn: Integer; 18 | ChModemOut: Integer; 19 | end; 20 | 21 | TVspCharacteristicUuids = record 22 | Uuid: TSimpleBleUuid; 23 | Name: String; 24 | end; 25 | 26 | procedure BleInit; 27 | function BleAssignedServiceUuidToName(uuid: TSimpleBleUuid): String; 28 | function BleAssignedCharacteristicUuidToName(uuid: TSimpleBleUuid): String; 29 | function BleAssignedDescriptorUuidToName(uuid: TSimpleBleUuid): String; 30 | function BleAssignedCompanyIdToName(code: String): String; 31 | function BleVspServiceUuidToName(uuid: TSimpleBleUuid): String; 32 | function BleVspCharacteristicUuidToName(uuid: TSimpleBleUuid): String; 33 | 34 | const 35 | CharDescMaxLength = 512; 36 | 37 | 38 | // proprietary ble uart services, indexes point to characteristics in VspCharacteristicUuids (set to -1 if some do not exist) 39 | VspServiceUuids: array of TVspServiceUuids = ( 40 | 41 | (Uuid: (Value: '569a1101-b87f-490c-92cb-11ba5ea5167c'); Name: 'Laird Connectivity VSP Service'; ChRx: 0; ChTx: 1; ChModemIn: 2; ChModemOut: 3), 42 | (Uuid: (Value: '6e400001-b5a3-f393-e0a9-e50e24dcca9e'); Name: 'Nordic UART Service (NUS)'; ChRx: 4; ChTx: 5; ChModemIn: -1; ChModemOut: -1), 43 | (UUid: (Value: '2456e1b9-26e2-8f83-e744-f34f01e9d701'); Name: 'u-connectXpress BLE Serial Port Service'; ChRx: 6; ChTx: 7; ChModemIn: -1; ChModemOut: -1) 44 | 45 | ); 46 | 47 | VspCharacteristicUuids: array of TVspCharacteristicUuids = ( // proprietary ble uart characteristics 48 | 49 | // Laird Connectivity VSP Service 50 | (Uuid: (Value: '569a2001-b87f-490c-92cb-11ba5ea5167c'); Name: 'Laird Vsp Rx'), // 0 51 | (Uuid: (Value: '569a2000-b87f-490c-92cb-11ba5ea5167c'); Name: 'Laird Vsp Tx'), // 1 52 | (Uuid: (Value: '569a2003-b87f-490c-92cb-11ba5ea5167c'); Name: 'Laird Vsp ModemIn'), // 2 53 | (Uuid: (Value: '569a2002-b87f-490c-92cb-11ba5ea5167c'); Name: 'Laird Vsp ModemOut'), // 3 54 | 55 | // Nordic UART Service (NUS) 56 | (Uuid: (Value: '6e400002-b5a3-f393-e0a9-e50e24dcca9e'); Name: 'NUS Rx'), // 4 57 | (Uuid: (Value: '6e400003-b5a3-f393-e0a9-e50e24dcca9e'); Name: 'NUS TX'), // 5 58 | 59 | // u-connectXpress BLE Serial Port Service 60 | (Uuid: (Value: '2456e1b9-26e2-8f83-e744-f34f01e9d703'); Name: 'Serial Port FIFO / Rx'), // 6 61 | (Uuid: (Value: '2456e1b9-26e2-8f83-e744-f34f01e9d704'); Name: 'Serial Port Credit / Tx') // 7 62 | 63 | ); 64 | 65 | VspFuncRx = 0; 66 | VspFuncTx = 1; 67 | VspFuncModemIn = 2; 68 | VspFuncModemOut = 3; 69 | TagPosDev = 24; 70 | TagPosSrv = 16; 71 | TagPosChr = 8; 72 | TagPosDes = 0; 73 | 74 | var 75 | BleAdapter: TSimplebleAdapter; 76 | SimpleBleErr: TSimpleBleErr; 77 | BleAdapterIsInitialized: Boolean; 78 | BleIsEnabled: Boolean; 79 | 80 | 81 | implementation 82 | 83 | { Initilize Ble, mainly loading SimpleBLE lib } 84 | procedure BleInit; 85 | begin 86 | 87 | {$IFDEF DYNAMIC_LOADING} 88 | if not SimpleBleLoadLibrary() then begin 89 | ShowMessage('Failed to load library simpleble-c.dll'); 90 | exit; 91 | end; 92 | {$ENDIF} 93 | 94 | BleAdapterIsInitialized := false; 95 | BleIsEnabled := false; 96 | 97 | // look for BLE adapters 98 | if SimpleBleAdapterGetCount() = 0 then begin 99 | ShowMessage('No BLE adapter was found.'); 100 | Exit; 101 | end; 102 | 103 | // get a handle for the BLE BleAdapter 104 | BleAdapter := SimpleBleAdapterGetHandle(0); 105 | if BleAdapter = 0 then begin 106 | ShowMessage('Could not get handle for BLE adapter.'); 107 | Exit 108 | end; 109 | 110 | BleAdapterIsInitialized := true; 111 | 112 | // check if BLE is enabled 113 | BleIsEnabled := SimpleBleAdapterIsBluetoothEnabled(); 114 | 115 | end; 116 | 117 | 118 | { Convert 128 bit assigned service uuid to name } 119 | function BleAssignedServiceUuidToName(uuid: TSimpleBleUuid): String; 120 | var 121 | i: Integer; 122 | begin 123 | for i := 0 to Length(ServiceUuids)-1 do begin // check official service uuids 124 | if (CompareChar(uuid.Value, ServiceUuids[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0) or (CompareChar(uuid.Value[4], ServiceUuids[i].Uuid.Value, 4) = 0) then begin 125 | Result := ServiceUuids[i].Name; 126 | Exit; 127 | end; 128 | end; 129 | Result := ''; 130 | end; 131 | 132 | 133 | { Convert 128 bit assigned characteristic uuid to name } 134 | function BleAssignedCharacteristicUuidToName(uuid: TSimpleBleUuid): String; 135 | var 136 | i: Integer; 137 | begin 138 | for i := 0 to Length(CharacteristicUuids)-1 do begin 139 | if (CompareChar(uuid.Value, CharacteristicUuids[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0) or (CompareChar(uuid.Value[4], CharacteristicUuids[i].Uuid.Value, 4) = 0) then begin 140 | Result := CharacteristicUuids[i].Name; 141 | Exit; 142 | end; 143 | end; 144 | Result := ''; 145 | end; 146 | 147 | 148 | { Convert 128 bit assigned descriptor uuid to name } 149 | function BleAssignedDescriptorUuidToName(uuid: TSimpleBleUuid): String; 150 | var 151 | i: Integer; 152 | begin 153 | for i := 0 to Length(DescriptorUuids)-1 do begin 154 | if (CompareChar(uuid.Value, DescriptorUuids[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0) or (CompareChar(uuid.Value[4], DescriptorUuids[i].Uuid.Value, 4) = 0) then begin 155 | Result := DescriptorUuids[i].Name; 156 | Exit; 157 | end; 158 | end; 159 | Result := ''; 160 | end; 161 | 162 | 163 | { Convert 16 bit assigned company id to name } 164 | function BleAssignedCompanyIdToName(code: String): String; 165 | var 166 | i: Integer; 167 | begin 168 | for i := 0 to Length(CompanyIds)-1 do begin 169 | if CompanyIds[i].code = code then begin 170 | Result := CompanyIds[i].Name; 171 | Exit; 172 | end; 173 | end; 174 | Result := ''; 175 | end; 176 | 177 | 178 | { Convert 128 bit vsp service uuid to name } 179 | function BleVspServiceUuidToName(uuid: TSimpleBleUuid): String; 180 | var 181 | i: Integer; 182 | begin 183 | for i := 0 to Length(VspServiceUuids)-1 do begin // check proprietary uart service uuids 184 | if (CompareChar(uuid.Value, VspServiceUuids[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0) or (CompareChar(uuid.Value[4], VspServiceUuids[i].Uuid.Value, 4) = 0) then begin 185 | Result := VspServiceUuids[i].Name; 186 | Exit; 187 | end; 188 | end; 189 | Result := ''; 190 | end; 191 | 192 | 193 | { Convert 128 bit vsp characteristic uuid to name and function id } 194 | function BleVspCharacteristicUuidToName(uuid: TSimpleBleUuid): String; 195 | var 196 | i: Integer; 197 | begin 198 | for i := 0 to Length(VspCharacteristicUuids)-1 do begin // check proprietary uart service uuids 199 | if (CompareChar(uuid.Value, VspCharacteristicUuids[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0) or (CompareChar(uuid.Value[4], VspCharacteristicUuids[i].Uuid.Value, 4) = 0) then begin 200 | Result := VspCharacteristicUuids[i].Name; 201 | Exit; 202 | end; 203 | end; 204 | Result := ''; 205 | end; 206 | 207 | end. 208 | 209 | -------------------------------------------------------------------------------- /src/connect.lfm: -------------------------------------------------------------------------------- 1 | object DeviceForm: TDeviceForm 2 | Left = 2011 3 | Height = 320 4 | Top = 146 5 | Width = 433 6 | Caption = 'Connect Device...' 7 | ClientHeight = 320 8 | ClientWidth = 433 9 | Color = clWhite 10 | Constraints.MinHeight = 320 11 | OnChangeBounds = FormChangeBounds 12 | OnCloseQuery = FormCloseQuery 13 | LCLVersion = '3.99.0.0' 14 | object LabelMacAddress: TLabel 15 | Left = 8 16 | Height = 15 17 | Top = 40 18 | Width = 184 19 | Caption = 'MAC Address [AA:BB:CC:DD:EE:FF]' 20 | ParentFont = False 21 | end 22 | object TextBoxDeviceName: TEdit 23 | Left = 8 24 | Height = 28 25 | Top = 8 26 | Width = 248 27 | AutoSelect = False 28 | Font.Height = -15 29 | Font.Style = [fsBold] 30 | ParentFont = False 31 | ReadOnly = True 32 | TabOrder = 0 33 | Text = 'TextBoxDeviceName' 34 | end 35 | object ScrollBoxGatt: TScrollBox 36 | Left = 0 37 | Height = 256 38 | Top = 64 39 | Width = 433 40 | HorzScrollBar.Page = 1 41 | VertScrollBar.Increment = 32 42 | VertScrollBar.Page = 1 43 | Anchors = [akTop, akLeft, akRight, akBottom] 44 | Color = clMenu 45 | ParentBackground = False 46 | ParentColor = False 47 | TabOrder = 1 48 | end 49 | object ButtonDisconnect: TButton 50 | Left = 329 51 | Height = 26 52 | Top = 8 53 | Width = 96 54 | Anchors = [akTop, akRight] 55 | Caption = 'Disconnect' 56 | OnClick = ButtonDisconnectClick 57 | TabOrder = 2 58 | end 59 | object LabelMtuSize: TLabel 60 | Left = 369 61 | Height = 15 62 | Top = 40 63 | Width = 54 64 | Alignment = taRightJustify 65 | Anchors = [akTop, akRight] 66 | Caption = 'MTU Size: ' 67 | ParentFont = False 68 | end 69 | object Tick: TTimer 70 | Interval = 250 71 | OnTimer = TickTimer 72 | Left = 272 73 | Top = 8 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /src/connect.pas: -------------------------------------------------------------------------------- 1 | unit Connect; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, 9 | SimpleBle, Util, Ble, Scan, UartTerminal; 10 | 11 | type 12 | 13 | { TDeviceForm } 14 | TDeviceForm = class(TForm) 15 | ButtonDisconnect: TButton; 16 | LabelMtuSize: TLabel; 17 | ScrollBoxGatt: TScrollBox; 18 | TextBoxDeviceName: TEdit; 19 | LabelMacAddress: TLabel; 20 | Tick: TTimer; 21 | procedure ButtonDisconnectClick(Sender: TObject); 22 | procedure ButtonVspTerminalClick(Sender: TObject); 23 | procedure ButtonCharRead(Sender: TObject); 24 | procedure ButtonCharWriteCommand(Sender: TObject); 25 | procedure ButtonCharWriteRequest(Sender: TObject); 26 | procedure ButtonCharNotify(Sender: TObject); 27 | procedure ButtonCharIndicate(Sender: TObject); 28 | procedure CheckboxHexAsciiClick(Sender: TObject); 29 | procedure CharEditingDone(Sender: TObject); 30 | procedure FormChangeBounds(Sender: TObject); 31 | procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); 32 | procedure TickTimer(Sender: TObject); 33 | 34 | private 35 | 36 | public 37 | 38 | end; 39 | 40 | { Form elements to show device GATT structure } 41 | TDeviceFormElements = record 42 | Panel: array of TPanel; 43 | LabelServiceUuid: array of TLabel; 44 | ButtonVspTerminal: array of TButton; 45 | LabelCharacteristicUuid: array of array of TLabel; 46 | TextBoxCharacteristic: array of array of TEdit; 47 | CheckBoxHexAscii: array of array of TCheckBox; 48 | ToggleBoxCharProp: array of array of array [0..4] of TToggleBox; 49 | LabelDescriptorUuid: array of array of array of TLabel; 50 | TextBoxDescriptor: array of array of array of TEdit; 51 | end; 52 | 53 | { Functions for other units } 54 | procedure ConnectInit; 55 | procedure ConnectDevice(DevName: string; MacAddr: string; PerHandle: TSimpleBlePeripheral; restore: TButton); 56 | function ConnectHandleIsConnected(h: TSimpleBlePeripheral): Boolean; 57 | procedure DisconnectDevice(idx: Integer); 58 | procedure ConnectRestoreVspPanel(DeIdx: Integer; SvIdx: Integer); 59 | 60 | 61 | implementation 62 | 63 | {$R *.lfm} 64 | 65 | type 66 | { Data of device characteristic or descriptor (for local copy) } 67 | TCharDescData = record 68 | data: array of Byte; 69 | len: Integer; 70 | restore: Integer; 71 | end; 72 | 73 | { Device data we need during an active device connection } 74 | TBleConnectData = record 75 | ScnIdx: integer; 76 | PeripheralHandle: TSimpleBlePeripheral; 77 | DeviceName: String; 78 | MacAddress: String; 79 | ServicesCount: Integer; 80 | Services: array of TSimpleBleService; 81 | Characteristic: array of array of TCharDescData; 82 | Descriptor: array of array of array of TCharDescData; 83 | IsConnected: Boolean; 84 | IsPaired: Boolean; 85 | HasVspService: Boolean; 86 | HasVspWriteCmd: Boolean; 87 | HasVspWriteReq: Boolean; 88 | NotIndActiveCnt: array of Integer; 89 | AttMtuSize: Integer; 90 | RestoreButton: TButton; 91 | end; 92 | 93 | const 94 | DeviceFormPaddingVertical = 6; 95 | DeviceFormPaddingHorizontal = 8; 96 | DeviceFormPropPadding = 4; 97 | DeviceFormMaxHeight = 840; 98 | CanRead = 0; 99 | CanWriteRequest = 1; 100 | CanWriteCommand = 2; 101 | CanNotify = 3; 102 | CanIndicate = 4; 103 | {$IFDEF UNIX} 104 | FormElementsExtraHeight = 4; 105 | FormElementsExtraWidth = 8; 106 | {$ELSE} 107 | FormElementsExtraHeight = 0; 108 | FormElementsExtraWidth = 0; 109 | {$ENDIF} 110 | DevicePanelColor = $EED4C0; 111 | TextBoxIndicateCnt = 2; 112 | 113 | var 114 | BleConnectData: array of TBleConnectData; 115 | DeviceForm: array of TDeviceForm; 116 | DeviceFormElements: array of TDeviceFormElements; 117 | SuppressUnsubscribe: Boolean; 118 | 119 | { initialize connect unit } 120 | procedure ConnectInit(); 121 | begin 122 | 123 | end; 124 | 125 | 126 | { Checks if the a peripheral handle is still being used for an active connection } 127 | function ConnectHandleIsConnected(h: TSimpleBlePeripheral): Boolean; 128 | var 129 | i: Integer; 130 | begin 131 | Result := false; 132 | i := 0; 133 | while i < Length(BleConnectData) do begin 134 | if BleConnectData[i].PeripheralHandle = h then begin 135 | Result := true; 136 | Exit; 137 | end; 138 | Inc(i); 139 | end; 140 | end; 141 | 142 | 143 | { Callback function on disconnect of device } 144 | procedure DeviceOnDisconnect(Peripheral: TSimpleBlePeripheral; UserData: PPointer); 145 | var 146 | idx: Integer; 147 | begin 148 | idx := 0; 149 | while idx < Length(BleConnectData) do begin 150 | if BleConnectData[idx].IsConnected then begin 151 | if BleConnectData[idx].PeripheralHandle = Peripheral then begin 152 | UtilLog('Device "' + BleConnectData[idx].DeviceName + '" [' + BleConnectData[idx].MacAddress + '] disconnected unexpectedly.'); 153 | ShowMessage('Device [' + BleConnectData[idx].MacAddress + '] disconnected unexpectedly.'); 154 | DeviceForm[idx].Close; // this automatically forces TDeviceForm.FormClose() 155 | end; 156 | end; 157 | Inc(idx); 158 | end; 159 | end; 160 | 161 | 162 | { connect to device } 163 | procedure ConnectDevice(DevName: string; MacAddr: string; PerHandle: TSimpleBlePeripheral; restore: TButton); 164 | var 165 | i, j: Integer; 166 | SvIdx, ChIdx, DeIdx: Integer; 167 | s, n: String; 168 | LabelNoConnect: TLabel; 169 | NextElementVertical, NextPanelVertical, NextButtonCoord: Integer; 170 | Buffer: array of Byte; 171 | ChData: PByte; 172 | ChLen: NativeUInt; 173 | FlagVspChar: Boolean; 174 | begin 175 | SetLength(Buffer, CharDescMaxLength); 176 | ChData := PByte(Buffer); 177 | 178 | // increment devices and increase some arrays 179 | i := Length(BleConnectData); 180 | SetLength(BleConnectData, i+1); 181 | SetLength(DeviceForm, i+1); 182 | SetLength(DeviceFormElements, i+1); 183 | 184 | // store device data locally 185 | BleConnectData[i].ScnIdx := restore.Tag; 186 | BleConnectData[i].DeviceName := DevName; 187 | BleConnectData[i].MacAddress := MacAddr; 188 | BleConnectData[i].PeripheralHandle := PerHandle; 189 | BleConnectData[i].RestoreButton := restore; // this points to the connect button on the scan form we need to re-enable after disconnect 190 | 191 | // register on disconnect callback function 192 | SimpleBlePeripheralSetCallbackOnDisconnected(BleConnectData[i].PeripheralHandle, @DeviceOnDisconnect, Nil); 193 | 194 | // compose form for the new device 195 | UtilLog('Connecting...'); 196 | Application.CreateForm(TDeviceForm, DeviceForm[i]); 197 | DeviceForm[i].Tag := i; 198 | DeviceForm[i].ButtonDisconnect.Tag := i; 199 | DeviceForm[i].Top := UtilGetNextFormTop(); 200 | DeviceForm[i].Left := UtilGetNextFormLeft(DeviceForm[i]); 201 | if BleConnectData[i].DeviceName = '' then begin 202 | DeviceForm[i].Caption := '"" [' + UpperCase(BleConnectData[i].MacAddress) + '] - Connecting...'; 203 | DeviceForm[i].TextBoxDeviceName.Caption := ''; 204 | DeviceForm[i].TextBoxDeviceName.Enabled := false; 205 | end else begin 206 | DeviceForm[i].Caption := '"' + BleConnectData[i].DeviceName + '" [' + UpperCase(BleConnectData[i].MacAddress) + '] - Connecting...'; 207 | DeviceForm[i].TextBoxDeviceName.Caption := BleConnectData[i].DeviceName; 208 | end; 209 | DeviceForm[i].LabelMacAddress.Caption := 'MAC Address [' + UpperCase(BleConnectData[i].MacAddress) + ']'; 210 | DeviceForm[i].LabelMtuSize.Caption := ''; 211 | DeviceForm[i].Show; 212 | UtilSetNextFormTop(DeviceForm[i]); 213 | UtilSetNextFormLeft(DeviceForm[i]); 214 | 215 | Application.ProcessMessages; // shows the form and elements before connecting because connecting blocks 216 | 217 | // try to connect to device 218 | if SimpleBlePeripheralConnect(BleConnectData[i].PeripheralHandle) = SIMPLEBLE_FAILURE then begin 219 | UtilLog('Failed to connect to "' + BleConnectData[i].DeviceName + '" [' + UpperCase(BleConnectData[i].MacAddress) + ']'); 220 | DeviceForm[i].Caption := '"' + BleConnectData[i].DeviceName + '" [' + UpperCase(BleConnectData[i].MacAddress) + '] - Could not connect.'; 221 | LabelNoConnect := TLabel.Create(DeviceForm[i]); 222 | LabelNoConnect.Parent := DeviceForm[i].ScrollBoxGatt; 223 | LabelNoConnect.Top := DeviceForm[i].TextBoxDeviceName.Top + DeviceForm[i].TextBoxDeviceName.Height + 4*DeviceFormPaddingVertical; 224 | LabelNoConnect.Left := DeviceFormPaddingHorizontal; 225 | LabelNoConnect.Font.Size := 12; 226 | LabelNoConnect.Font.Color := clRed; 227 | LabelNoConnect.Font.Style := [fsBold]; 228 | LabelNoConnect.Caption := 'Failed to connect to device.'; 229 | Exit; 230 | end; 231 | 232 | // connect was successful 233 | BleConnectData[i].IsConnected := true; 234 | UtilLog('Connected to "' + BleConnectData[i].DeviceName + '" [' + UpperCase(BleConnectData[i].MacAddress) + ']'); 235 | DeviceForm[i].Caption := '"' + BleConnectData[i].DeviceName + '" [' + UpperCase(BleConnectData[i].MacAddress) + '] - Connected.'; 236 | 237 | BleConnectData[i].AttMtuSize := SimpleBlePeripheralMtu(BleConnectData[i].PeripheralHandle); 238 | UtilLog('MTU Size: ' + IntToStr(BleConnectData[i].AttMtuSize+3)); 239 | DeviceForm[i].LabelMtuSize.Caption := 'MTU Size: ' + IntToStr(BleConnectData[i].AttMtuSize+3); 240 | 241 | Application.ProcessMessages; // shows the form and elements before connecting because connecting blocks 242 | 243 | // check for GATT services, characteristics and descriptors 244 | UtilLog('Reading GATT table:'); 245 | if SimpleBlePeripheralServicesCount(BleConnectData[i].PeripheralHandle) > 0 then begin 246 | BleConnectData[i].ServicesCount := SimpleBlePeripheralServicesCount(BleConnectData[i].PeripheralHandle); 247 | SetLength(BleConnectData[i].Services, BleConnectData[i].ServicesCount); 248 | SetLength(BleConnectData[i].Characteristic, BleConnectData[i].ServicesCount); 249 | SetLength(BleConnectData[i].Descriptor, BleConnectData[i].ServicesCount); 250 | SetLength(BleConnectData[i].NotIndActiveCnt, BleConnectData[i].ServicesCount); 251 | SetLength(DeviceFormElements[i].Panel, BleConnectData[i].ServicesCount); 252 | SetLength(DeviceFormElements[i].LabelServiceUuid, BleConnectData[i].ServicesCount); 253 | SetLength(DeviceFormElements[i].ButtonVspTerminal, BleConnectData[i].ServicesCount); 254 | SetLength(DeviceFormElements[i].LabelCharacteristicUuid, BleConnectData[i].ServicesCount); 255 | SetLength(DeviceFormElements[i].LabelDescriptorUuid, BleConnectData[i].ServicesCount); 256 | SetLength(DeviceFormElements[i].TextBoxCharacteristic, BleConnectData[i].ServicesCount); 257 | SetLength(DeviceFormElements[i].ToggleBoxCharProp, BleConnectData[i].ServicesCount); 258 | SetLength(DeviceFormElements[i].TextBoxDescriptor, BleConnectData[i].ServicesCount); 259 | SetLength(DeviceFormElements[i].CheckBoxHexAscii, BleConnectData[i].ServicesCount); 260 | NextPanelVertical := DeviceFormPaddingVertical; 261 | 262 | for SvIdx := 0 to BleConnectData[i].ServicesCount-1 do begin 263 | 264 | // create panel for service characteristics and descriptors 265 | DeviceFormElements[i].Panel[SvIdx] := TPanel.Create(DeviceForm[i]); 266 | DeviceFormElements[i].Panel[SvIdx].Parent := DeviceForm[i].ScrollBoxGatt; 267 | DeviceFormElements[i].Panel[SvIdx].Top := NextPanelVertical; 268 | DeviceFormElements[i].Panel[SvIdx].Left := DeviceFormPaddingHorizontal; 269 | DeviceFormElements[i].Panel[SvIdx].Width := DeviceForm[i].ScrollBoxGatt.Width - 4*DeviceFormPaddingHorizontal; 270 | DeviceFormElements[i].Panel[SvIdx].Color := TColor(DevicePanelColor); 271 | NextElementVertical := DeviceFormPaddingVertical; 272 | SimpleBlePeripheralServicesGet(BleConnectData[i].PeripheralHandle, SvIdx, BleConnectData[i].Services[SvIdx]); 273 | SetString(s, BleConnectData[i].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 274 | 275 | // look for assigned service numbers or proprietary vsp services 276 | n := BleVspServiceUuidToName(BleConnectData[i].Services[SvIdx].Uuid); 277 | if n = '' then begin 278 | n := BleAssignedServiceUuidToName(BleConnectData[i].Services[SvIdx].Uuid); 279 | if n = '' then begin // neither assigned service nor vsp service 280 | UtilLog(' SV: ' + s); 281 | end else begin // assigned service 282 | UtilLog(' SV: ' + s + ' (' + n + ')'); 283 | BleConnectData[i].HasVspService := false; 284 | end; 285 | end else begin // vsp service 286 | UtilLog(' SV: ' + s + ' (' + n + ')'); 287 | BleConnectData[i].HasVspService := true; 288 | end; 289 | 290 | // show service uuid 291 | NextElementVertical := 2*DeviceFormPaddingVertical; 292 | DeviceFormElements[i].LabelServiceUuid[SvIdx] := TLabel.Create(DeviceForm[i]); 293 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 294 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Top := Trunc(NextElementVertical/2); 295 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Left := DeviceFormPaddingHorizontal; 296 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Font.Size := 11; 297 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Font.Style := [fsBold]; 298 | if n = '' then 299 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Caption := UpperCase(s) 300 | else begin 301 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Caption := n; 302 | DeviceFormElements[i].LabelServiceUuid[SvIdx].ShowHint := true; 303 | DeviceFormElements[i].LabelServiceUuid[SvIdx].Hint := s; 304 | end; 305 | if BleConnectData[i].HasVspService then begin 306 | DeviceFormElements[i].ButtonVspTerminal[SvIdx] := TButton.Create(DeviceForm[i]); 307 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 308 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv); 309 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].Caption := 'VSP Terminal'; 310 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].AutoSize := true; 311 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].Top := DeviceFormElements[i].LabelServiceUuid[SvIdx].Top; 312 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].Left := DeviceFormElements[i].Panel[SvIdx].Width - DeviceFormElements[i].ButtonVspTerminal[SvIdx].Width - DeviceFormPaddingHorizontal; 313 | DeviceFormElements[i].ButtonVspTerminal[SvIdx].OnClick := @DeviceForm[i].ButtonVspTerminalClick; 314 | end; 315 | NextElementVertical := DeviceFormElements[i].LabelServiceUuid[SvIdx].Top + DeviceFormElements[i].LabelServiceUuid[SvIdx].Height + DeviceFormPaddingVertical; 316 | 317 | if BleConnectData[i].Services[SvIdx].CharacteristicCount > 0 then begin 318 | SetLength(BleConnectData[i].Characteristic[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 319 | SetLength(BleConnectData[i].Descriptor[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 320 | SetLength(DeviceFormElements[i].LabelCharacteristicUuid[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 321 | SetLength(DeviceFormElements[i].LabelDescriptorUuid[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 322 | SetLength(DeviceFormElements[i].TextBoxCharacteristic[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 323 | SetLength(DeviceFormElements[i].ToggleBoxCharProp[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 324 | SetLength(DeviceFormElements[i].TextBoxDescriptor[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 325 | SetLength(DeviceFormElements[i].CheckBoxHexAscii[SvIdx], BleConnectData[i].Services[SvIdx].CharacteristicCount); 326 | 327 | for ChIdx := 0 to BleConnectData[i].Services[SvIdx].CharacteristicCount-1 do begin 328 | SetLength(BleConnectData[i].Characteristic[SvIdx][ChIdx].data, CharDescMaxLength); 329 | SetString(s, BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 330 | n := BleVspCharacteristicUuidToName(BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Uuid); 331 | FlagVspChar := false; 332 | if n = '' then begin 333 | n := BleAssignedCharacteristicUuidToName(BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Uuid); 334 | if n = '' then // neither assigned characteristic nor vsp characteristic 335 | UtilLog(' CH: ' + s) 336 | else // assigned characteristic 337 | UtilLog(' CH: ' + s + ' (' + n + ')'); 338 | end else begin // vsp characteristic 339 | UtilLog(' CH: ' + s + ' (' + n + ')'); 340 | FlagVspChar := true; 341 | end; 342 | 343 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx] := TLabel.Create(DeviceForm[i]); 344 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 345 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Top := NextElementVertical; 346 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Left := 3*DeviceFormPaddingHorizontal; 347 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Font.Size := 10; 348 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Caption := ' '; 349 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].AutoSize := false; 350 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Width := Round(DeviceFormElements[i].Panel[SvIdx].Width * 2 / 3); 351 | //DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Height := 18; 352 | if n = '' then 353 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Caption := UpperCase(s) 354 | else begin 355 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Caption := n; 356 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].ShowHint := true; 357 | DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Hint := s; 358 | end; 359 | NextElementVertical := DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Top + DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Height; 360 | 361 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx] := TEdit.Create(DeviceForm[i]); 362 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 363 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].ReadOnly := true; 364 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 365 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top := NextElementVertical; 366 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Left := 3*DeviceFormPaddingHorizontal; 367 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Caption := ' '; 368 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].AutoSize := false; 369 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Width := Round(DeviceFormElements[i].Panel[SvIdx].Width * 2 / 3); 370 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Color := clForm; 371 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Font.Size := 10; 372 | NextElementVertical := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top + DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Height + DeviceFormPaddingVertical; 373 | 374 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx] := TCheckBox.Create(DeviceForm[i]); 375 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 376 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 377 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].Caption := 'ASCII'; 378 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].Top := DeviceFormElements[i].LabelCharacteristicUuid[SvIdx][ChIdx].Top; 379 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].Left := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Left + DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Width + DeviceFormPaddingHorizontal; 380 | DeviceFormElements[i].CheckBoxHexAscii[SvIdx][ChIdx].OnClick := @DeviceForm[i].CheckboxHexAsciiClick; 381 | 382 | // check characteristic properties and add buttons for each 383 | NextButtonCoord := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Left + DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Width + DeviceFormPaddingHorizontal; 384 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].CanRead then begin 385 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead] := TToggleBox.Create(DeviceForm[i]); 386 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Parent := DeviceFormElements[i].Panel[SvIdx]; 387 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 388 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Top := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top; 389 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Left := NextButtonCoord; 390 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Font.Size := 8; 391 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].Caption := 'RD'; 392 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].AutoSize := true; 393 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanRead].OnChange := @DeviceForm[i].ButtonCharRead; 394 | NextButtonCoord := NextButtonCoord + 4*DeviceFormPaddingHorizontal + FormElementsExtraWidth + DeviceFormPropPadding; 395 | 396 | // read characteristic value, store into device record and display in text box 397 | SimpleBlePeripheralRead(BleConnectData[i].PeripheralHandle, BleConnectData[i].Services[SvIdx].Uuid, 398 | BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Uuid, 399 | ChData, ChLen); 400 | for j := 0 to ChLen-1 do 401 | BleConnectData[i].Characteristic[SvIdx][ChIdx].data[j] := ChData[j]; 402 | BleConnectData[i].Characteristic[SvIdx][ChIdx].len := ChLen; 403 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToHex(BleConnectData[i].Characteristic[SvIdx][ChIdx].data, BleConnectData[i].Characteristic[SvIdx][ChIdx].len); 404 | end; 405 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].CanWriteCommand then begin 406 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].ReadOnly := false; 407 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand] := TToggleBox.Create(DeviceForm[i]); 408 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Parent := DeviceFormElements[i].Panel[SvIdx]; 409 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 410 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Top := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top; 411 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Left := NextButtonCoord; 412 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Font.Size := 8; 413 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].Caption := 'WRc'; 414 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].AutoSize := true; 415 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteCommand].OnChange := @DeviceForm[i].ButtonCharWriteCommand; 416 | NextButtonCoord := NextButtonCoord + 4*DeviceFormPaddingHorizontal + FormElementsExtraWidth + DeviceFormPropPadding; 417 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].OnEditingDone := @DeviceForm[i].CharEditingDone; 418 | if FlagVspChar then 419 | BleConnectData[i].HasVspWriteCmd := True; 420 | end; 421 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].CanWriteRequest then begin 422 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].ReadOnly := false; 423 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest] := TToggleBox.Create(DeviceForm[i]); 424 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Parent := DeviceFormElements[i].Panel[SvIdx]; 425 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 426 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Top := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top; 427 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Left := NextButtonCoord; 428 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Font.Size := 8; 429 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].Caption := 'WRr'; 430 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].AutoSize := true; 431 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanWriteRequest].OnChange := @DeviceForm[i].ButtonCharWriteRequest; 432 | NextButtonCoord := NextButtonCoord + 4*DeviceFormPaddingHorizontal + FormElementsExtraWidth + DeviceFormPropPadding; 433 | DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].OnEditingDone := @DeviceForm[i].CharEditingDone; 434 | if FlagVspChar then 435 | BleConnectData[i].HasVspWriteReq := True; 436 | end; 437 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].CanNotify then begin 438 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify] := TToggleBox.Create(DeviceForm[i]); 439 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Parent := DeviceFormElements[i].Panel[SvIdx]; 440 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 441 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Top := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top; 442 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Left := NextButtonCoord; 443 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Font.Size := 8; 444 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].Caption := 'NOT'; 445 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].AutoSize := true; 446 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanNotify].OnChange := @DeviceForm[i].ButtonCharNotify; 447 | NextButtonCoord := NextButtonCoord + 4*DeviceFormPaddingHorizontal + FormElementsExtraWidth + DeviceFormPropPadding; 448 | end; 449 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].CanIndicate then begin 450 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate] := TToggleBox.Create(DeviceForm[i]); 451 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Parent := DeviceFormElements[i].Panel[SvIdx]; 452 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Tag := (i shl TagPosDev) or (SvIdx shl TagPosSrv) or (ChIdx shl TagPosChr); 453 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Top := DeviceFormElements[i].TextBoxCharacteristic[SvIdx][ChIdx].Top; 454 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Left := NextButtonCoord; 455 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Font.Size := 8; 456 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].Caption := 'IND'; 457 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].AutoSize := true; 458 | DeviceFormElements[i].ToggleBoxCharProp[SvIdx][ChIdx][CanIndicate].OnChange := @DeviceForm[i].ButtonCharIndicate; 459 | NextButtonCoord := NextButtonCoord + 4*DeviceFormPaddingHorizontal + FormElementsExtraWidth + DeviceFormPropPadding; 460 | end; 461 | 462 | if BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].DescriptorCount > 0 then begin 463 | SetLength(BleConnectData[i].Descriptor[SvIdx][ChIdx], BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].DescriptorCount); 464 | SetLength(DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx], BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].DescriptorCount); 465 | SetLength(DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx], BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].DescriptorCount); 466 | NextElementVertical := NextElementVertical - DeviceFormPaddingVertical div 2; 467 | 468 | for DeIdx := 0 to BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].DescriptorCount-1 do begin 469 | SetString(s, BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Descriptors[DeIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 470 | n := BleAssignedDescriptorUuidToName(BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Descriptors[DeIdx].Uuid); 471 | if n = '' then 472 | UtilLog(' DE: ' + s) 473 | else 474 | UtilLog(' DE: ' + s + ' (' + n + ')'); 475 | 476 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx] := TLabel.Create(DeviceForm[i]); 477 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 478 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Top := NextElementVertical; 479 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Left := 5*DeviceFormPaddingHorizontal; 480 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Caption := 'LabelDescriptorUuid'; 481 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Width := Round(DeviceFormElements[i].Panel[SvIdx].Width * 2 / 3); 482 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].AutoSize := false; 483 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Font.Size := 10; 484 | if n = '' then 485 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Caption := UpperCase(s) 486 | else begin 487 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Caption := n; 488 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].ShowHint := true; 489 | DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Hint := s; 490 | end; 491 | NextElementVertical := DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Top + DeviceFormElements[i].LabelDescriptorUuid[SvIdx][ChIdx][DeIdx].Height; 492 | 493 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx] := TEdit.Create(DeviceForm[i]); 494 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Parent := DeviceFormElements[i].Panel[SvIdx]; 495 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].ReadOnly := true; 496 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Top := NextElementVertical; 497 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Left := 5*DeviceFormPaddingHorizontal; 498 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Caption := 'TextBoxDescriptor'; 499 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].AutoSize := false; 500 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Width := 56; 501 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Color := clForm; 502 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Font.Size := 10; 503 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].MaxLength := 4; 504 | NextElementVertical := DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Top + DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Height + DeviceFormPaddingVertical div 2; 505 | 506 | // read descriptor value, store in device data record and display in text box 507 | SetLength(BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].data, CharDescMaxLength); 508 | ChData := PByte(BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].data); 509 | SimpleBlePeripheralReadDescriptor(BleConnectData[i].PeripheralHandle, BleConnectData[i].Services[SvIdx].Uuid, 510 | BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Uuid, 511 | BleConnectData[i].Services[SvIdx].Characteristics[ChIdx].Descriptors[DeIdx].Uuid, 512 | ChData, ChLen); 513 | for j := 0 to ChLen-1 do 514 | BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].data[j] := ChData[j]; 515 | BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].len := ChLen; 516 | DeviceFormElements[i].TextBoxDescriptor[SvIdx][ChIdx][DeIdx].Caption := UtilDataToHex(BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].data, BleConnectData[i].Descriptor[SvIdx][ChIdx][DeIdx].len); 517 | end; 518 | end; 519 | end; 520 | end; 521 | DeviceFormElements[i].Panel[SvIdx].Height := NextElementVertical + DeviceFormPaddingVertical; 522 | NextPanelVertical := DeviceFormElements[i].Panel[SvIdx].Top + NextElementVertical + 2*DeviceFormPaddingVertical; 523 | end; 524 | end; 525 | 526 | DeviceForm[i].Height := NextPanelVertical + 12*DeviceFormPaddingVertical; 527 | if DeviceForm[i].Height > DeviceFormMaxHeight then 528 | DeviceForm[i].Height := DeviceFormMaxHeight; 529 | DeviceForm[i].Constraints.MaxHeight := NextPanelVertical + 12*DeviceFormPaddingVertical; 530 | DeviceForm[i].Constraints.MaxWidth := DeviceForm[i].Width; 531 | DeviceForm[i].Constraints.MinWidth := DeviceForm[i].Width; 532 | end; 533 | 534 | 535 | { Disconnect from device } 536 | procedure DisconnectDevice(idx: Integer); 537 | begin 538 | if BleConnectData[idx].IsConnected then begin 539 | BleConnectData[idx].IsConnected := false; 540 | UtilLog('Disconnecting...'); 541 | DeviceForm[idx].Caption := BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + '] - Disconnecting...'; 542 | Application.ProcessMessages; 543 | 544 | // close open vsp terminal 545 | while UartTerminalIsActive(BleConnectData[idx].PeripheralHandle) do 546 | UartTerminalStop(BleConnectData[idx].PeripheralHandle); 547 | 548 | if SimpleBlePeripheralDisconnect(BleConnectData[idx].PeripheralHandle) = SIMPLEBLE_FAILURE then begin 549 | UtilLog('Failed to disconnect from "' + BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + ']'); 550 | DeviceForm[idx].Caption := BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + '] - Failed to disconnect.'; 551 | end else begin 552 | UtilLog('Disconnected from "' + BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + ']'); 553 | DeviceForm[idx].Caption := BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + '] - Disconnected.'; 554 | end; 555 | end; 556 | end; 557 | 558 | 559 | { close device form and also disconnect from device } 560 | procedure TDeviceForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 561 | var 562 | idx, i: Integer; 563 | begin 564 | idx := TForm(Sender).Tag; 565 | 566 | DisconnectDevice(idx); 567 | BleConnectData[idx].RestoreButton.Enabled := true; 568 | UtilSetNextFormLeft(DeviceForm[idx], true); 569 | for i := Length(BleConnectData)-1 downto 0 do begin 570 | if BleConnectData[i].IsConnected = false then 571 | Delete(BleConnectData, i, 1) 572 | else 573 | break; 574 | end; 575 | end; 576 | 577 | 578 | { Disconnect from device } 579 | procedure TDeviceForm.ButtonDisconnectClick(Sender: TObject); 580 | var 581 | idx: Integer; 582 | begin 583 | idx := TForm(Sender).Tag; 584 | DeviceForm[idx].Close; // this automatically forces TDeviceForm.FormCloseQuery() 585 | end; 586 | 587 | 588 | { Open vsp terminal } 589 | procedure TDeviceForm.ButtonVspTerminalClick(Sender: TObject); 590 | var 591 | i, DeIdx, SvIdx: Integer; 592 | s: string; 593 | begin 594 | i := TCheckBox(Sender).Tag; 595 | DeIdx := (i shr TagPosDev) and $ff; 596 | SvIdx := (i shr TagPosSrv) and $ff; 597 | 598 | DeviceFormElements[DeIdx].Panel[SvIdx].Enabled := false; 599 | 600 | UartTerminalStart(BleConnectData[DeIdx].PeripheralHandle, 601 | BleConnectData[DeIdx].DeviceName, 602 | BleConnectData[DeIdx].MacAddress, 603 | BleConnectData[DeIdx].Services[SvIdx].Uuid, 604 | BleConnectData[DeIdx].HasVspWriteCmd, 605 | BleConnectData[DeIdx].HasVspWriteReq, 606 | DeviceFormElements[DeIdx].Panel[SvIdx]); 607 | end; 608 | 609 | 610 | { Re-enable the service panel in the connect window after closing vsp terminal } 611 | procedure ConnectRestoreVspPanel(DeIdx: Integer; SvIdx: Integer); 612 | begin 613 | DeviceFormElements[DeIdx].Panel[SvIdx].Enabled := true; 614 | end; 615 | 616 | 617 | { Checkbox Hex/ASCII clicked } 618 | procedure TDeviceForm.CheckboxHexAsciiClick(Sender: TObject); 619 | var 620 | i, DeIdx, SvIdx, ChIdx: Integer; 621 | begin 622 | // from button tag property we get which device, service and characteristic we are about to change 623 | i := TCheckBox(Sender).Tag; 624 | DeIdx := (i shr TagPosDev) and $ff; 625 | SvIdx := (i shr TagPosSrv) and $ff; 626 | ChIdx := (i shr TagPosChr) and $ff; 627 | if TCheckBox(Sender).Checked then // show as ascii 628 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToAscii(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len) 629 | else // show as hex 630 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len); 631 | end; 632 | 633 | 634 | { Read characteristic button clicked } 635 | procedure TDeviceForm.ButtonCharRead(Sender: TObject); 636 | var 637 | i, DeIdx, SvIdx, ChIdx: Integer; 638 | sSv, sCh: String; 639 | Buffer: array of Byte; 640 | ChData: PByte; 641 | ChLen: NativeUInt; 642 | begin 643 | if TToggleBox(Sender).State = cbUnchecked then 644 | Exit; 645 | 646 | SetLength(Buffer, CharDescMaxLength); 647 | ChData := PByte(Buffer); 648 | 649 | // from button tag property we get which device, service and characteristic we are about to change 650 | i := TToggleBox(Sender).Tag; 651 | DeIdx := (i shr TagPosDev) and $ff; 652 | SvIdx := (i shr TagPosSrv) and $ff; 653 | ChIdx := (i shr TagPosChr) and $ff; 654 | 655 | SetString(sSv, BleConnectData[DeIdx].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 656 | SetString(sCh, BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 657 | 658 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := TColor($EED4C0); 659 | TToggleBox(Sender).Font.Style := [fsBold]; 660 | Application.ProcessMessages; 661 | 662 | if(SimpleBlePeripheralRead(BleConnectData[DeIdx].PeripheralHandle, BleConnectData[DeIdx].Services[SvIdx].Uuid, 663 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid, 664 | ChData, ChLen) = SIMPLEBLE_FAILURE) then 665 | begin 666 | UtilLog('Read failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 667 | ShowMessage('Read failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 668 | end else begin 669 | for i := 0 to ChLen-1 do 670 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i] := ChData[i]; 671 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len := ChLen; 672 | UtilLog('Read: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4) + 673 | ' Data=' + UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len)); 674 | if DeviceFormElements[DeIdx].CheckBoxHexAscii[SvIdx][ChIdx].State = cbChecked then // show data as ascii string 675 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToAscii(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len) 676 | else // show data as hex string 677 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len); 678 | end; 679 | 680 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := clForm; 681 | TToggleBox(Sender).Font.Style := []; 682 | TToggleBox(Sender).State := cbUnchecked; 683 | end; 684 | 685 | 686 | { Write Command characteristic button clicked } 687 | procedure TDeviceForm.ButtonCharWriteCommand(Sender: TObject); 688 | var 689 | i, DeIdx, SvIdx, ChIdx: Integer; 690 | sSv, sCh: String; 691 | Buffer: array of Byte; 692 | ChData: PByte; 693 | ChLen: NativeUInt; 694 | begin 695 | if TToggleBox(Sender).State = cbUnchecked then 696 | Exit; 697 | 698 | SetLength(Buffer, CharDescMaxLength); 699 | 700 | // from button tag property we get which device, service and characteristic we are about to change 701 | i := TToggleBox(Sender).Tag; 702 | DeIdx := (i shr TagPosDev) and $ff; 703 | SvIdx := (i shr TagPosSrv) and $ff; 704 | ChIdx := (i shr TagPosChr) and $ff; 705 | 706 | SetString(sSv, BleConnectData[DeIdx].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 707 | SetString(sCh, BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 708 | 709 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := TColor($EED4C0); 710 | TToggleBox(Sender).Font.Style := [fsBold]; 711 | Application.ProcessMessages; 712 | 713 | ChLen := BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len; 714 | for i := 0 to ChLen-1 do 715 | Buffer[i] := BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i]; 716 | 717 | ChData := PByte(Buffer); 718 | if(SimpleBlePeripheralWriteCommand(BleConnectData[DeIdx].PeripheralHandle, BleConnectData[DeIdx].Services[SvIdx].Uuid, 719 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid, 720 | ChData, ChLen) = SIMPLEBLE_FAILURE) then 721 | begin 722 | UtilLog('WriteCommand failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 723 | ShowMessage('WriteCommand failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 724 | end else 725 | UtilLog('WriteCommand: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4) + 726 | ' Data=' + UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len)); 727 | 728 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := clForm; 729 | TToggleBox(Sender).Font.Style := []; 730 | TToggleBox(Sender).State := cbUnchecked; 731 | end; 732 | 733 | 734 | { Write Request characteristic button clicked } 735 | procedure TDeviceForm.ButtonCharWriteRequest(Sender: TObject); 736 | var 737 | i, DeIdx, SvIdx, ChIdx: Integer; 738 | sSv, sCh: String; 739 | Buffer: array of Byte; 740 | ChData: PByte; 741 | ChLen: NativeUInt; 742 | begin 743 | if TToggleBox(Sender).State = cbUnchecked then 744 | Exit; 745 | 746 | SetLength(Buffer, CharDescMaxLength); 747 | 748 | // from button tag property we get which device, service and characteristic we are about to change 749 | i := TToggleBox(Sender).Tag; 750 | DeIdx := (i shr TagPosDev) and $ff; 751 | SvIdx := (i shr TagPosSrv) and $ff; 752 | ChIdx := (i shr TagPosChr) and $ff; 753 | 754 | SetString(sSv, BleConnectData[DeIdx].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 755 | SetString(sCh, BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 756 | 757 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := TColor($EED4C0); 758 | TToggleBox(Sender).Font.Style := [fsBold]; 759 | Application.ProcessMessages; 760 | 761 | ChLen := BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len; 762 | for i := 0 to ChLen-1 do 763 | Buffer[i] := BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i]; 764 | 765 | ChData := PByte(Buffer); 766 | if(SimpleBlePeripheralWriteRequest(BleConnectData[DeIdx].PeripheralHandle, BleConnectData[DeIdx].Services[SvIdx].Uuid, 767 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid, 768 | ChData, ChLen) = SIMPLEBLE_FAILURE) then 769 | begin 770 | UtilLog('WriteRequest failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 771 | ShowMessage('WriteRequest failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 772 | end else 773 | UtilLog('WriteRequest: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4) + 774 | ' Data=' + UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len)); 775 | 776 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := clForm; 777 | TToggleBox(Sender).Font.Style := []; 778 | TToggleBox(Sender).State := cbUnchecked; 779 | end; 780 | 781 | 782 | { Callback function on notification from peripheral } 783 | procedure PeripheralOnNotify(SvUuid: TSimpleBleUuid; ChUuid: TSimpleBleUuid; Data: PByte; Len: NativeUInt; UserData: PPointer); 784 | var 785 | i, j, k, DeIdx, SvIdx, ChIdx: Integer; 786 | s, sSv, sCh: String; 787 | f: Boolean; 788 | begin 789 | SetString(sSv, SvUuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 790 | SetString(sCh, ChUuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 791 | 792 | // get device, service and characteristic indexes from service and characteristic uuids 793 | f := false; 794 | for i := 0 to Length(BleConnectData)-1 do begin 795 | for j := 0 to BleConnectData[i].ServicesCount-1 do begin 796 | SetString(s, BleConnectData[i].Services[j].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 797 | if s = sSv then 798 | for k := 0 to BleConnectData[i].Services[j].CharacteristicCount-1 do begin 799 | SetString(s, BleConnectData[i].Services[j].Characteristics[k].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 800 | if s = sCh then begin 801 | DeIdx := i; 802 | SvIdx := j; 803 | ChIdx := k; 804 | f := true; 805 | break; 806 | end; 807 | end; 808 | if f then break; 809 | end; 810 | if f then break; 811 | end; 812 | 813 | if not f then begin 814 | UtilLog('Notification: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" Unknown UUIDs.'); 815 | Exit; 816 | end; 817 | 818 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].restore := TextBoxIndicateCnt; 819 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := TColor($EED4C0); 820 | Application.ProcessMessages; 821 | 822 | 823 | for i := 0 to Len-1 do 824 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i] := Data[i]; 825 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len := Len; 826 | 827 | if DeviceFormElements[DeIdx].CheckBoxHexAscii[SvIdx][ChIdx].State = cbChecked then // show data as ascii string 828 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToAscii(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len) 829 | else // show data as hex string 830 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len); 831 | 832 | UtilLog('Notification: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4) + 833 | ' Data=' + UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len)); 834 | end; 835 | 836 | 837 | { Notify characteristic button clicked } 838 | procedure TDeviceForm.ButtonCharNotify(Sender: TObject); 839 | var 840 | i, DeIdx, SvIdx, ChIdx: Integer; 841 | sSv, sCh: String; 842 | begin 843 | // from button tag property we get which device, service and characteristic we are about to change 844 | i := tButton(Sender).Tag; 845 | DeIdx := (i shr TagPosDev) and $ff; 846 | SvIdx := (i shr TagPosSrv) and $ff; 847 | ChIdx := (i shr TagPosChr) and $ff; 848 | 849 | SetString(sSv, BleConnectData[DeIdx].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 850 | SetString(sCh, BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 851 | 852 | if TToggleBox(Sender).State = cbChecked then begin // subscribe 853 | if(SimpleBlePeripheralNotify(BleConnectData[DeIdx].PeripheralHandle, 854 | BleConnectData[DeIdx].Services[SvIdx].Uuid, 855 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid, 856 | @PeripheralOnNotify, Nil) = SIMPLEBLE_FAILURE) then 857 | begin // failed... 858 | UtilLog('Subscribing failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 859 | ShowMessage('Subscribing failed.'); 860 | SuppressUnsubscribe := true; 861 | TToggleBox(Sender).Font.Style := []; 862 | TToggleBox(Sender).State := cbUnchecked; 863 | end else begin // success... 864 | Inc(BleConnectData[DeIdx].NotIndActiveCnt[SvIdx]); 865 | UtilLog('Subscribed notifications: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 866 | SuppressUnsubscribe := false; 867 | if BleConnectData[DeIdx].HasVspService then 868 | DeviceFormElements[DeIdx].ButtonVspTerminal[SvIdx].Enabled := false; 869 | TToggleBox(Sender).Font.Style := [fsBold]; 870 | end; 871 | end else begin // unsubscribe 872 | if not SuppressUnsubscribe then begin 873 | TToggleBox(Sender).Font.Style := []; 874 | if(SimpleBlePeripheralUnsubscribe(BleConnectData[DeIdx].PeripheralHandle, 875 | BleConnectData[DeIdx].Services[SvIdx].Uuid, 876 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid) = SIMPLEBLE_FAILURE) then 877 | begin // failed... 878 | UtilLog('Unsubscribing failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 879 | ShowMessage('Unsubscribing failed.'); 880 | end else begin // success 881 | Dec(BleConnectData[DeIdx].NotIndActiveCnt[SvIdx]); 882 | if BleConnectData[DeIdx].NotIndActiveCnt[SvIdx] = 0 then 883 | if BleConnectData[DeIdx].HasVspService then 884 | DeviceFormElements[DeIdx].ButtonVspTerminal[SvIdx].Enabled := true; 885 | UtilLog('Unsubscribed notifications: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 886 | end; 887 | end; 888 | end; 889 | end; 890 | 891 | 892 | { Callback function on indication from peripheral } 893 | procedure PeripheralOnIndicate(SvUuid: TSimpleBleUuid; ChUuid: TSimpleBleUuid; Data: PByte; Len: NativeUInt; UserData: PPointer); 894 | var 895 | i, j, k, DeIdx, SvIdx, ChIdx: Integer; 896 | s, sSv, sCh: String; 897 | f: Boolean; 898 | begin 899 | SetString(sSv, SvUuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 900 | SetString(sCh, ChUuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 901 | 902 | // get device, service and characteristic indexes from service and characteristic uuids 903 | f := false; 904 | for i := 0 to Length(BleConnectData)-1 do begin 905 | for j := 0 to BleConnectData[i].ServicesCount-1 do begin 906 | SetString(s, BleConnectData[i].Services[j].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 907 | if s = sSv then 908 | for k := 0 to BleConnectData[i].Services[j].CharacteristicCount-1 do begin 909 | SetString(s, BleConnectData[i].Services[j].Characteristics[k].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 910 | if s = sCh then begin 911 | DeIdx := i; 912 | SvIdx := j; 913 | ChIdx := k; 914 | f := true; 915 | break; 916 | end; 917 | end; 918 | if f then break; 919 | end; 920 | if f then break; 921 | end; 922 | 923 | if not f then begin 924 | UtilLog('Indication: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" Unknown UUIDs.'); 925 | Exit; 926 | end; 927 | 928 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].restore := TextBoxIndicateCnt; 929 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Color := TColor($EED4C0); 930 | Application.ProcessMessages; 931 | 932 | for i := 0 to Len-1 do 933 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i] := Data[i]; 934 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len := Len; 935 | if DeviceFormElements[DeIdx].CheckBoxHexAscii[SvIdx][ChIdx].State = cbChecked then // show data as ascii string 936 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToAscii(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len) 937 | else // show data as hex string 938 | DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption := UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len); 939 | UtilLog('Indication: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4) + 940 | ' Data=' + UtilDataToHex(BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data, BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len)); 941 | end; 942 | 943 | 944 | { Indicate characteristic button clicked } 945 | procedure TDeviceForm.ButtonCharIndicate(Sender: TObject); 946 | var 947 | i, DeIdx, SvIdx, ChIdx: Integer; 948 | sSv, sCh: String; 949 | begin 950 | // from button tag property we get which device, service and characteristic we are about to change 951 | i := tButton(Sender).Tag; 952 | DeIdx := (i shr TagPosDev) and $ff; 953 | SvIdx := (i shr TagPosSrv) and $ff; 954 | ChIdx := (i shr TagPosChr) and $ff; 955 | 956 | SetString(sSv, BleConnectData[DeIdx].Services[SvIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 957 | SetString(sCh, BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 958 | 959 | if TToggleBox(Sender).State = cbChecked then begin // subscribe 960 | if(SimpleBlePeripheralIndicate(BleConnectData[DeIdx].PeripheralHandle, 961 | BleConnectData[DeIdx].Services[SvIdx].Uuid, 962 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid, 963 | @PeripheralOnNotify, Nil) = SIMPLEBLE_FAILURE) then 964 | begin // failed... 965 | UtilLog('Subscribing failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 966 | ShowMessage('Subscribing failed.'); 967 | SuppressUnsubscribe := true; 968 | TToggleBox(Sender).Font.Style := []; 969 | TToggleBox(Sender).State := cbUnchecked; 970 | end else begin // success... 971 | Inc(BleConnectData[DeIdx].NotIndActiveCnt[SvIdx]); 972 | UtilLog('Subscribed notifications: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 973 | SuppressUnsubscribe := false; 974 | if BleConnectData[DeIdx].HasVspService then 975 | DeviceFormElements[DeIdx].ButtonVspTerminal[SvIdx].Enabled := false; 976 | TToggleBox(Sender).Font.Style := [fsBold]; 977 | end; 978 | end else begin // unsubscribe 979 | if not SuppressUnsubscribe then begin 980 | TToggleBox(Sender).Font.Style := []; 981 | if(SimpleBlePeripheralUnsubscribe(BleConnectData[DeIdx].PeripheralHandle, 982 | BleConnectData[DeIdx].Services[SvIdx].Uuid, 983 | BleConnectData[DeIdx].Services[SvIdx].Characteristics[ChIdx].Uuid) = SIMPLEBLE_FAILURE) then 984 | begin // failed... 985 | UtilLog('Unsubscribing failed: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 986 | ShowMessage('Unsubscribing failed.'); 987 | end else begin // success 988 | Dec(BleConnectData[DeIdx].NotIndActiveCnt[SvIdx]); 989 | if BleConnectData[DeIdx].NotIndActiveCnt[SvIdx] = 0 then 990 | if BleConnectData[DeIdx].HasVspService then 991 | DeviceFormElements[DeIdx].ButtonVspTerminal[SvIdx].Enabled := true; 992 | UtilLog('Unsubscribed notifications: [' + BleConnectData[DeIdx].MacAddress + '] "' + BleConnectData[DeIdx].DeviceName + '" SV:' + Copy(sSv, 5, 4) + ' CH:' + Copy(sCh, 5, 4)); 993 | end; 994 | end; 995 | end; 996 | end; 997 | 998 | 999 | { Characteristic text box editing done } 1000 | procedure TDeviceForm.CharEditingDone(Sender: TObject); 1001 | var 1002 | i, DeIdx, SvIdx, ChIdx: Integer; 1003 | Buffer: array of Byte; 1004 | len: Integer; 1005 | begin 1006 | SetLength(Buffer, CharDescMaxLength); 1007 | 1008 | // from button tag property we get which device, service and characteristic we are about to change 1009 | i := TToggleBox(Sender).Tag; 1010 | DeIdx := (i shr TagPosDev) and $ff; 1011 | SvIdx := (i shr TagPosSrv) and $ff; 1012 | ChIdx := (i shr TagPosChr) and $ff; 1013 | 1014 | if DeviceFormElements[DeIdx].CheckBoxHexAscii[SvIdx][ChIdx].State = cbChecked then // convert ascii string to data 1015 | Buffer := UtilAsciiToData(DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption, len) 1016 | else begin // convert hex string to data 1017 | if not UtilStringIsHex(DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption) then begin 1018 | ShowMessage('Invalid hex characters or not multiple of 2'); 1019 | Exit; 1020 | end; 1021 | Buffer := UtilHexToData(DeviceFormElements[DeIdx].TextBoxCharacteristic[SvIdx][ChIdx].Caption, len); 1022 | end; 1023 | 1024 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].len := len; 1025 | for i := 0 to len-1 do 1026 | BleConnectData[DeIdx].Characteristic[SvIdx][ChIdx].data[i] := Buffer[i]; 1027 | end; 1028 | 1029 | 1030 | { Tick timer for device connect functions } 1031 | procedure TDeviceForm.TickTimer(Sender: TObject); 1032 | var 1033 | idx, s, c: Integer; 1034 | begin 1035 | idx := 0; 1036 | while idx < Length(BleConnectData) do begin // we loop over all connected device, since we only have this one timer function for all devices 1037 | if BleConnectData[idx].IsConnected then begin 1038 | // get current mtu size, might change during connection 1039 | if SimpleBlePeripheralMtu(BleConnectData[idx].PeripheralHandle) <> BleConnectData[idx].AttMtuSize then begin 1040 | BleConnectData[idx].AttMtuSize := SimpleBlePeripheralMtu(BleConnectData[idx].PeripheralHandle); 1041 | UtilLog('MTU Size on "' + BleConnectData[idx].DeviceName + '" [' + UpperCase(BleConnectData[idx].MacAddress) + '] changed: ' + IntToStr(BleConnectData[idx].AttMtuSize+3)); 1042 | DeviceForm[idx].LabelMtuSize.Caption := 'MTU Size: ' + IntToStr(SimpleBlePeripheralMtu(BleConnectData[idx].PeripheralHandle)+3); 1043 | end; 1044 | // check if we need to restore the color of some notified characteristic text boxes 1045 | s := 0; 1046 | while s < Length(BleConnectData[idx].Services) do begin 1047 | c := 0; 1048 | while c < Length(BleConnectData[idx].Characteristic[s]) do begin 1049 | if BleConnectData[idx].Characteristic[s][c].restore > 0 then begin 1050 | Dec(BleConnectData[idx].Characteristic[s][c].restore); 1051 | if BleConnectData[idx].Characteristic[s][c].restore = 0 then begin 1052 | DeviceFormElements[idx].TextBoxCharacteristic[s][c].Color := clForm; 1053 | end; 1054 | end; 1055 | Inc(c); 1056 | end; 1057 | Inc(s); 1058 | end; 1059 | end; 1060 | Inc(idx); 1061 | end; 1062 | end; 1063 | 1064 | 1065 | { Form changed bounds, used to set coordinates for device form } 1066 | procedure TDeviceForm.FormChangeBounds(Sender: TObject); 1067 | var 1068 | idx: Integer; 1069 | begin 1070 | idx := TForm(Sender).Tag; 1071 | 1072 | UtilSetNextFormTop(DeviceForm[idx]); 1073 | UtilSetNextFormLeft(DeviceForm[idx]); 1074 | end; 1075 | 1076 | 1077 | end. 1078 | 1079 | 1080 | -------------------------------------------------------------------------------- /src/help.lfm: -------------------------------------------------------------------------------- 1 | object HelpForm: THelpForm 2 | Left = 13 3 | Height = 480 4 | Top = 684 5 | Width = 440 6 | Caption = 'InsideBlue BLE Tool - Help' 7 | ClientHeight = 480 8 | ClientWidth = 440 9 | Color = clWhite 10 | OnClose = FormClose 11 | OnCreate = FormCreate 12 | LCLVersion = '3.99.0.0' 13 | object RichMemoHelp: TRichMemo 14 | Left = 8 15 | Height = 480 16 | Top = 0 17 | Width = 432 18 | Anchors = [akTop, akLeft, akRight, akBottom] 19 | BorderStyle = bsNone 20 | HideSelection = False 21 | Lines.Strings = ( 22 | 'RichMemo1' 23 | ) 24 | ReadOnly = True 25 | ScrollBars = ssAutoVertical 26 | TabOrder = 0 27 | ZoomFactor = 1 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /src/help.pas: -------------------------------------------------------------------------------- 1 | unit Help; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, RichMemo, 9 | Util; 10 | 11 | type 12 | 13 | { THelpForm } 14 | 15 | THelpForm = class(TForm) 16 | RichMemoHelp: TRichMemo; 17 | procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); 18 | procedure FormCreate(Sender: TObject); 19 | 20 | private 21 | 22 | public 23 | 24 | end; 25 | 26 | function HelpInit(): Boolean; 27 | procedure HelpShow(b: TButton); 28 | 29 | var 30 | HelpForm: THelpForm; 31 | HelpFileExist: Boolean; 32 | HelpFilePath: String; 33 | RestoreButton: TButton; 34 | 35 | 36 | implementation 37 | 38 | {$R *.lfm} 39 | 40 | 41 | { THelpForm } 42 | 43 | { Init, check if help file exist } 44 | function HelpInit(): Boolean; 45 | begin 46 | 47 | HelpFileExist := true; 48 | HelpFilePath := ''; // first look into local folder 49 | if not FileExists(HelpFilePath + 'help.rtf') then begin 50 | HelpFilePath := 'help\'; // this is relative path when started from within Lazarus IDE 51 | if not FileExists(HelpFilePath + 'help.rtf') then begin 52 | UtilLog('Could not open help file.'); 53 | HelpFileExist := false; 54 | end; 55 | end; 56 | Result := HelpFileExist; 57 | end; 58 | 59 | 60 | { Show the help form } 61 | procedure HelpShow(b: TButton); 62 | begin 63 | Application.CreateForm(THelpForm, HelpForm); 64 | HelpForm.Top := UtilGetNextFormTop; 65 | HelpForm.Left := UtilGetNextFormLeft(HelpForm); 66 | HelpForm.Show; 67 | UtilSetNextFormTop(HelpForm); 68 | UtilSetNextFormLeft(HelpForm); 69 | RestoreButton := b; 70 | end; 71 | 72 | 73 | { Create form and load rich text file into RichMemo } 74 | procedure THelpForm.FormCreate(Sender: TObject); 75 | var 76 | FS: TFileStream; 77 | begin 78 | if HelpFileExist then begin 79 | FS := TFileStream.Create(HelpFilePath + 'help.rtf', fmOpenRead or fmShareDenyNone); 80 | try 81 | RichMemoHelp.LoadRichText(FS); 82 | finally 83 | FS.Free; 84 | end; 85 | end; 86 | end; 87 | 88 | 89 | { On close help window we reset the coordinate for the next window and re-enable the help button } 90 | procedure THelpForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); 91 | begin 92 | UtilSetNextFormLeft(HelpForm, true); 93 | RestoreButton.Enabled := true; 94 | end; 95 | 96 | 97 | end. 98 | 99 | -------------------------------------------------------------------------------- /src/insideblue.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/src/insideblue.ico -------------------------------------------------------------------------------- /src/insideblue.lpi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <Scaled Value="True"/> 10 | <ResourceType Value="res"/> 11 | <UseXPManifest Value="True"/> 12 | <XPManifest> 13 | <DpiAware Value="True"/> 14 | </XPManifest> 15 | <Icon Value="0"/> 16 | </General> 17 | <BuildModes> 18 | <Item Name="Debug" Default="True"/> 19 | <Item Name="Win64"> 20 | <CompilerOptions> 21 | <Version Value="11"/> 22 | <PathDelim Value="\"/> 23 | <Target> 24 | <Filename Value="bin\$(TargetOS)-$(TargetCPU)\InsideBlue"/> 25 | </Target> 26 | <SearchPaths> 27 | <IncludeFiles Value="$(ProjOutDir)"/> 28 | <UnitOutputDirectory Value="lib\$(TargetOS)-$(TargetCPU)"/> 29 | </SearchPaths> 30 | <CodeGeneration> 31 | <SmartLinkUnit Value="True"/> 32 | <TargetCPU Value="x86_64"/> 33 | <TargetOS Value="win64"/> 34 | <Optimizations> 35 | <OptimizationLevel Value="2"/> 36 | </Optimizations> 37 | </CodeGeneration> 38 | <Linking> 39 | <Debugging> 40 | <GenerateDebugInfo Value="False"/> 41 | <DebugInfoType Value="dsDwarf3"/> 42 | </Debugging> 43 | <LinkSmart Value="True"/> 44 | <Options> 45 | <Win32> 46 | <GraphicApplication Value="True"/> 47 | </Win32> 48 | </Options> 49 | </Linking> 50 | </CompilerOptions> 51 | </Item> 52 | <Item Name="Win32"> 53 | <CompilerOptions> 54 | <Version Value="11"/> 55 | <PathDelim Value="\"/> 56 | <Target> 57 | <Filename Value="bin\$(TargetOS)-$(TargetCPU)\InsideBlue"/> 58 | </Target> 59 | <SearchPaths> 60 | <IncludeFiles Value="$(ProjOutDir)"/> 61 | <UnitOutputDirectory Value="lib\$(TargetOS)-$(TargetCPU)"/> 62 | </SearchPaths> 63 | <CodeGeneration> 64 | <SmartLinkUnit Value="True"/> 65 | <TargetCPU Value="i386"/> 66 | <TargetOS Value="win32"/> 67 | <Optimizations> 68 | <OptimizationLevel Value="2"/> 69 | </Optimizations> 70 | </CodeGeneration> 71 | <Linking> 72 | <Debugging> 73 | <GenerateDebugInfo Value="False"/> 74 | <DebugInfoType Value="dsDwarf3"/> 75 | </Debugging> 76 | <LinkSmart Value="True"/> 77 | <Options> 78 | <Win32> 79 | <GraphicApplication Value="True"/> 80 | </Win32> 81 | </Options> 82 | </Linking> 83 | </CompilerOptions> 84 | </Item> 85 | <Item Name="Linux64"> 86 | <CompilerOptions> 87 | <Version Value="11"/> 88 | <PathDelim Value="\"/> 89 | <Target> 90 | <Filename Value="bin\$(TargetOS)-$(TargetCPU)\InsideBlue"/> 91 | </Target> 92 | <SearchPaths> 93 | <IncludeFiles Value="$(ProjOutDir)"/> 94 | <UnitOutputDirectory Value="lib\$(TargetOS)-$(TargetCPU)"/> 95 | </SearchPaths> 96 | <CodeGeneration> 97 | <SmartLinkUnit Value="True"/> 98 | <TargetCPU Value="x86_64"/> 99 | <TargetOS Value="linux"/> 100 | <Optimizations> 101 | <OptimizationLevel Value="2"/> 102 | </Optimizations> 103 | </CodeGeneration> 104 | <Linking> 105 | <Debugging> 106 | <GenerateDebugInfo Value="False"/> 107 | <DebugInfoType Value="dsDwarf3"/> 108 | </Debugging> 109 | <LinkSmart Value="True"/> 110 | <Options> 111 | <PassLinkerOptions Value="True"/> 112 | <LinkerOptions Value="-R .\"/> 113 | <Win32> 114 | <GraphicApplication Value="True"/> 115 | </Win32> 116 | </Options> 117 | </Linking> 118 | </CompilerOptions> 119 | </Item> 120 | </BuildModes> 121 | <PublishOptions> 122 | <Version Value="2"/> 123 | <UseFileFilters Value="True"/> 124 | </PublishOptions> 125 | <RunParams> 126 | <FormatVersion Value="2"/> 127 | </RunParams> 128 | <RequiredPackages> 129 | <Item> 130 | <PackageName Value="SynEditDsgn"/> 131 | </Item> 132 | <Item> 133 | <PackageName Value="SynEdit"/> 134 | </Item> 135 | <Item> 136 | <PackageName Value="richmemopackage"/> 137 | </Item> 138 | <Item> 139 | <PackageName Value="LCL"/> 140 | </Item> 141 | </RequiredPackages> 142 | <Units> 143 | <Unit> 144 | <Filename Value="insideblue.lpr"/> 145 | <IsPartOfProject Value="True"/> 146 | <UnitName Value="InsideBlue"/> 147 | </Unit> 148 | <Unit> 149 | <Filename Value="main.pas"/> 150 | <IsPartOfProject Value="True"/> 151 | <ComponentName Value="ScanForm"/> 152 | <HasResources Value="True"/> 153 | <ResourceBaseClass Value="Form"/> 154 | <UnitName Value="Main"/> 155 | </Unit> 156 | <Unit> 157 | <Filename Value="simpleble.pas"/> 158 | <IsPartOfProject Value="True"/> 159 | <UnitName Value="SimpleBle"/> 160 | </Unit> 161 | <Unit> 162 | <Filename Value="connect.pas"/> 163 | <IsPartOfProject Value="True"/> 164 | <ComponentName Value="DeviceForm"/> 165 | <HasResources Value="True"/> 166 | <ResourceBaseClass Value="Form"/> 167 | <UnitName Value="Connect"/> 168 | </Unit> 169 | <Unit> 170 | <Filename Value="scan.pas"/> 171 | <IsPartOfProject Value="True"/> 172 | <UnitName Value="Scan"/> 173 | </Unit> 174 | <Unit> 175 | <Filename Value="ble.pas"/> 176 | <IsPartOfProject Value="True"/> 177 | <UnitName Value="Ble"/> 178 | </Unit> 179 | <Unit> 180 | <Filename Value="help.pas"/> 181 | <IsPartOfProject Value="True"/> 182 | <ComponentName Value="HelpForm"/> 183 | <HasResources Value="True"/> 184 | <ResourceBaseClass Value="Form"/> 185 | <UnitName Value="Help"/> 186 | </Unit> 187 | <Unit> 188 | <Filename Value="assignednumbers.pas"/> 189 | <IsPartOfProject Value="True"/> 190 | <UnitName Value="AssignedNumbers"/> 191 | </Unit> 192 | <Unit> 193 | <Filename Value="uartterminal.pas"/> 194 | <IsPartOfProject Value="True"/> 195 | <ComponentName Value="TerminalForm"/> 196 | <HasResources Value="True"/> 197 | <ResourceBaseClass Value="Form"/> 198 | <UnitName Value="UartTerminal"/> 199 | </Unit> 200 | <Unit> 201 | <Filename Value="util.pas"/> 202 | <IsPartOfProject Value="True"/> 203 | <UnitName Value="Util"/> 204 | </Unit> 205 | </Units> 206 | </ProjectOptions> 207 | <CompilerOptions> 208 | <Version Value="11"/> 209 | <PathDelim Value="\"/> 210 | <Target> 211 | <Filename Value="bin\Debug\InsideBlue"/> 212 | </Target> 213 | <SearchPaths> 214 | <IncludeFiles Value="$(ProjOutDir)"/> 215 | <UnitOutputDirectory Value="lib\Debug"/> 216 | </SearchPaths> 217 | <CodeGeneration> 218 | <SmartLinkUnit Value="True"/> 219 | </CodeGeneration> 220 | <Linking> 221 | <Debugging> 222 | <DebugInfoType Value="dsDwarf3"/> 223 | </Debugging> 224 | <LinkSmart Value="True"/> 225 | <Options> 226 | <PassLinkerOptions Value="True"/> 227 | <LinkerOptions Value="-R .\"/> 228 | <Win32> 229 | <GraphicApplication Value="True"/> 230 | </Win32> 231 | </Options> 232 | </Linking> 233 | </CompilerOptions> 234 | <Debugging> 235 | <Exceptions> 236 | <Item> 237 | <Name Value="EAbort"/> 238 | </Item> 239 | <Item> 240 | <Name Value="ECodetoolError"/> 241 | </Item> 242 | <Item> 243 | <Name Value="EFOpenError"/> 244 | </Item> 245 | </Exceptions> 246 | </Debugging> 247 | </CONFIG> 248 | -------------------------------------------------------------------------------- /src/insideblue.lpr: -------------------------------------------------------------------------------- 1 | program InsideBlue; 2 | 3 | {$mode objfpc}{$H+} 4 | {$APPTYPE GUI} 5 | 6 | {$UNDEF DYNAMIC_LOADING} 7 | {$IFDEF WINDOWS} 8 | {$DEFINE DYNAMIC_LOADING} { UNCOMMENT IF YOU WANT DYNAMIC LOADING } 9 | {$ENDIF} 10 | 11 | uses 12 | {$IFDEF UNIX} 13 | cthreads, 14 | {$ENDIF} 15 | {$IFDEF HASAMIGA} 16 | athreads, 17 | {$ENDIF} 18 | Interfaces, // this includes the LCL widgetset 19 | Forms, Dialogs, Main, SimpleBle; 20 | 21 | {$R *.res} 22 | 23 | 24 | begin 25 | RequireDerivedFormResource := True; 26 | Application.Scaled:=True; 27 | Application.Initialize; 28 | 29 | // check for dynamic library loading (Windows only) 30 | {$IFDEF DYNAMIC_LOADING} 31 | if not SimpleBleLoadLibrary() then begin 32 | ShowMessage('Failed to load SimpleBLE library.'); 33 | Halt; 34 | end; 35 | {$ENDIF} 36 | 37 | // create the main scan form and run the application 38 | Application.CreateForm(TScanForm, ScanForm); 39 | Application.Run; 40 | end. 41 | 42 | -------------------------------------------------------------------------------- /src/insideblue.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/InsideBlue-BLE-Tool/66c8680e711b565d4fd7e39e4029c10438c2bbd4/src/insideblue.res -------------------------------------------------------------------------------- /src/main.pas: -------------------------------------------------------------------------------- 1 | unit Main; 2 | 3 | {$mode objfpc}{$H+} 4 | 5 | { InsideBlue BLE Tool 6 | =================== 7 | Welcome to InsideBlue BLE Tool, an easy to use cross-platform 8 | application for Bluetooth Low-Energy (BLE) connectivity leveraging 9 | the PC’s integrated Bluetooth adapter. 10 | 11 | You can scan for BLE devices that are advertising and discover 12 | their GAP services or manufacturer specific data, engage a 13 | connection and access the peripheral GATT table. 14 | 15 | For more details click on help button or check README.md. 16 | 17 | Project 18 | InsideBlue is at github: https://github.com/eriklins/InsideBlue-BLE-Tool 19 | 20 | BLE Library 21 | Inside Blue BLE Tool is based on the SimpleBLE library 22 | (https://github.com/OpenBluetoothToolbox/SimpleBLE) 23 | and uses the Pascal bindings for that library 24 | (https://github.com/eriklins/Pascal-Bindings-For-SimpleBLE-Library). 25 | 26 | License 27 | Copyright (C) 2023 Erik Lins 28 | This project is released under the MIT License (see README.md) 29 | } 30 | 31 | interface 32 | 33 | uses 34 | Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Buttons, ClipBrd, 35 | SimpleBle, Util, Ble, Scan, Connect, Help; 36 | 37 | type 38 | { type: form elements for ble device scanning } 39 | TPanelBleScanElements = record 40 | PanelDeviceInfo: TPanel; 41 | TextBoxDeviceName: TEdit; 42 | ButtonConnectDevice: TButton; 43 | LabelDeviceConnectable: TLabel; 44 | LabelTxPower: TLabel; 45 | LabelRssi: TLabel; 46 | LabelPaired: TLabel; 47 | LabelMacAddress: TLabel; 48 | LabelServices: TLabel; 49 | LabelServiceUuid: array of TLabel; 50 | LabelServiceData: TLabel; 51 | TextBoxServiceData: array of TEdit; 52 | CheckBoxSrvsHexAscii: array of TCheckBox; 53 | LabelManufData: TLabel; 54 | LabelManufDataId: array of TLabel; 55 | TextBoxManufData: array of TEdit; 56 | CheckBoxManufHexAscii: array of TCheckBox; 57 | BevelSeparatorServices: TBevel; 58 | BevelSeparatorManufact: TBevel; 59 | end; 60 | 61 | { TScanForm } 62 | TScanForm = class(TForm) 63 | ButtonHelp: TButton; 64 | ButtonCopyLog: TButton; 65 | ButtonClearLog: TButton; 66 | ButtonStartScan: TButton; 67 | ButtonClear: TButton; 68 | CheckBoxShowLog: TCheckBox; 69 | EditFilterDeviceName: TEdit; 70 | EditFilterMacAddress: TEdit; 71 | EditFilterRssi: TEdit; 72 | LabelHeader: TLabel; 73 | LabelFilter: TLabel; 74 | LabelFilterRssi: TLabel; 75 | LabelFilterMacAddress: TLabel; 76 | LabelFilterDeviceName: TLabel; 77 | LabelNofDevices: TLabel; 78 | LogOutput: TMemo; 79 | ScrollBoxDevices: TScrollBox; 80 | ScanTimer: TTimer; 81 | procedure ButtonHelpClick(Sender: TObject); 82 | procedure ButtonClearLogClick(Sender: TObject); 83 | procedure ButtonClearClick(Sender: TObject); 84 | procedure ButtonCopyLogClick(Sender: TObject); 85 | procedure ButtonStartScanClick(Sender: TObject); 86 | procedure CheckBoxShowLogChange(Sender: TObject); 87 | procedure EditFilterDeviceNameEditingDone(Sender: TObject); 88 | procedure EditFilterMacAddressEditingDone(Sender: TObject); 89 | procedure EditFilterRssiEditingDone(Sender: TObject); 90 | procedure FormChangeBounds(Sender: TObject); 91 | procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); 92 | procedure FormCreate(Sender: TObject); 93 | procedure ButtonConnectDeviceClick(Sender: TObject); 94 | procedure CheckBoxManufHexAsciiClick(Sender: TObject); 95 | procedure CheckBoxServiceHexAsciiClick(Sender: TObject); 96 | procedure ScanTimerTimer(Sender: TObject); 97 | 98 | private 99 | 100 | public 101 | 102 | end; 103 | 104 | const 105 | ScanPanelHeight = 132; 106 | ScanPanelPaddingHorizontal = 8; 107 | ScanPanelPaddingVertical = 4; 108 | ScanPanelColor = $EED4C0; 109 | 110 | var 111 | ScanForm: TScanForm; 112 | PeripheralScanPanel: array of TPanelBleScanElements; 113 | ScanFormWidth: Integer = 0; 114 | 115 | implementation 116 | 117 | {$R *.lfm} 118 | 119 | 120 | { Tick timer for updating the scan device list } 121 | procedure TScanForm.ScanTimerTimer(Sender: TObject); 122 | var 123 | DevIdx, i, j: Integer; 124 | DevicePanelTop: Integer; 125 | s, n: String; 126 | begin 127 | 128 | // check if BLE adapter is still enabled 129 | if SimpleBleAdapterIsBluetoothEnabled() <> BleIsEnabled then begin 130 | BleIsEnabled := SimpleBleAdapterIsBluetoothEnabled(); 131 | if not BleIsEnabled then begin 132 | LabelNofDevices.Caption := 'Bluetooth is disabled.'; 133 | ButtonStartScan.Enabled := false; 134 | FlagBleScanningActive := false; 135 | for i := 0 to Length(PeripheralScanPanel)-1 do begin 136 | PeripheralScanPanel[i].PanelDeviceInfo.Free; 137 | PeripheralScanPanel[i].PanelDeviceInfo := Nil; 138 | end; 139 | SetLength(PeripheralScanPanel, 0); 140 | ScanClearPeripheralList; 141 | UtilLog('Bluetooth is disabled.'); 142 | Exit; 143 | end else begin 144 | UtilLog('Bluetooth is enabled.'); 145 | LabelNofDevices.Caption := 'Press Start Scan to Discover Devices...'; 146 | ButtonStartScan.Enabled := true; 147 | ButtonStartScan.Caption := 'Start Scan'; 148 | FlagBleScanningActive := false; 149 | SimpleBleAdapterScanStop(BleAdapter); 150 | Exit; 151 | end; 152 | end; 153 | 154 | // create and/or update device form elements if scanning is active and we have found some devices 155 | if FlagBleScanningActive and (PeripheralNofDevices > 0) then begin 156 | 157 | // show headline with number of devices found 158 | LabelNofDevices.Caption := IntToStr(PeripheralNofDevices) + ' Devices Found'; 159 | 160 | if Length(PeripheralScanPanel) < PeripheralNofDevices then 161 | SetLength(PeripheralScanPanel, PeripheralNofDevices); 162 | 163 | // walk through all existing devices 164 | for DevIdx := 0 to PeripheralNofDevices-1 do begin 165 | 166 | // calculate top coordinate of current device panel 167 | If DevIdx > 0 then 168 | DevicePanelTop := PeripheralScanPanel[DevIdx-1].PanelDeviceInfo.Top + PeripheralScanPanel[DevIdx-1].PanelDeviceInfo.Height + 2*ScanPanelPaddingVertical 169 | else 170 | DevicePanelTop := 2*ScanPanelPaddingVertical; 171 | 172 | // check if device's form elements needs an update 173 | if BleScanData[DevIdx].UpdateForm then begin 174 | 175 | // if it's a newly discovered device we draw the form elements 176 | if PeripheralScanPanel[DevIdx].PanelDeviceInfo = Nil then begin 177 | PeripheralScanPanel[DevIdx].PanelDeviceInfo := TPanel.Create(ScanForm); 178 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Parent := ScanForm.ScrollBoxDevices; 179 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Top := DevicePanelTop; 180 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Left := ScanPanelPaddingHorizontal; 181 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width := ScanForm.ScrollBoxDevices.Width - 32; 182 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := ScanPanelHeight; 183 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Color := TColor(ScanPanelColor); 184 | 185 | PeripheralScanPanel[DevIdx].TextBoxDeviceName := TEdit.Create(ScanForm); 186 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 187 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.ReadOnly := True; 188 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Top := 2*ScanPanelPaddingVertical; 189 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Left := ScanPanelPaddingHorizontal; 190 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Width := Trunc((ScanForm.ScrollBoxDevices.Width - 32) * 2 / 3); 191 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Color := clWhite; 192 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Font.Size := 10; 193 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Font.Style := [fsBold]; 194 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.AutoSize := True; 195 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Caption := 'Device Name: '; // placeholder for proper positioning of further elements 196 | 197 | PeripheralScanPanel[DevIdx].ButtonConnectDevice := TButton.Create(ScanForm); 198 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 199 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Caption := 'Connect'; 200 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Width := (ScanForm.ScrollBoxDevices.Width - 32) - PeripheralScanPanel[DevIdx].TextBoxDeviceName.Width - (3*ScanPanelPaddingHorizontal); 201 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Height := PeripheralScanPanel[DevIdx].TextBoxDeviceName.Height; 202 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Top := 2*ScanPanelPaddingVertical; 203 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Left := PeripheralScanPanel[DevIdx].TextBoxDeviceName.Width + 2*ScanPanelPaddingHorizontal; 204 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Font.Size := 10; 205 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Tag := DevIdx; 206 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.OnClick := @ScanForm.ButtonConnectDeviceClick; 207 | 208 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable := TLabel.Create(ScanForm); 209 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 210 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Top := PeripheralScanPanel[DevIdx].TextBoxDeviceName.Height + 2*ScanPanelPaddingVertical; 211 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Left := ScanPanelPaddingHorizontal; 212 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Font.Size := 10; 213 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Caption := 'Connectable: Yes'; // placeholder for proper positioning of further elements 214 | 215 | PeripheralScanPanel[DevIdx].LabelTxPower := TLabel.Create(ScanForm); 216 | PeripheralScanPanel[DevIdx].LabelTxPower.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 217 | PeripheralScanPanel[DevIdx].LabelTxPower.Top := PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Top; 218 | PeripheralScanPanel[DevIdx].LabelTxPower.Left := PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Left + PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Width + (3*ScanPanelPaddingHorizontal); 219 | PeripheralScanPanel[DevIdx].LabelTxPower.Font.Size := 10; 220 | PeripheralScanPanel[DevIdx].LabelTxPower.Caption := 'TX Power: n/a'; // placeholder for proper positioning of further elements 221 | 222 | PeripheralScanPanel[DevIdx].LabelRssi := TLabel.Create(ScanForm); 223 | PeripheralScanPanel[DevIdx].LabelRssi.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 224 | PeripheralScanPanel[DevIdx].LabelRssi.Top := PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Top; 225 | PeripheralScanPanel[DevIdx].LabelRssi.Left := PeripheralScanPanel[DevIdx].LabelTxPower.Left + PeripheralScanPanel[DevIdx].LabelTxPower.Width + (3*ScanPanelPaddingHorizontal); 226 | PeripheralScanPanel[DevIdx].LabelRssi.Font.Size := 10; 227 | PeripheralScanPanel[DevIdx].LabelRssi.Caption := 'RSSI: -99 dBm'; // placeholder for proper positioning of further elements 228 | 229 | if BleScanData[DevIdx].IsPaired then begin 230 | PeripheralScanPanel[DevIdx].LabelPaired := TLabel.Create(ScanForm); 231 | PeripheralScanPanel[DevIdx].LabelPaired.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 232 | PeripheralScanPanel[DevIdx].LabelPaired.Top := PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Top; 233 | PeripheralScanPanel[DevIdx].LabelPaired.Left := PeripheralScanPanel[DevIdx].LabelRssi.Left + PeripheralScanPanel[DevIdx].LabelRssi.Width + (3*ScanPanelPaddingHorizontal); 234 | PeripheralScanPanel[DevIdx].LabelPaired.Font.Size := 10; 235 | PeripheralScanPanel[DevIdx].LabelPaired.Caption := 'Paired'; // placeholder for proper positioning of further elements 236 | end; 237 | 238 | PeripheralScanPanel[DevIdx].LabelMacAddress := TLabel.Create(ScanForm); 239 | PeripheralScanPanel[DevIdx].LabelMacAddress.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 240 | PeripheralScanPanel[DevIdx].LabelMacAddress.Top := PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Top + PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Height + ScanPanelPaddingVertical div 2; 241 | PeripheralScanPanel[DevIdx].LabelMacAddress.Left := ScanPanelPaddingHorizontal; 242 | PeripheralScanPanel[DevIdx].LabelMacAddress.Font.Size := 10; 243 | PeripheralScanPanel[DevIdx].LabelMacAddress.Caption := 'MAC Address:'; // placeholder for proper positioning of further elements 244 | 245 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].LabelMacAddress.Top + PeripheralScanPanel[DevIdx].LabelMacAddress.Height + 2*ScanPanelPaddingVertical; 246 | 247 | // if we have advertised services we add labels for each 248 | if BleScanData[DevIdx].ServicesCount > 0 then begin 249 | SetLength(PeripheralScanPanel[DevIdx].LabelServiceUuid, BleScanData[DevIdx].ServicesCount); 250 | SetLength(PeripheralScanPanel[DevIdx].TextBoxServiceData, BleScanData[DevIdx].ServicesCount); 251 | SetLength(PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii, BleScanData[DevIdx].ServicesCount); 252 | 253 | PeripheralScanPanel[DevIdx].BevelSeparatorServices := TBevel.Create(ScanForm); 254 | PeripheralScanPanel[DevIdx].BevelSeparatorServices.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 255 | PeripheralScanPanel[DevIdx].BevelSeparatorServices.Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - ScanPanelPaddingVertical; 256 | PeripheralScanPanel[DevIdx].BevelSeparatorServices.Left := ScanPanelPaddingHorizontal - (ScanPanelPaddingHorizontal div 2); 257 | PeripheralScanPanel[DevIdx].BevelSeparatorServices.Width := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - ScanPanelPaddingHorizontal; 258 | PeripheralScanPanel[DevIdx].BevelSeparatorServices.Height := 1; 259 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].BevelSeparatorServices.Top + PeripheralScanPanel[DevIdx].BevelSeparatorServices.Height + ScanPanelPaddingVertical; 260 | 261 | PeripheralScanPanel[DevIdx].LabelServices := TLabel.Create(ScanForm); 262 | PeripheralScanPanel[DevIdx].LabelServices.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 263 | PeripheralScanPanel[DevIdx].LabelServices.Caption := 'Services:'; 264 | PeripheralScanPanel[DevIdx].LabelServices.Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - ScanPanelPaddingVertical; 265 | PeripheralScanPanel[DevIdx].LabelServices.Left := ScanPanelPaddingHorizontal; 266 | PeripheralScanPanel[DevIdx].LabelServices.Font.Size := 10; 267 | 268 | SetLength(PeripheralScanPanel[DevIdx].LabelServiceUuid, BleScanData[DevIdx].ServicesCount); 269 | for i := 0 to BleScanData[DevIdx].ServicesCount-1 do begin 270 | // label for the service uuid or assigned name 271 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i] := TLabel.Create(ScanForm); 272 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 273 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Top := PeripheralScanPanel[DevIdx].LabelServices.Top + i*PeripheralScanPanel[DevIdx].LabelServices.Height; 274 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Left := PeripheralScanPanel[DevIdx].LabelServices.Left + PeripheralScanPanel[DevIdx].LabelServices.Width + ScanPanelPaddingHorizontal; 275 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Font.Size := 10; 276 | // check if service is BT assigned number or VSP service 277 | SetString(s, BleScanData[DevIdx].Services[i].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 278 | n := BleAssignedServiceUuidToName(BleScanData[DevIdx].Services[i].Uuid); 279 | if n = '' then begin 280 | n := BleVspServiceUuidToName(BleScanData[DevIdx].Services[i].Uuid); 281 | if n = '' then 282 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Caption := s 283 | else begin 284 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Caption := n; 285 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].ShowHint := true; 286 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Hint := s; 287 | end; 288 | end else begin 289 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Caption := n; 290 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].ShowHint := true; 291 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Hint := s; 292 | end; 293 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Top + PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Height + 2*ScanPanelPaddingVertical; 294 | 295 | if BleScanData[DevIdx].Services[i].DataLength > 0 then begin 296 | // checkbox for ascii/hex view 297 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i] := TCheckBox.Create(ScanForm); 298 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 299 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Caption := 'ASCII'; 300 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Top := PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Top; 301 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Left := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Width - ScanPanelPaddingHorizontal; 302 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Tag := (DevIdx shl TagPosDev) or (i shl TagPosSrv); 303 | PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].OnClick := @ScanForm.CheckBoxServiceHexAsciiClick; 304 | // resize of company name to fit the ascii/hex checkbox 305 | PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Width := PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Width - PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].Width - ScanPanelPaddingHorizontal; 306 | // textbox for service data 307 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i] := TEdit.Create(ScanForm); 308 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 309 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].ReadOnly := True; 310 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Top := PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Top + PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Height;// + ScanPanelPaddingVertical; 311 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Left := PeripheralScanPanel[DevIdx].LabelServiceUuid[i].Left; 312 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Caption := 'TextBoxServiceData'; 313 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].AutoSize := false; 314 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Width := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - PeripheralScanPanel[DevIdx].LabelServices.Width - (3*ScanPanelPaddingHorizontal); 315 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Color := clForm; 316 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Font.Size := 10; 317 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Top + PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Height + 2*ScanPanelPaddingVertical; 318 | end; 319 | end; 320 | end; 321 | end; 322 | 323 | // check if new manufacturing data is available and if we need to add form elements for that 324 | j := Length(PeripheralScanPanel[DevIdx].LabelManufDataId); 325 | if BleScanData[DevIdx].ManufacturerDataCount > j then begin 326 | 327 | if Length(PeripheralScanPanel[DevIdx].LabelManufDataId) = 0 then begin // one very first data set we also add a separator and label 328 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact := TBevel.Create(ScanForm); 329 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 330 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - ScanPanelPaddingVertical; 331 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Left := ScanPanelPaddingHorizontal - (ScanPanelPaddingHorizontal div 2); 332 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Width := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - ScanPanelPaddingHorizontal; 333 | PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Height := 1; 334 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Top + PeripheralScanPanel[DevIdx].BevelSeparatorManufact.Height + ScanPanelPaddingVertical; 335 | PeripheralScanPanel[DevIdx].LabelManufData := TLabel.Create(ScanForm); 336 | PeripheralScanPanel[DevIdx].LabelManufData.Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 337 | PeripheralScanPanel[DevIdx].LabelManufData.Caption := 'Manufacturer Specific Data:'; 338 | PeripheralScanPanel[DevIdx].LabelManufData.Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - ScanPanelPaddingVertical; 339 | PeripheralScanPanel[DevIdx].LabelManufData.Left := ScanPanelPaddingHorizontal; 340 | PeripheralScanPanel[DevIdx].LabelManufData.Font.Size := 10; 341 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].LabelManufData.Top + PeripheralScanPanel[DevIdx].LabelManufData.Height + 2*ScanPanelPaddingVertical; 342 | end; 343 | 344 | SetLength(PeripheralScanPanel[DevIdx].LabelManufDataId, BleScanData[DevIdx].ManufacturerDataCount); 345 | SetLength(PeripheralScanPanel[DevIdx].TextBoxManufData, BleScanData[DevIdx].ManufacturerDataCount); 346 | SetLength(PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii, BleScanData[DevIdx].ManufacturerDataCount); 347 | 348 | for i := j to BleScanData[DevIdx].ManufacturerDataCount-1 do begin 349 | // checkbox for ascii/hex view of manufacturer data 350 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i] := TCheckBox.Create(ScanForm); 351 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 352 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Caption := 'ASCII'; 353 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - 2*ScanPanelPaddingVertical; 354 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Left := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Width - ScanPanelPaddingHorizontal; 355 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Tag := (DevIdx shl TagPosDev) or (i shl TagPosSrv); 356 | PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].OnClick := @ScanForm.CheckBoxManufHexAsciiClick; 357 | // label for the company name 358 | PeripheralScanPanel[DevIdx].LabelManufDataId[i] := TLabel.Create(ScanForm); 359 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 360 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Top := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height - 2*ScanPanelPaddingVertical; 361 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Left := ScanPanelPaddingHorizontal; 362 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Caption := 'LabelManufDataId'; 363 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].AutoSize := false; 364 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Width := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].Width - 4*ScanPanelPaddingHorizontal; 365 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Font.Size := 10; 366 | // check if company id is assigned company name 367 | n := BleAssignedCompanyIdToName(LowerCase(IntToHex(BleScanData[DevIdx].ManufacturerData[i].ManufacturerId, 4))); 368 | if n = '' then 369 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Caption := 'Unknown (' + IntToHex(BleScanData[DevIdx].ManufacturerData[i].ManufacturerId, 4) + ')' 370 | else begin 371 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Caption := n; 372 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].ShowHint := true; 373 | PeripheralScanPanel[DevIdx].LabelManufDataId[i].Hint := n + ' (0x' + IntToHex(BleScanData[DevIdx].ManufacturerData[i].ManufacturerId, 4) + ')'; 374 | end; 375 | // textbox for the manufacturer data payload 376 | PeripheralScanPanel[DevIdx].TextBoxManufData[i] := TEdit.Create(ScanForm); 377 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Parent := PeripheralScanPanel[DevIdx].PanelDeviceInfo; 378 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].ReadOnly := True; 379 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Top := PeripheralScanPanel[DevIdx].LabelManufDataId[i].Top + PeripheralScanPanel[DevIdx].LabelManufDataId[i].Height + ScanPanelPaddingVertical; 380 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Left := ScanPanelPaddingHorizontal; 381 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Caption := 'TextBoxManufData'; 382 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].AutoSize := false; 383 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Width := PeripheralScanPanel[DevIdx].PanelDeviceInfo.Width - (2*ScanPanelPaddingHorizontal); 384 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Color := clForm; 385 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Font.Size := 10; 386 | PeripheralScanPanel[DevIdx].PanelDeviceInfo.Height := PeripheralScanPanel[DevIdx].TextBoxManufData[i].Top + PeripheralScanPanel[DevIdx].TextBoxManufData[i].Height + 2*ScanPanelPaddingVertical; 387 | end; 388 | end; 389 | 390 | // update device's form elements 391 | PeripheralScanPanel[DevIdx].TextBoxDeviceName.Caption := BleScanData[DevIdx].DeviceName; 392 | if BleScanData[DevIdx].IsConnectable then begin 393 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Enabled := true; 394 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Caption := 'Connectable: Yes' 395 | end else begin 396 | PeripheralScanPanel[DevIdx].ButtonConnectDevice.Enabled := false; 397 | PeripheralScanPanel[DevIdx].LabelDeviceConnectable.Caption := 'Connectable: No'; 398 | end; 399 | if BleScanData[DevIdx].TxPower = -32768 then 400 | PeripheralScanPanel[DevIdx].LabelTxPower.Caption := 'TX Power: n/a' 401 | else 402 | PeripheralScanPanel[DevIdx].LabelTxPower.Caption := 'TX Power: ' + IntToStr(BleScanData[DevIdx].TxPower) + ' dBm'; 403 | PeripheralScanPanel[DevIdx].LabelRssi.Caption := 'RSSI: ' + IntToStr(BleScanData[DevIdx].Rssi) + ' dBm'; 404 | PeripheralScanPanel[DevIdx].LabelMacAddress.Caption := 'MAC Address: ' + UpperCase(BleScanData[DevIdx].MacAddress); 405 | 406 | // add service data if available 407 | if BleScanData[DevIdx].ServicesCount > 0 then begin 408 | for i := 0 to BleScanData[DevIdx].ServicesCount-1 do 409 | if BleScanData[DevIdx].Services[i].DataLength > 0 then begin 410 | if PeripheralScanPanel[DevIdx].CheckBoxSrvsHexAscii[i].State = cbChecked then 411 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Caption := UtilDataToAscii(BleScanData[DevIdx].Services[i].Data, BleScanData[DevIdx].Services[i].DataLength) 412 | else 413 | PeripheralScanPanel[DevIdx].TextBoxServiceData[i].Caption := UtilDataToHex(BleScanData[DevIdx].Services[i].Data, BleScanData[DevIdx].Services[i].DataLength); 414 | end; 415 | end; 416 | 417 | // add manufacturer specific data if available 418 | if BleScanData[DevIdx].ManufacturerDataCount > 0 then begin 419 | for i := 0 to BleScanData[DevIdx].ManufacturerDataCount-1 do 420 | if PeripheralScanPanel[DevIdx].CheckBoxManufHexAscii[i].State = cbChecked then 421 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Caption := UtilDataToAscii(BleScanData[DevIdx].ManufacturerData[i].Data, BleScanData[DevIdx].ManufacturerData[i].DataLength) 422 | else 423 | PeripheralScanPanel[DevIdx].TextBoxManufData[i].Caption := UtilDataToHex(BleScanData[DevIdx].ManufacturerData[i].Data, BleScanData[DevIdx].ManufacturerData[i].DataLength); 424 | end; 425 | 426 | // clear flag since we updated everything 427 | BleScanData[DevIdx].UpdateForm := false; 428 | end; 429 | end; 430 | end; 431 | end; 432 | 433 | 434 | { ScanForm functions } 435 | 436 | { create main form and initialize SimpleBLE adapter and callbacks } 437 | procedure TScanForm.FormCreate(Sender: TObject); 438 | begin 439 | // resize main form to hide log output 440 | Self.Constraints.MaxWidth := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 441 | Self.Constraints.MinWidth := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 442 | Self.Width := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 443 | UtilSetNextFormLeft(Self); 444 | LogOutput.Clear; 445 | 446 | // welcome log output 447 | UtilInit(Self.LogOutput); 448 | UtilLog('Welcome to InsideBlue BLE Tool V0.8'); 449 | UtilLog('Copyright (C) 2023 Erik Lins'); 450 | UtilLog('https://github.com/eriklins/InsideBlue-BLE-Tool'); 451 | UtilLog('Uses SimpleBLE version ' + String(SimpleBleGetVersion())); 452 | UtilLog(''); 453 | 454 | // initialize BLE units 455 | BleInit; 456 | ScanInit; 457 | ConnectInit; 458 | 459 | // init help form 460 | if not HelpInit() then 461 | ButtonHelp.Enabled := false; 462 | 463 | // check if BLE adapter could be initialized and Bluetooth is enabled 464 | if not BleAdapterIsInitialized then begin 465 | ButtonStartScan.Enabled := false; 466 | LabelNofDevices.Caption := 'Could not initialize BLE adapter.'; 467 | UtilLog('Could not initialize BLE adapter.'); 468 | end else if not BleIsEnabled then begin 469 | ButtonStartScan.Enabled := false; 470 | LabelNofDevices.Caption := 'Bluetooth is disabled.'; 471 | UtilLog('Bluetooth is disabled.'); 472 | end; 473 | 474 | UtilLog('Init done.'); 475 | end; 476 | 477 | 478 | { Help button clicked } 479 | procedure TScanForm.ButtonHelpClick(Sender: TObject); 480 | begin 481 | ButtonHelp.Enabled := false; 482 | HelpShow(ButtonHelp); 483 | end; 484 | 485 | 486 | { Start / Stop Scan button clicked } 487 | procedure TScanForm.ButtonStartScanClick(Sender: TObject); 488 | begin 489 | if not FlagBleScanningActive then begin 490 | FlagBleScanningActive := true; 491 | EditFilterDeviceName.Enabled := false; 492 | EditFilterMacAddress.Enabled := false; 493 | EditFilterRssi.Enabled := false; 494 | ButtonClear.Enabled := false; 495 | ButtonStartScan.Caption := 'Stop Scan'; 496 | LabelNofDevices.Caption := IntToStr(PeripheralNofDevices) + ' Devices Found'; 497 | //ShowMessage('#paired=' + IntToStr(SimpleBleAdapterGetPairedPeripheralsCount(BleAdapter))); 498 | SimpleBleAdapterScanStart(BleAdapter); 499 | end else begin 500 | FlagBleScanningActive := false; 501 | EditFilterDeviceName.Enabled := true; 502 | EditFilterMacAddress.Enabled := true; 503 | EditFilterRssi.Enabled := true; 504 | ButtonClear.Enabled := true; 505 | ButtonStartScan.Caption := 'Start Scan'; 506 | LabelNofDevices.Caption := 'Press Start Scan to Discover Devices...'; 507 | //ShowMessage('#paired=' + IntToStr(SimpleBleAdapterGetPairedPeripheralsCount(BleAdapter))); 508 | SimpleBleAdapterScanStop(BleAdapter); 509 | end; 510 | end; 511 | 512 | 513 | { Clear scan list button clicked } 514 | procedure TScanForm.ButtonClearClick(Sender: TObject); 515 | var 516 | i: Integer; 517 | begin 518 | // delete form elements of devices 519 | for i := 0 to Length(PeripheralScanPanel)-1 do begin 520 | PeripheralScanPanel[i].PanelDeviceInfo.Free; 521 | PeripheralScanPanel[i].PanelDeviceInfo := Nil; 522 | end; 523 | // clear record 524 | SetLength(PeripheralScanPanel, 0); 525 | // clear all peripheral devices 526 | ScanClearPeripheralList; 527 | end; 528 | 529 | 530 | { BLE scan filter values (device name, mac address and rssi } 531 | procedure TScanForm.EditFilterDeviceNameEditingDone(Sender: TObject); 532 | begin 533 | BleScanFilter.DeviceName := EditFilterDeviceName.Caption; 534 | end; 535 | 536 | procedure TScanForm.EditFilterMacAddressEditingDone(Sender: TObject); 537 | begin 538 | BleScanFilter.MacAddress := LowerCase(EditFilterMacAddress.Caption); 539 | end; 540 | 541 | procedure TScanForm.EditFilterRssiEditingDone(Sender: TObject); 542 | begin 543 | if EditFilterRssi.Caption <> '' then 544 | BleScanFilter.Rssi := -StrToInt(EditFilterRssi.Caption) 545 | else 546 | BleScanFilter.Rssi := 0; 547 | end; 548 | 549 | 550 | { Connect to device button clicked } 551 | procedure TScanForm.ButtonConnectDeviceClick(Sender: TObject); 552 | var 553 | i: Integer; 554 | begin 555 | tButton(Sender).Enabled := false; 556 | i := TButton(Sender).Tag; 557 | ConnectDevice(BleScanData[i].DeviceName, BleScanData[i].MacAddress, BleScanData[i].PeripheralHandle, tButton(Sender)); 558 | end; 559 | 560 | 561 | { Checkbox service data ascii hex clicked } 562 | procedure TScanForm.CheckBoxServiceHexAsciiClick(Sender: TObject); 563 | var 564 | DeIdx, SvIdx: Integer; 565 | begin 566 | DeIdx := (TCheckBox(Sender).Tag shr TagPosDev) and $ff; 567 | SvIdx := (TCheckBox(Sender).Tag shr TagPosSrv) and $ff; 568 | if PeripheralScanPanel[DeIdx].CheckBoxSrvsHexAscii[SvIdx].State = cbChecked then 569 | PeripheralScanPanel[DeIdx].TextBoxServiceData[SvIdx].Caption := UtilDataToAscii(BleScanData[DeIdx].Services[SvIdx].Data, BleScanData[DeIdx].Services[SvIdx].DataLength) 570 | else 571 | PeripheralScanPanel[DeIdx].TextBoxServiceData[SvIdx].Caption := UtilDataToHex(BleScanData[DeIdx].Services[SvIdx].Data, BleScanData[DeIdx].Services[SvIdx].DataLength); 572 | end; 573 | 574 | 575 | { Checkbox manufacturer data ascii hex clicked } 576 | procedure TScanForm.CheckBoxManufHexAsciiClick(Sender: TObject); 577 | var 578 | DeIdx, SvIdx: Integer; 579 | begin 580 | DeIdx := (TCheckBox(Sender).Tag shr TagPosDev) and $ff; 581 | SvIdx := (TCheckBox(Sender).Tag shr TagPosSrv) and $ff; 582 | if PeripheralScanPanel[DeIdx].CheckBoxManufHexAscii[SvIdx].State = cbChecked then 583 | PeripheralScanPanel[DeIdx].TextBoxManufData[SvIdx].Caption := UtilDataToAscii(BleScanData[DeIdx].ManufacturerData[SvIdx].Data, BleScanData[DeIdx].ManufacturerData[SvIdx].DataLength) 584 | else 585 | PeripheralScanPanel[DeIdx].TextBoxManufData[SvIdx].Caption := UtilDataToHex(BleScanData[DeIdx].ManufacturerData[SvIdx].Data, BleScanData[DeIdx].ManufacturerData[SvIdx].DataLength); 586 | end; 587 | 588 | 589 | { log output checkbox clicked } 590 | procedure TScanForm.CheckBoxShowLogChange(Sender: TObject); 591 | begin 592 | if CheckBoxShowLog.State = cbChecked then begin 593 | // increase scan form width to show log output 594 | ScanForm.Constraints.MaxWidth := 0; 595 | ScanForm.Constraints.MinWidth := ButtonClearLog.Left + ButtonClearLog.Width + ButtonCopyLog.Width + 2*ScanPanelPaddingHorizontal; 596 | if ScanFormWidth = 0 then 597 | ScanForm.Width := ScrollBoxDevices.Left + ScrollBoxDevices.Width + 456 + ScanPanelPaddingHorizontal 598 | else 599 | ScanForm.Width := ScanFormWidth; 600 | ScanForm.ButtonCopyLog.Left := ScanForm.Width - ScanForm.ButtonCopyLog.Width - ScanPanelPaddingHorizontal; 601 | ScanForm.ButtonCopyLog.Anchors := [akTop, akRight]; 602 | UtilSetNextFormTop(ScanForm); 603 | UtilSetNextFormLeft(ScanForm); 604 | end else begin 605 | // decrease scan form width to hide log output 606 | ScanForm.ButtonCopyLog.Anchors := [akTop, akLeft]; 607 | ScanForm.Constraints.MaxWidth := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 608 | ScanForm.Constraints.MinWidth := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 609 | ScanFormWidth := ScanForm.Width; // remember width of form for later 610 | ScanForm.Width := ScrollBoxDevices.Left + ScrollBoxDevices.Width; 611 | UtilSetNextFormTop(ScanForm); 612 | UtilSetNextFormLeft(ScanForm); 613 | end; 614 | end; 615 | 616 | 617 | { Clear Log button clicked } 618 | procedure TScanForm.ButtonClearLogClick(Sender: TObject); 619 | begin 620 | LogOutput.Clear; 621 | end; 622 | 623 | 624 | { button copy log to clipboard clicked } 625 | procedure TScanForm.ButtonCopyLogClick(Sender: TObject); 626 | begin 627 | Clipboard.AsText := LogOutput.Text; 628 | end; 629 | 630 | 631 | { Form changed bounds, used to set coordinates for device form } 632 | procedure TScanForm.FormChangeBounds(Sender: TObject); 633 | begin 634 | UtilSetNextFormTop(ScanForm); 635 | UtilSetNextFormLeft(ScanForm); 636 | end; 637 | 638 | procedure TScanForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); 639 | begin 640 | SimpleBleAdapterReleaseHandle(BleAdapter); 641 | end; 642 | 643 | 644 | end. 645 | 646 | -------------------------------------------------------------------------------- /src/scan.pas: -------------------------------------------------------------------------------- 1 | unit Scan; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Dialogs, StrUtils, 9 | SimpleBle, Util, Ble; 10 | 11 | type 12 | { type: device data from BLE scanning } 13 | TBleScanData = record 14 | PeripheralHandle: TSimpleBlePeripheral; 15 | DeviceName: String; 16 | MacAddress: String; 17 | IsConnectable: Boolean; 18 | IsPaired: Boolean; 19 | TxPower: Integer; 20 | Rssi: Integer; 21 | ServicesCount: Integer; 22 | Services: array of TSimpleBleService; 23 | ManufacturerDataCount: Integer; 24 | ManufacturerData: array of TSimpleBleManufacturerData; 25 | UpdateForm: Boolean; 26 | IsConnected: Boolean; 27 | end; 28 | 29 | { type: filters for ble scanning } 30 | TBleScanFilter = record 31 | DeviceName: String; 32 | MacAddress: String; 33 | Rssi: Integer; 34 | end; 35 | 36 | procedure ScanClearPeripheralList; 37 | procedure ScanInit; 38 | 39 | var 40 | FlagBleScanningActive: Boolean = false; 41 | PeripheralNofDevices: Integer = 0; 42 | BleScanData: array of TBleScanData; 43 | BleScanFilter: TBleScanFilter; 44 | 45 | 46 | implementation 47 | 48 | 49 | { Callback functions for SimpleBLE library } 50 | 51 | { SimpleBLE callback for scan start } 52 | procedure AdapterOnScanStart(Adapter: TSimplebleAdapter; Userdata: PPointer); 53 | var 54 | Identifier: PChar; 55 | begin 56 | Identifier := SimpleBleAdapterIdentifier(Adapter); 57 | if Identifier = '' then 58 | Exit; 59 | UtilLog('Scanning started...'); 60 | end; 61 | 62 | 63 | { SimpleBLE callback for scan stop } 64 | procedure AdapterOnScanStop(Adapter: TSimplebleAdapter; Userdata: PPointer); 65 | var 66 | Identifier: PChar; 67 | begin 68 | Identifier := SimpleBleAdapterIdentifier(Adapter); 69 | if Identifier = '' then 70 | Exit; 71 | UtilLog('Scanning stopped.'); 72 | end; 73 | 74 | 75 | { SimpleBLE callback for scan found or updated } 76 | procedure AdapterOnScanFoundUpdated(Adapter: TSimplebleAdapter; Peripheral: TSimpleBlePeripheral; Userdata: PPointer); 77 | var 78 | AdapterIdentifier: PChar; 79 | PeripheralAddress: PChar; 80 | DevIdx, j, k: Integer; 81 | FlagNewData: Boolean; 82 | s: String; 83 | TmpManufacturerData: TSimpleBleManufacturerData; 84 | begin 85 | 86 | AdapterIdentifier := SimpleBleAdapterIdentifier(Adapter); 87 | PeripheralAddress := SimpleBlePeripheralAddress(Peripheral); 88 | 89 | if (AdapterIdentifier = '') or (PeripheralAddress = '') then 90 | Exit; 91 | 92 | // filter for device name 93 | if BleScanFilter.DeviceName <> '' then 94 | if not AnsiContainsText(string(SimpleBlePeripheralIdentifier(Peripheral)), BleScanFilter.DeviceName) then 95 | Exit; 96 | 97 | // filter for mac address 98 | if BleScanFilter.MacAddress <> '' then 99 | if not AnsiContainsText(string(SimpleBlePeripheralAddress(Peripheral)), BleScanFilter.MacAddress) then 100 | Exit; 101 | 102 | // filter for rssi 103 | if BleScanFilter.Rssi <> 0 then 104 | if SimpleBlePeripheralRssi(Peripheral) < BleScanFilter.Rssi then 105 | Exit; 106 | 107 | // check if device already exist 108 | DevIdx := 0; 109 | while DevIdx < PeripheralNofDevices do begin 110 | if BleScanData[DevIdx].MacAddress = string(SimpleBlePeripheralAddress(Peripheral)) then begin 111 | break; // DevIdx is now index of existing device 112 | end; 113 | Inc(DevIdx); 114 | end; 115 | 116 | // if device doesn't exist, we extend the array and increment nof devices 117 | if DevIdx = PeripheralNofDevices then begin 118 | SetLength(BleScanData, DevIdx + 1); 119 | //SetLength(PeripheralScanPanel, DevIdx + 1); 120 | Inc(PeripheralNofDevices); 121 | end; 122 | 123 | // Skip device in case of an active BLE connection 124 | if BleScanData[DevIdx].IsConnected then 125 | Exit; 126 | 127 | // populate device scan data 128 | BleScanData[DevIdx].PeripheralHandle := Peripheral; 129 | BleScanData[DevIdx].DeviceName := string(SimpleBlePeripheralIdentifier(Peripheral)); 130 | BleScanData[DevIdx].MacAddress := string(SimpleBlePeripheralAddress(Peripheral)); 131 | BleScanData[DevIdx].Rssi := SimpleBlePeripheralRssi(Peripheral); 132 | SimpleBlePeripheralIsConnectable(Peripheral, BleScanData[DevIdx].IsConnectable); 133 | BleScanData[DevIdx].TxPower := SimpleBlePeripheralTxPower(Peripheral); 134 | 135 | s := ''; 136 | if not BleScanData[DevIdx].IsConnectable then s := 'Not '; 137 | UtilLog('Dev: [' + UpperCase(BleScanData[DevIdx].MacAddress) + '] "' + BleScanData[DevIdx].DeviceName + '" ' + IntToStr(BleScanData[DevIdx].Rssi) + 'dBm ' + s + 'Connectable'); 138 | 139 | // check if we got advertised services 140 | if SimpleBlePeripheralServicesCount(Peripheral) > 0 then begin 141 | if BleScanData[DevIdx].ServicesCount = 0 then begin 142 | BleScanData[DevIdx].ServicesCount := SimpleBlePeripheralServicesCount(Peripheral); 143 | SetLength(BleScanData[DevIdx].Services, BleScanData[DevIdx].ServicesCount); 144 | end; 145 | for j := 0 to BleScanData[DevIdx].ServicesCount-1 do begin 146 | SimpleBlePeripheralServicesGet(Peripheral, j, BleScanData[DevIdx].Services[j]); 147 | SetString(s, BleScanData[DevIdx].Services[j].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 148 | UtilLog(' SV: ' + s); 149 | end; 150 | end; 151 | 152 | //if SimpleBlePeripheralServiceDataCount(Peripheral) > 0 then begin 153 | // if BleScanData[DevIdx].ServiceDataCount = 0 then begin 154 | // BleScanData[DevIdx].ServiceDataCount := SimpleBlePeripheralServiceDataCount(Peripheral); 155 | // SetLength(BleScanData[DevIdx].ServiceData, BleScanData[DevIdx].ServiceDataCount); 156 | // end; 157 | // for j := 0 to BleScanData[DevIdx].ServiceDataCount-1 do begin 158 | // SimpleBlePeripheralServiceDataGet(Peripheral, j, BleScanData[DevIdx].ServiceData[j]); 159 | // SetString(s, BleScanData[DevIdx].ServiceData[j].ServiceUuid.Value, SIMPLEBLE_UUID_STR_LEN-1); 160 | // UtilLog(' SV: ' + s); 161 | // UtilLog(' SD: ' + UtilDataToHex(BleScanData[DevIdx].ServiceData[j].Data, BleScanData[DevIdx].ServiceData[j].DataLength)); 162 | // end; 163 | //end; 164 | 165 | // check if we got manufacturer specific data 166 | if SimpleBlePeripheralManufacturerDataCount(Peripheral) > 0 then begin 167 | 168 | for k := 0 to SimpleBlePeripheralManufacturerDataCount(Peripheral) do begin 169 | SimpleBlePeripheralManufacturerDataGet(Peripheral, k, TmpManufacturerData); // store manuf data temporarily 170 | j := 0; 171 | FlagNewData := true; 172 | while j < BleScanData[DevIdx].ManufacturerDataCount do begin 173 | if BleScanData[DevIdx].ManufacturerData[j].ManufacturerId = TmpManufacturerData.ManufacturerId then begin 174 | FlagNewData := false; 175 | break; 176 | end; 177 | Inc(j); 178 | end; 179 | if FlagNewData then begin 180 | SetLength(BleScanData[DevIdx].ManufacturerData, j + 1); 181 | BleScanData[DevIdx].ManufacturerDataCount := j + 1; 182 | end; 183 | SimpleBlePeripheralManufacturerDataGet(Peripheral, k, BleScanData[DevIdx].ManufacturerData[j]); 184 | end; 185 | for j := 0 to BleScanData[DevIdx].ManufacturerDataCount-1 do begin 186 | UtilLog(' MD: ' + IntToHex(BleScanData[DevIdx].ManufacturerData[j].ManufacturerId, 4) + ':' + UtilDataToHex(BleScanData[DevIdx].ManufacturerData[j].Data, BleScanData[DevIdx].ManufacturerData[j].DataLength)); 187 | end; 188 | end; 189 | 190 | // set flag to update form elements in timer event 191 | BleScanData[DevIdx].UpdateForm := true; 192 | end; 193 | 194 | 195 | { Clear the list of scanned devices } 196 | procedure ScanClearPeripheralList; 197 | var 198 | i: Integer; 199 | connected: Boolean; 200 | begin 201 | // delete peripheral scan data and release ble peripheral handles 202 | for i := 0 to PeripheralNofDevices-1 do begin 203 | SimpleBlePeripheralIsConnected(BleScanData[i].PeripheralHandle, connected); 204 | if not connected then // only release handle if device is not connected! 205 | SimpleBlePeripheralReleaseHandle(BleScanData[i].PeripheralHandle); 206 | end; 207 | // reset number of devices and records for scan data and form elements 208 | PeripheralNofDevices := 0; 209 | SetLength(BleScanData, 0); 210 | end; 211 | 212 | 213 | procedure ScanInit; 214 | begin 215 | 216 | // register SimpleBLE scan callback functions 217 | SimpleBleAdapterSetCallbackOnScanStart(BleAdapter, @AdapterOnScanStart, Nil); 218 | SimpleBleAdapterSetCallbackOnScanStop(BleAdapter, @AdapterOnScanStop, Nil); 219 | SimpleBleAdapterSetCallbackOnScanFound(BleAdapter, @AdapterOnScanFoundUpdated, Nil); 220 | SimpleBleAdapterSetCallbackOnScanUpdated(BleAdapter, @AdapterOnScanFoundUpdated, Nil); 221 | 222 | end; 223 | 224 | end. 225 | 226 | -------------------------------------------------------------------------------- /src/simpleble.pas: -------------------------------------------------------------------------------- 1 | unit SimpleBle; 2 | 3 | {$mode ObjFPC}{$H+} 4 | {$macro on} 5 | 6 | { Lazarus / Free Pascal bindings for the cross-platform SimpleBLE library. 7 | 8 | Pascal bindings are Copyright (c) 2022-2023 Erik Lins and released under the MIT License. 9 | https://github.com/eriklins/Pascal-Bindings-For-SimpleBLE-Library 10 | 11 | The SimpleBLE library is Copyright (c) 2021-2022 Kevin Dewald and released under the MIT License. 12 | https://github.com/OpenBluetoothToolbox/SimpleBLE 13 | } 14 | 15 | {$UNDEF DYNAMIC_LOADING} 16 | {$IFDEF WINDOWS} 17 | {$DEFINE DYNAMIC_LOADING} { UNCOMMENT IF YOU WANT DYNAMIC LOADING } 18 | {$ENDIF} 19 | 20 | 21 | interface 22 | 23 | uses 24 | {$IFDEF UNIX} 25 | ctypes, 26 | {$ENDIF} 27 | {$IFDEF DYNAMIC_LOADING} 28 | Classes, SysUtils, DynLibs; 29 | {$ELSE} 30 | Classes, SysUtils; 31 | {$ENDIF} 32 | 33 | const 34 | {$IFDEF WINDOWS} 35 | SimpleBleExtLibrary = 'simpleble-c.dll'; 36 | {$ELSE} 37 | {$IFDEF DARWIN} 38 | SimpleBleExtLibrary = 'simpleble-c.dylib'; 39 | {$ELSE} 40 | SimpleBleExtLibrary = 'simpleble-c.so'; 41 | {$ENDIF} 42 | {$ENDIF} 43 | 44 | {$IFDEF FPC} 45 | {$PACKRECORDS C} 46 | {$ENDIF} 47 | 48 | //#define SIMPLEBLE_UUID_STR_LEN 37 // 36 characters + null terminator 49 | //#define SIMPLEBLE_CHARACTERISTIC_MAX_COUNT 16 50 | //#define SIMPLEBLE_DESCRIPTOR_MAX_COUNT 16 51 | //Note: in C array declaration the above is the number of elements, 52 | //hence in Pascal we need to subtract 1 in the array declaration 53 | //like array[0..SIMPLEBLE_UUID_STR_LEN-1] 54 | SIMPLEBLE_UUID_STR_LEN = 37; 55 | SIMPLEBLE_CHARACTERISTIC_MAX_COUNT = 16; 56 | SIMPLEBLE_DESCRIPTOR_MAX_COUNT = 16; 57 | 58 | 59 | { types from SimpleBLE types.h } 60 | 61 | type 62 | //typedef enum { 63 | // SIMPLEBLE_SUCCESS = 0, 64 | // SIMPLEBLE_FAILURE = 1, 65 | //} simpleble_err_t; 66 | TSimpleBleErr = (SIMPLEBLE_SUCCESS = 0, SIMPLEBLE_FAILURE = 1); 67 | 68 | //typedef struct { 69 | // char value[SIMPLEBLE_UUID_STR_LEN]; 70 | //} simpleble_uuid_t; 71 | TSimpleBleUuid = record 72 | Value: array[0..SIMPLEBLE_UUID_STR_LEN-1] of Char; 73 | end; 74 | 75 | //typedef struct { 76 | // simpleble_uuid_t uuid; 77 | //} simpleble_descriptor_t; 78 | TSimpleBleDescriptor = record 79 | Uuid: TSimpleBleUuid; 80 | end; 81 | 82 | //typedef struct { 83 | // simpleble_uuid_t uuid; 84 | // bool can_read; 85 | // bool can_write_request; 86 | // bool can_write_command; 87 | // bool can_notify; 88 | // bool can_indicate; 89 | // size_t descriptor_count; 90 | // simpleble_descriptor_t descriptors[SIMPLEBLE_DESCRIPTOR_MAX_COUNT]; 91 | //} simpleble_characteristic_t; 92 | TSimpleBleCharacteristic = record 93 | Uuid: TSimpleBleUuid; 94 | CanRead: Boolean; 95 | CanWriteRequest: Boolean; 96 | CanWriteCommand: Boolean; 97 | CanNotify: Boolean; 98 | CanIndicate: Boolean; 99 | DescriptorCount: NativeUInt; 100 | Descriptors: array[0..SIMPLEBLE_DESCRIPTOR_MAX_COUNT-1] of TSimpleBleDescriptor; 101 | end; 102 | 103 | //typedef struct { 104 | // simpleble_uuid_t uuid; 105 | // size_t data_length; 106 | // uint8_t data[27]; 107 | // // Note: The maximum length of a BLE advertisement is 31 bytes. 108 | // // The first byte will be the length of the field, 109 | // // the second byte will be the type of the field, 110 | // // the next two bytes will be the service UUID, 111 | // // and the remaining 27 bytes are the manufacturer data. 112 | // size_t characteristic_count; 113 | // simpleble_characteristic_t characteristics[SIMPLEBLE_CHARACTERISTIC_MAX_COUNT]; 114 | //} simpleble_service_t; 115 | TSimpleBleService = record 116 | Uuid: TSimpleBleUuid; 117 | DataLength: NativeUInt; 118 | Data: array[0..27-1] of Byte; 119 | CharacteristicCount: NativeUInt; 120 | Characteristics: array[0..SIMPLEBLE_CHARACTERISTIC_MAX_COUNT-1] of TSimpleBleCharacteristic; 121 | end; 122 | 123 | //typedef struct { 124 | // uint16_t manufacturer_id; 125 | // size_t data_length; 126 | // uint8_t data[27]; 127 | // // Note: The maximum length of a BLE advertisement is 31 bytes. 128 | // // The first byte will be the length of the field, 129 | // // the second byte will be the type of the field (0xFF for manufacturer data), 130 | // // the next two bytes will be the manufacturer ID, 131 | // // and the remaining 27 bytes are the manufacturer data. 132 | //} simpleble_manufacturer_data_t; 133 | TSimpleBleManufacturerData = record 134 | ManufacturerId: UInt16; 135 | DataLength: NativeUInt; 136 | Data: array[0..27-1] of Byte 137 | end; 138 | 139 | //typedef void* simpleble_adapter_t; 140 | //typedef void* simpleble_peripheral_t; 141 | TSimpleBleAdapter = NativeUInt; 142 | TSimpleBlePeripheral = NativeUInt; 143 | 144 | //typedef enum { 145 | // SIMPLEBLE_OS_WINDOWS = 0, 146 | // SIMPLEBLE_OS_MACOS = 1, 147 | // SIMPLEBLE_OS_LINUX = 2, 148 | //} simpleble_os_t; 149 | TSimpleBleOs = (SIMPLEBLE_OS_WINDOWS = 0, SIMPLEBLE_OS_MACOS = 1, SIMPLEBLE_OS_LINUX = 2); 150 | 151 | //typedef enum { 152 | // SIMPLEBLE_ADDRESS_TYPE_PUBLIC = 0, 153 | // SIMPLEBLE_ADDRESS_TYPE_RANDOM = 1, 154 | // SIMPLEBLE_ADDRESS_TYPE_UNSPECIFIED = 2, 155 | //} simpleble_address_type_t; 156 | TSimpleBleAddressType = (SIMPLEBLE_ADDRESS_TYPE_PUBLIC = 0, SIMPLEBLE_ADDRESS_TYPE_RANDOM = 1, SIMPLEBLE_ADDRESS_TYPE_UNSPECIFIED = 2); 157 | 158 | 159 | {$IFNDEF DYNAMIC_LOADING} 160 | 161 | { functions from SimpleBLE adapter.h } 162 | 163 | // new types for callback functions 164 | type 165 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_start(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, void* userdata), void* userdata); 166 | TSimpleBleCallbackScanStart = procedure(Adapter: TSimpleBleAdapter; UserData: PPointer); 167 | 168 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_stop(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, void* userdata), void* userdata); 169 | TSimpleBleCallbackScanStop = procedure(Adapter: TSimpleBleAdapter; UserData: PPointer); 170 | 171 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_updated(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata), void* userdata); 172 | TSimpleBleCallbackScanUpdated = procedure(Adapter: TSimpleBleAdapter; peripheral: TSimpleBleAdapter; UserData: PPointer); 173 | 174 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_found(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata), void* userdata); 175 | TSimpleBleCallbackScanFound = procedure(Adapter: TSimpleBleAdapter; peripheral: TSimpleBleAdapter; UserData: PPointer); 176 | 177 | //SIMPLEBLE_EXPORT bool simpleble_adapter_is_bluetooth_enabled(void); 178 | function SimpleBleAdapterIsBluetoothEnabled(): Boolean; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_is_bluetooth_enabled'; 179 | 180 | //SIMPLEBLE_EXPORT size_t simpleble_adapter_get_count(void); 181 | function SimpleBleAdapterGetCount(): NativeUInt; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_get_count'; 182 | 183 | //SIMPLEBLE_EXPORT simpleble_adapter_t simpleble_adapter_get_handle(size_t index); 184 | function SimpleBleAdapterGetHandle(Index: NativeUInt): TSimpleBleAdapter; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_get_handle'; 185 | 186 | //SIMPLEBLE_EXPORT void simpleble_adapter_release_handle(simpleble_adapter_t handle); 187 | procedure SimpleBleAdapterReleaseHandle(Handle: TSimpleBleAdapter); cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_release_handle'; 188 | 189 | //SIMPLEBLE_EXPORT char* simpleble_adapter_identifier(simpleble_adapter_t handle); 190 | function SimpleBleAdapterIdentifier(Handle: TSimpleBleAdapter): PChar; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_identifier'; 191 | 192 | //SIMPLEBLE_EXPORT char* simpleble_adapter_address(simpleble_adapter_t handle); 193 | function SimpleBleAdapterAddress(Handle: TSimpleBleAdapter): PChar; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_address'; 194 | 195 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_scan_start(simpleble_adapter_t handle); 196 | function SimpleBleAdapterScanStart(Handle: TSimpleBleAdapter): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_start'; 197 | 198 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_scan_stop(simpleble_adapter_t handle); 199 | function SimpleBleAdapterScanStop(Handle: TSimpleBleAdapter): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_stop'; 200 | 201 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_scan_is_active(simpleble_adapter_t handle, bool* active); 202 | function SimpleBleAdapterScanIsActive(Handle: TSimpleBleAdapter; var Active: Boolean): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_is_active'; 203 | 204 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_scan_for(simpleble_adapter_t handle, int timeout_ms); 205 | function SimpleBleAdapterScanFor(Handle: TSimpleBleAdapter; TimeoutMs: Integer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_for'; 206 | 207 | //SIMPLEBLE_EXPORT size_t simpleble_adapter_scan_get_results_count(simpleble_adapter_t handle); 208 | function SimpleBleAdapterScanGetResultsCount(Handle: TSimpleBleAdapter): NativeUInt; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_get_results_count'; 209 | 210 | //SIMPLEBLE_EXPORT simpleble_peripheral_t simpleble_adapter_scan_get_results_handle(simpleble_adapter_t handle, size_t index); 211 | function SimpleBleAdapterScanGetResultsHandle(Handle: TSimpleBleAdapter; Index: NativeUInt): TSimpleBlePeripheral; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_scan_get_results_handle'; 212 | 213 | //SIMPLEBLE_EXPORT size_t simpleble_adapter_get_paired_peripherals_count(simpleble_adapter_t handle); 214 | function SimpleBleAdapterGetPairedPeripheralsCount(Handle: TSimpleBleAdapter): NativeUInt; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_get_paired_peripherals_count'; 215 | 216 | //SIMPLEBLE_EXPORT simpleble_peripheral_t simpleble_adapter_get_paired_peripherals_handle(simpleble_adapter_t handle, size_t index); 217 | function SimpleBleAdapterGetPairedPeripheralsHandle(Handle: TSimpleBleAdapter; Index: NativeUInt): TSimpleBlePeripheral; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_get_paired_peripherals_handle'; 218 | 219 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_start(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, void* UserData), void* userdata); 220 | function SimpleBleAdapterSetCallbackOnScanStart(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanStart; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_set_callback_on_scan_start'; 221 | 222 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_stop(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, void* userdata), void* userdata); 223 | function SimpleBleAdapterSetCallbackOnScanStop(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanStop; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_set_callback_on_scan_stop'; 224 | 225 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_updated(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata), void* userdata); 226 | function SimpleBleAdapterSetCallbackOnScanUpdated(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanUpdated; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_set_callback_on_scan_updated'; 227 | 228 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_adapter_set_callback_on_scan_found(simpleble_adapter_t handle, void (*callback)(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata), void* userdata); 229 | function SimpleBleAdapterSetCallbackOnScanFound(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanFound; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_adapter_set_callback_on_scan_found'; 230 | 231 | 232 | { functions from SimpleBLE peripheral.h } 233 | 234 | // new types for callback functions 235 | type 236 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_set_callback_on_connected(simpleble_peripheral_t handle, void (*callback)(simpleble_peripheral_t peripheral, void* userdata), void* userdata); 237 | TSimpleBleCallbackOnConnected = procedure(Peripheral: TSimpleBlePeripheral; UserData: PPointer); 238 | 239 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_set_callback_on_disconnected(simpleble_peripheral_t handle, void (*callback)(simpleble_peripheral_t peripheral, void* userdata), void* userdata); 240 | TSimpleBleCallbackOnDisconnected = procedure(Peripheral: TSimpleBlePeripheral; UserData: PPointer); 241 | 242 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_notify(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, void (*callback)(simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length, void* userdata), void* userdata); 243 | TSimpleBleCallbackNotify = procedure(Service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt; UserData: PPointer); 244 | 245 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_indicate(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, void (*callback)(simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length, void* userdata), void* userdata); 246 | TSimpleBleCallbackIndicate = procedure(Service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt; UserData: PPointer); 247 | 248 | //SIMPLEBLE_EXPORT void simpleble_peripheral_release_handle(simpleble_peripheral_t handle); 249 | procedure SimpleBlePeripheralReleaseHandle(Handle: TSimpleBlePeripheral); cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_release_handle'; 250 | 251 | //SIMPLEBLE_EXPORT char* simpleble_peripheral_identifier(simpleble_peripheral_t handle); 252 | function SimpleBlePeripheralIdentifier(Handle: TSimpleBlePeripheral): PChar; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_identifier'; 253 | 254 | //SIMPLEBLE_EXPORT char* simpleble_peripheral_address(simpleble_peripheral_t handle); 255 | function SimpleBlePeripheralAddress(Handle: TSimpleBlePeripheral): PChar; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_address'; 256 | 257 | //SIMPLEBLE_EXPORT simpleble_address_type_t simpleble_peripheral_address_type(simpleble_peripheral_t handle); 258 | function SimpleBlePeripheralAddressType(Handle: TSimpleBlePeripheral): TSimpleBleAddressType; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_address_type'; 259 | 260 | //SIMPLEBLE_EXPORT int16_t simpleble_peripheral_rssi(simpleble_peripheral_t handle); 261 | function SimpleBlePeripheralRssi(Handle: TSimpleBlePeripheral): Int16; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_rssi'; 262 | 263 | //SIMPLEBLE_EXPORT int16_t simpleble_peripheral_tx_power(simpleble_peripheral_t handle); 264 | function SimpleBlePeripheralTxPower(Handle: TSimpleBlePeripheral): Int16; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_tx_power'; 265 | 266 | //SIMPLEBLE_EXPORT uint16_t simpleble_peripheral_mtu(simpleble_peripheral_t handle); 267 | function SimpleBlePeripheralMtu(Handle: TSimpleBlePeripheral): UInt16; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_mtu'; 268 | 269 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_connect(simpleble_peripheral_t handle); 270 | function SimpleBlePeripheralConnect(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_connect'; 271 | 272 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_disconnect(simpleble_peripheral_t handle); 273 | function SimpleBlePeripheralDisconnect(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_disconnect'; 274 | 275 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_is_connected(simpleble_peripheral_t handle, bool* connected); 276 | function SimpleBlePeripheralIsConnected(Handle: TSimpleBlePeripheral; var Connected: Boolean): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_is_connected'; 277 | 278 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_is_connectable(simpleble_peripheral_t handle, bool* connectable); 279 | function SimpleBlePeripheralIsConnectable(Handle: TSimpleBlePeripheral; var Connectable: Boolean): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_is_connectable'; 280 | 281 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_is_paired(simpleble_peripheral_t handle, bool* paired); 282 | function SimpleBlePeripheralIsPaired(Handle: TSimpleBlePeripheral; var Paired: Boolean): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_is_paired'; 283 | 284 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_unpair(simpleble_peripheral_t handle); 285 | function SimpleBlePeripheralUnpair(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_unpair'; 286 | 287 | //SIMPLEBLE_EXPORT size_t simpleble_peripheral_services_count(simpleble_peripheral_t handle); 288 | function SimpleBlePeripheralServicesCount(Handle: TSimpleBlePeripheral): NativeUInt; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_services_count'; 289 | 290 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_services_get(simpleble_peripheral_t handle, size_t index, simpleble_service_t* services); 291 | function SimpleBlePeripheralServicesGet(Handle: TSimpleBlePeripheral; Index: NativeUInt; var Services: TSimpleBleService): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_services_get'; 292 | 293 | //SIMPLEBLE_EXPORT size_t simpleble_peripheral_manufacturer_data_count(simpleble_peripheral_t handle); 294 | function SimpleBlePeripheralManufacturerDataCount(Handle: TSimpleBlePeripheral): NativeUInt; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_manufacturer_data_count'; 295 | 296 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_manufacturer_data_get(simpleble_peripheral_t handle, size_t index, simpleble_manufacturer_data_t* manufacturer_data); 297 | function SimpleBlePeripheralManufacturerDataGet(Handle: TSimpleBlePeripheral; Index: NativeUInt; var ManufacturerData: TSimpleBleManufacturerData): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_manufacturer_data_get'; 298 | 299 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_read(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, uint8_t** data, size_t* data_length); 300 | function SimpleBlePeripheralRead(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; var Data: PByte; var DataLength: NativeUInt): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_read'; 301 | 302 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_write_request(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length); 303 | function SimpleBlePeripheralWriteRequest(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_write_request'; 304 | 305 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_write_command(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length); 306 | function SimpleBlePeripheralWriteCommand(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_write_command'; 307 | 308 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_notify(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, void (*callback)(simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length, void* userdata), void* userdata); 309 | function SimpleBlePeripheralNotify(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Callback: TSimpleBleCallbackNotify; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_notify'; 310 | 311 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_indicate(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, void (*callback)(simpleble_uuid_t service, simpleble_uuid_t characteristic, const uint8_t* data, size_t data_length, void* userdata), void* userdata); 312 | function SimpleBlePeripheralIndicate(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Callback: TSimpleBleCallbackIndicate; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_indicate'; 313 | 314 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_unsubscribe(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic); 315 | function SimpleBlePeripheralUnsubscribe(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid):TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_unsubscribe'; 316 | 317 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_read_descriptor(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, simpleble_uuid_t descriptor, uint8_t** data, size_t* data_length); 318 | function SimpleBlePeripheralReadDescriptor(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Descriptor: TSimpleBleUuid; var Data: PByte; var DataLength: NativeUInt): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_read_descriptor'; 319 | 320 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_write_descriptor(simpleble_peripheral_t handle, simpleble_uuid_t service, simpleble_uuid_t characteristic, simpleble_uuid_t descriptor, const uint8_t* data, size_t data_length); 321 | function SimpleBlePeripheralWriteDescriptor(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Descriptor: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_write_descriptor'; 322 | 323 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_set_callback_on_connected(simpleble_peripheral_t handle, void (*callback)(simpleble_peripheral_t peripheral, void* userdata), void* userdata); 324 | function SimpleBlePeripheralSetCallbackOnConnected(Handle: TSimpleBlePeripheral; Callback: TSimpleBleCallbackOnConnected; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_set_callback_on_connected'; 325 | 326 | //SIMPLEBLE_EXPORT simpleble_err_t simpleble_peripheral_set_callback_on_disconnected(simpleble_peripheral_t handle, void (*callback)(simpleble_peripheral_t peripheral, void* userdata), void* userdata); 327 | function SimpleBlePeripheralSetCallbackOnDisconnected(Handle: TSimpleBlePeripheral; Callback: TSimpleBleCallbackOnDisconnected; UserData: PPointer): TSimpleBleErr; cdecl; external SimpleBleExtLibrary name 'simpleble_peripheral_set_callback_on_disconnected'; 328 | 329 | 330 | { functions from SimpleBLE simpleble.h } 331 | 332 | //SIMPLEBLE_EXPORT void simpleble_free(void* handle); 333 | procedure SimpleBleFree(var Handle); cdecl; external SimpleBleExtLibrary name 'simpleble_free'; 334 | 335 | 336 | { functions from SimpleBLE logging.h } 337 | 338 | type 339 | //typedef enum { 340 | // SIMPLEBLE_LOG_LEVEL_NONE = 0, 341 | // SIMPLEBLE_LOG_LEVEL_FATAL, 342 | // SIMPLEBLE_LOG_LEVEL_ERROR, 343 | // SIMPLEBLE_LOG_LEVEL_WARN, 344 | // SIMPLEBLE_LOG_LEVEL_INFO, 345 | // SIMPLEBLE_LOG_LEVEL_DEBUG, 346 | // SIMPLEBLE_LOG_LEVEL_VERBOSE 347 | //} simpleble_log_level_t; 348 | TSimpleBleLogLevel = (SIMPLEBLE_LOG_LEVEL_NONE = 0, 349 | SIMPLEBLE_LOG_LEVEL_FATAL = 1, 350 | SIMPLEBLE_LOG_LEVEL_ERROR = 2, 351 | SIMPLEBLE_LOG_LEVEL_WARN = 3, 352 | SIMPLEBLE_LOG_LEVEL_INFO = 4, 353 | SIMPLEBLE_LOG_LEVEL_DEBUG = 5, 354 | SIMPLEBLE_LOG_LEVEL_VERBOSE = 6); 355 | 356 | //typedef void (*simpleble_log_callback_t)( 357 | // simpleble_log_level_t level, 358 | // const char* module, 359 | // const char* file, 360 | // uint32_t line, 361 | // const char* function, 362 | // const char* message 363 | //); 364 | TCallbackLog = procedure(Level: TSimpleBleLogLevel; Module: PChar; LFile: PChar; Line: DWord; LFunction: PChar; LMessage: PChar); 365 | 366 | //SIMPLEBLE_EXPORT void simpleble_logging_set_level(simpleble_log_level_t level); 367 | procedure SimpleBleLoggingSetLevel(Level: TSimpleBleLogLevel); cdecl; external SimpleBleExtLibrary name 'simpleble_logging_set_level'; 368 | 369 | //SIMPLEBLE_EXPORT void simpleble_logging_set_callback(simpleble_log_callback_t callback); 370 | procedure SimpleBleloggingSetCallback(Callback: TCallbackLog); cdecl; external SimpleBleExtLibrary name 'simpleble_logging_set_callback'; 371 | 372 | 373 | { functions from SimpleBLE utils.h } 374 | 375 | //SIMPLEBLE_EXPORT simpleble_os_t simpleble_get_operating_system(void); 376 | function SimpleBleGetOperatingSystem(): TSimpleBleOs; cdecl; external SimpleBleExtLibrary name 'simpleble_get_operating_system'; 377 | 378 | //SIMPLEBLE_EXPORT const char* simpleble_get_version(void); 379 | function SimpleBleGetVersion(): PChar; cdecl; external SimpleBleExtLibrary name 'simpleble_get_version'; 380 | 381 | {$ELSE} 382 | 383 | // the below is for dynamically loading the DLL libraries on Windows only! 384 | 385 | 386 | // define function for dynamically loading/unloading the DLL 387 | function SimpleBleLoadLibrary(dllPath:string=''): Boolean; 388 | procedure SimpleBleUnloadLibrary(); 389 | 390 | 391 | { functions from SimpleBLE adapter.h } 392 | 393 | type 394 | TSimpleBleCallbackScanStart = procedure(Adapter: TSimpleBleAdapter; UserData: PPointer); 395 | TSimpleBleCallbackScanStop = procedure(Adapter: TSimpleBleAdapter; UserData: PPointer); 396 | TSimpleBleCallbackScanUpdated = procedure(Adapter: TSimpleBleAdapter; peripheral: TSimpleBleAdapter; UserData: PPointer); 397 | TSimpleBleCallbackScanFound = procedure(Adapter: TSimpleBleAdapter; peripheral: TSimpleBleAdapter; UserData: PPointer); 398 | 399 | var 400 | SimpleBleAdapterIsBluetoothEnabled : function() : Boolean; cdecl; 401 | SimpleBleAdapterGetCount : function() : NativeUInt; cdecl; 402 | SimpleBleAdapterGetHandle : function(Index: NativeUInt): TSimpleBleAdapter; cdecl; 403 | SimpleBleAdapterReleaseHandle : procedure(Handle: TSimpleBleAdapter); cdecl; 404 | SimpleBleAdapterIdentifier : function(Handle: TSimpleBleAdapter): PChar; cdecl; 405 | SimpleBleAdapterAddress : function(Handle: TSimpleBleAdapter): PChar; cdecl; 406 | SimpleBleAdapterScanStart : function(Handle: TSimpleBleAdapter): TSimpleBleErr; cdecl; 407 | SimpleBleAdapterScanStop : function(Handle: TSimpleBleAdapter): TSimpleBleErr; cdecl; 408 | SimpleBleAdapterScanIsActive : function(Handle: TSimpleBleAdapter; var Active: Boolean): TSimpleBleErr; cdecl; 409 | SimpleBleAdapterScanFor : function(Handle: TSimpleBleAdapter; TimeoutMs: Integer): TSimpleBleErr; cdecl; 410 | SimpleBleAdapterScanGetResultsCount : function(Handle: TSimpleBleAdapter): NativeUInt; cdecl; 411 | SimpleBleAdapterScanGetResultsHandle : function(Handle: TSimpleBleAdapter; Index: NativeUInt): TSimpleBlePeripheral; cdecl; 412 | SimpleBleAdapterGetPairedPeripheralsCount : function(Handle: TSimpleBleAdapter): NativeUInt; cdecl; 413 | SimpleBleAdapterGetPairedPeripheralsHandle : function(Handle: TSimpleBleAdapter; Index: NativeUInt): TSimpleBlePeripheral; cdecl; 414 | SimpleBleAdapterSetCallbackOnScanStart : function(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanStart; UserData: PPointer): TSimpleBleErr; cdecl; 415 | SimpleBleAdapterSetCallbackOnScanStop : function(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanStop; UserData: PPointer): TSimpleBleErr; cdecl; 416 | SimpleBleAdapterSetCallbackOnScanUpdated : function(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanUpdated; UserData: PPointer): TSimpleBleErr; cdecl; 417 | SimpleBleAdapterSetCallbackOnScanFound : function(Handle: TSimpleBleAdapter; Callback: TSimpleBleCallbackScanFound; UserData: PPointer): TSimpleBleErr; cdecl; 418 | 419 | 420 | { functions from SimpleBLE peripheral.h } 421 | 422 | type 423 | TSimpleBleCallbackOnConnected = procedure(peripheral: TSimpleBlePeripheral; UserData: PPointer); 424 | TSimpleBleCallbackOnDisconnected = procedure(peripheral: TSimpleBlePeripheral; UserData: PPointer); 425 | TSimpleBleCallbackNotify = procedure(service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt; UserData: PPointer); 426 | TSimpleBleCallbackIndicate = procedure(service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt; UserData: PPointer); 427 | 428 | var 429 | SimpleBlePeripheralReleaseHandle : procedure(Handle: TSimpleBlePeripheral); cdecl; 430 | SimpleBlePeripheralIdentifier : function(Handle: TSimpleBlePeripheral): PChar; cdecl; 431 | SimpleBlePeripheralAddress : function(Handle: TSimpleBlePeripheral): PChar; cdecl; 432 | SimpleBlePeripheralAddressType : function(Handle: TSimpleBlePeripheral): TSimpleBleAddressType; cdecl; 433 | SimpleBlePeripheralRssi : function(Handle: TSimpleBlePeripheral): Int16; cdecl; 434 | SimpleBlePeripheralTxPower : function(Handle: TSimpleBlePeripheral): Int16; cdecl; 435 | SimpleBlePeripheralMtu : function(Handle: TSimpleBlePeripheral): UInt16; cdecl; 436 | SimpleBlePeripheralConnect : function(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; 437 | SimpleBlePeripheralDisconnect : function(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; 438 | SimpleBlePeripheralIsConnected : function(Handle: TSimpleBlePeripheral; var connected: Boolean): TSimpleBleErr; cdecl; 439 | SimpleBlePeripheralIsConnectable : function(Handle: TSimpleBlePeripheral; var connectable: Boolean): TSimpleBleErr; cdecl; 440 | SimpleBlePeripheralIsPaired : function(Handle: TSimpleBlePeripheral; var paired: Boolean): TSimpleBleErr; cdecl; 441 | SimpleBlePeripheralUnpair : function(Handle: TSimpleBlePeripheral): TSimpleBleErr; cdecl; 442 | SimpleBlePeripheralServicesCount : function(Handle: TSimpleBlePeripheral): NativeUInt; cdecl; 443 | SimpleBlePeripheralServicesGet : function(Handle: TSimpleBlePeripheral; Index: NativeUInt; var Services: TSimpleBleService): TSimpleBleErr; cdecl; 444 | SimpleBlePeripheralManufacturerDataCount : function(Handle: TSimpleBlePeripheral): NativeUInt; cdecl; 445 | SimpleBlePeripheralManufacturerDataGet : function(Handle: TSimpleBlePeripheral; Index: NativeUInt; var ManufacturerData: TSimpleBleManufacturerData): TSimpleBleErr; cdecl; 446 | SimpleBlePeripheralRead : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; var Data: PByte; var DataLength: NativeUInt): TSimpleBleErr; cdecl; 447 | SimpleBlePeripheralWriteRequest : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; 448 | SimpleBlePeripheralWriteCommand : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; 449 | SimpleBlePeripheralNotify : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Callback: TSimpleBleCallbackNotify; UserData: PPointer): TSimpleBleErr; cdecl; 450 | SimpleBlePeripheralIndicate : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Callback: TSimpleBleCallbackIndicate; UserData: PPointer): TSimpleBleErr; cdecl; 451 | SimpleBlePeripheralUnsubscribe : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid):TSimpleBleErr; cdecl; 452 | SimpleBlePeripheralReadDescriptor : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Descriptor: TSimpleBleUuid; var Data: PByte; var DataLength: NativeUInt): TSimpleBleErr; cdecl; 453 | SimpleBlePeripheralWriteDescriptor : function(Handle: TSimpleBlePeripheral; service: TSimpleBleUuid; Characteristic: TSimpleBleUuid; Descriptor: TSimpleBleUuid; Data: PByte; DataLength: NativeUInt): TSimpleBleErr; cdecl; 454 | SimpleBlePeripheralSetCallbackOnConnected : function(Handle: TSimpleBlePeripheral; Callback: TSimpleBleCallbackOnConnected; UserData: PPointer): TSimpleBleErr; cdecl; 455 | SimpleBlePeripheralSetCallbackOnDisconnected : function(Handle: TSimpleBlePeripheral; Callback: TSimpleBleCallbackOnDisconnected; UserData: PPointer): TSimpleBleErr; cdecl; 456 | 457 | 458 | { functions from SimpleBLE simpleble.h } 459 | 460 | var 461 | SimpleBleFree : procedure(var Handle); cdecl; 462 | 463 | 464 | { functions from SimpleBLE logging.h } 465 | 466 | type 467 | TSimpleBleLogLevel = (SIMPLEBLE_LOG_LEVEL_NONE = 0, 468 | SIMPLEBLE_LOG_LEVEL_FATAL = 1, 469 | SIMPLEBLE_LOG_LEVEL_ERROR = 2, 470 | SIMPLEBLE_LOG_LEVEL_WARN = 3, 471 | SIMPLEBLE_LOG_LEVEL_INFO = 4, 472 | SIMPLEBLE_LOG_LEVEL_DEBUG = 5, 473 | SIMPLEBLE_LOG_LEVEL_VERBOSE = 6); 474 | 475 | TCallbackLog = procedure(Level: TSimpleBleLogLevel; Module: PChar; LFile: PChar; Line: DWord; LFunction: PChar; LMessage: PChar); 476 | 477 | var 478 | SimpleBleLoggingSetLevel : procedure(Level: TSimpleBleLogLevel); cdecl; 479 | SimpleBleloggingSetCallback : procedure(Callback: TCallbackLog); cdecl; 480 | 481 | 482 | { functions from SimpleBLE utils.h } 483 | 484 | //var 485 | SimpleBleGetOperatingSystem : function(): TSimpleBleOs; cdecl; 486 | SimpleBleGetVersion : function(): PChar; cdecl; 487 | 488 | {$ENDIF} 489 | 490 | 491 | implementation 492 | 493 | {$IFDEF DYNAMIC_LOADING} 494 | 495 | var 496 | hLib : TLibHandle = 0; 497 | 498 | 499 | { Clear the pointers to the functions and procedures } 500 | procedure ClearPointers; 501 | begin 502 | { functions from SimpleBLE adapter.h } 503 | pointer(SimpleBleAdapterIsBluetoothEnabled) := Nil; 504 | pointer(SimpleBleAdapterGetCount) := Nil; 505 | pointer(SimpleBleAdapterGetHandle) := Nil; 506 | pointer(SimpleBleAdapterReleaseHandle) := Nil; 507 | pointer(SimpleBleAdapterIdentifier) := Nil; 508 | pointer(SimpleBleAdapterAddress) := Nil; 509 | pointer(SimpleBleAdapterScanStart) := Nil; 510 | pointer(SimpleBleAdapterScanStop) := Nil; 511 | pointer(SimpleBleAdapterScanIsActive) := Nil; 512 | pointer(SimpleBleAdapterScanFor) := Nil; 513 | pointer(SimpleBleAdapterScanGetResultsCount) := Nil; 514 | pointer(SimpleBleAdapterScanGetResultsHandle) := Nil; 515 | pointer(SimpleBleAdapterGetPairedPeripheralsCount) := Nil; 516 | pointer(SimpleBleAdapterGetPairedPeripheralsHandle) := Nil; 517 | pointer(SimpleBleAdapterSetCallbackOnScanStart) := Nil; 518 | pointer(SimpleBleAdapterSetCallbackOnScanStop) := Nil; 519 | pointer(SimpleBleAdapterSetCallbackOnScanUpdated) := Nil; 520 | pointer(SimpleBleAdapterSetCallbackOnScanFound) := Nil; 521 | 522 | { functions from SimpleBLE peripheral.h } 523 | pointer(SimpleBlePeripheralReleaseHandle) := Nil; 524 | pointer(SimpleBlePeripheralIdentifier) := Nil; 525 | pointer(SimpleBlePeripheralAddress) := Nil; 526 | pointer(SimpleBlePeripheralAddressType) := Nil; 527 | pointer(SimpleBlePeripheralRssi) := Nil; 528 | pointer(SimpleBlePeripheralTxPower) := Nil; 529 | pointer(SimpleBlePeripheralMtu) := Nil; 530 | pointer(SimpleBlePeripheralConnect) := Nil; 531 | pointer(SimpleBlePeripheralDisconnect) := Nil; 532 | pointer(SimpleBlePeripheralIsConnected) := Nil; 533 | pointer(SimpleBlePeripheralIsConnectable) := Nil; 534 | pointer(SimpleBlePeripheralIsPaired) := Nil; 535 | pointer(SimpleBlePeripheralUnpair) := Nil; 536 | pointer(SimpleBlePeripheralServicesCount) := Nil; 537 | pointer(SimpleBlePeripheralServicesGet) := Nil; 538 | pointer(SimpleBlePeripheralManufacturerDataCount) := Nil; 539 | pointer(SimpleBlePeripheralManufacturerDataGet) := Nil; 540 | pointer(SimpleBlePeripheralRead) := Nil; 541 | pointer(SimpleBlePeripheralWriteRequest) := Nil; 542 | pointer(SimpleBlePeripheralWriteCommand) := Nil; 543 | pointer(SimpleBlePeripheralNotify) := Nil; 544 | pointer(SimpleBlePeripheralIndicate) := Nil; 545 | pointer(SimpleBlePeripheralUnsubscribe) := Nil; 546 | pointer(SimpleBlePeripheralReadDescriptor) := Nil; 547 | pointer(SimpleBlePeripheralWriteDescriptor) := Nil; 548 | pointer(SimpleBlePeripheralSetCallbackOnConnected) := Nil; 549 | pointer(SimpleBlePeripheralSetCallbackOnDisconnected) := Nil; 550 | 551 | { functions from SimpleBLE simpleble.h } 552 | pointer(SimpleBleFree) := Nil; 553 | 554 | { functions from SimpleBLE logging.h } 555 | pointer(SimpleBleLoggingSetLevel) := Nil; 556 | pointer(SimpleBleloggingSetCallback) := Nil; 557 | 558 | { functions from SimpleBLE utils.h } 559 | pointer(SimpleBleGetOperatingSystem) := Nil; 560 | pointer(SimpleBleGetVersion) := Nil; 561 | 562 | end; 563 | 564 | 565 | { Load the DLL file with an optional path specified } 566 | function SimpleBleLoadLibrary(dllPath:string=''): Boolean; 567 | begin 568 | result := false; 569 | ClearPointers; 570 | if dllPath <> '' then begin 571 | if not DirectoryExists(dllPath) then exit; 572 | if rightstr(dllPath,1) <> DirectorySeparator then dllPath := dllPath + DirectorySeparator; 573 | if not FileExists(dllPath + SimpleBleExtLibrary) then exit; 574 | hLib := LoadLibrary(PChar(dllPath + SimpleBleExtLibrary)); 575 | end else begin 576 | hLib := LoadLibrary(PChar(SimpleBleExtLibrary)); 577 | end; 578 | if hLib = 0 then exit; 579 | 580 | try 581 | { functions from SimpleBLE adapter.h } 582 | pointer(SimpleBleAdapterIsBluetoothEnabled) := GetProcedureAddress(hLib, 'simpleble_adapter_is_bluetooth_enabled'); 583 | pointer(SimpleBleAdapterGetCount) := GetProcedureAddress(hLib, 'simpleble_adapter_get_count'); 584 | pointer(SimpleBleAdapterGetHandle) := GetProcedureAddress(hLib, 'simpleble_adapter_get_handle'); 585 | pointer(SimpleBleAdapterReleaseHandle) := GetProcedureAddress(hLib, 'simpleble_adapter_release_handle'); 586 | pointer(SimpleBleAdapterIdentifier) := GetProcedureAddress(hLib, 'simpleble_adapter_identifier'); 587 | pointer(SimpleBleAdapterAddress) := GetProcedureAddress(hLib, 'simpleble_adapter_address'); 588 | pointer(SimpleBleAdapterScanStart) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_start'); 589 | pointer(SimpleBleAdapterScanStop) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_stop'); 590 | pointer(SimpleBleAdapterScanIsActive) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_is_active'); 591 | pointer(SimpleBleAdapterScanFor) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_for'); 592 | pointer(SimpleBleAdapterScanGetResultsCount) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_get_results_count'); 593 | pointer(SimpleBleAdapterScanGetResultsHandle) := GetProcedureAddress(hLib, 'simpleble_adapter_scan_get_results_handle'); 594 | pointer(SimpleBleAdapterGetPairedPeripheralsCount) := GetProcedureAddress(hLib, 'simpleble_adapter_get_paired_peripherals_count'); 595 | pointer(SimpleBleAdapterGetPairedPeripheralsHandle) := GetProcedureAddress(hLib, 'simpleble_adapter_get_paired_peripherals_handle'); 596 | pointer(SimpleBleAdapterSetCallbackOnScanStart) := GetProcedureAddress(hLib, 'simpleble_adapter_set_callback_on_scan_start'); 597 | pointer(SimpleBleAdapterSetCallbackOnScanStop) := GetProcedureAddress(hLib, 'simpleble_adapter_set_callback_on_scan_stop'); 598 | pointer(SimpleBleAdapterSetCallbackOnScanUpdated) := GetProcedureAddress(hLib, 'simpleble_adapter_set_callback_on_scan_updated'); 599 | pointer(SimpleBleAdapterSetCallbackOnScanFound) := GetProcedureAddress(hLib, 'simpleble_adapter_set_callback_on_scan_found'); 600 | 601 | { functions from SimpleBLE peripheral.h } 602 | pointer(SimpleBlePeripheralReleaseHandle) := GetProcedureAddress(hLib, 'simpleble_peripheral_release_handle'); 603 | pointer(SimpleBlePeripheralIdentifier) := GetProcedureAddress(hLib, 'simpleble_peripheral_identifier'); 604 | pointer(SimpleBlePeripheralAddress) := GetProcedureAddress(hLib, 'simpleble_peripheral_address'); 605 | pointer(SimpleBlePeripheralAddressType) := GetProcedureAddress(hLib, 'simpleble_peripheral_address_type'); 606 | pointer(SimpleBlePeripheralRssi) := GetProcedureAddress(hLib, 'simpleble_peripheral_rssi'); 607 | pointer(SimpleBlePeripheralTxPower) := GetProcedureAddress(hLib, 'simpleble_peripheral_tx_power'); 608 | pointer(SimpleBlePeripheralMtu) := GetProcedureAddress(hLib, 'simpleble_peripheral_mtu'); 609 | pointer(SimpleBlePeripheralConnect) := GetProcedureAddress(hLib, 'simpleble_peripheral_connect'); 610 | pointer(SimpleBlePeripheralDisconnect) := GetProcedureAddress(hLib, 'simpleble_peripheral_disconnect'); 611 | pointer(SimpleBlePeripheralIsConnected) := GetProcedureAddress(hLib, 'simpleble_peripheral_is_connected'); 612 | pointer(SimpleBlePeripheralIsConnectable) := GetProcedureAddress(hLib, 'simpleble_peripheral_is_connectable'); 613 | pointer(SimpleBlePeripheralIsPaired) := GetProcedureAddress(hLib, 'simpleble_peripheral_is_paired'); 614 | pointer(SimpleBlePeripheralUnpair) := GetProcedureAddress(hLib, 'simpleble_peripheral_unpair'); 615 | pointer(SimpleBlePeripheralServicesCount) := GetProcedureAddress(hLib, 'simpleble_peripheral_services_count'); 616 | pointer(SimpleBlePeripheralServicesGet) := GetProcedureAddress(hLib, 'simpleble_peripheral_services_get'); 617 | pointer(SimpleBlePeripheralManufacturerDataCount) := GetProcedureAddress(hLib, 'simpleble_peripheral_manufacturer_data_count'); 618 | pointer(SimpleBlePeripheralManufacturerDataGet) := GetProcedureAddress(hLib, 'simpleble_peripheral_manufacturer_data_get'); 619 | pointer(SimpleBlePeripheralRead) := GetProcedureAddress(hLib, 'simpleble_peripheral_read'); 620 | pointer(SimpleBlePeripheralWriteRequest) := GetProcedureAddress(hLib, 'simpleble_peripheral_write_request'); 621 | pointer(SimpleBlePeripheralWriteCommand) := GetProcedureAddress(hLib, 'simpleble_peripheral_write_command'); 622 | pointer(SimpleBlePeripheralNotify) := GetProcedureAddress(hLib, 'simpleble_peripheral_notify'); 623 | pointer(SimpleBlePeripheralIndicate) := GetProcedureAddress(hLib, 'simpleble_peripheral_indicate'); 624 | pointer(SimpleBlePeripheralUnsubscribe) := GetProcedureAddress(hLib, 'simpleble_peripheral_unsubscribe'); 625 | pointer(SimpleBlePeripheralReadDescriptor) := GetProcedureAddress(hLib, 'simpleble_peripheral_read_descriptor'); 626 | pointer(SimpleBlePeripheralWriteDescriptor) := GetProcedureAddress(hLib, 'simpleble_peripheral_write_descriptor'); 627 | pointer(SimpleBlePeripheralSetCallbackOnConnected) := GetProcedureAddress(hLib, 'simpleble_peripheral_set_callback_on_connected'); 628 | pointer(SimpleBlePeripheralSetCallbackOnDisconnected) := GetProcedureAddress(hLib, 'simpleble_peripheral_set_callback_on_disconnected'); 629 | 630 | { functions from SimpleBLE simpleble.h } 631 | pointer(SimpleBleFree) := GetProcedureAddress(hLib, 'simpleble_free'); 632 | 633 | { functions from SimpleBLE logging.h } 634 | pointer(SimpleBleLoggingSetLevel) := GetProcedureAddress(hLib, 'simpleble_logging_set_level'); 635 | pointer(SimpleBleloggingSetCallback) := GetProcedureAddress(hLib, 'simpleble_logging_set_callback'); 636 | 637 | { functions from SimpleBLE utils.h } 638 | pointer(SimpleBleGetOperatingSystem) := GetProcedureAddress(hLib, 'simpleble_get_operating_system'); 639 | pointer(SimpleBleGetVersion) := GetProcedureAddress(hLib, 'simpleble_get_version'); 640 | 641 | except 642 | SimpleBleUnloadLibrary; 643 | exit; 644 | end; 645 | 646 | if 647 | { functions from SimpleBLE adapter.h } 648 | (pointer(SimpleBleAdapterIsBluetoothEnabled) = Nil) or 649 | (pointer(SimpleBleAdapterGetCount) = Nil) or 650 | (pointer(SimpleBleAdapterGetHandle) = Nil) or 651 | (pointer(SimpleBleAdapterReleaseHandle) = Nil) or 652 | (pointer(SimpleBleAdapterIdentifier) = Nil) or 653 | (pointer(SimpleBleAdapterAddress) = Nil) or 654 | (pointer(SimpleBleAdapterScanStart) = Nil) or 655 | (pointer(SimpleBleAdapterScanStop) = Nil) or 656 | (pointer(SimpleBleAdapterScanIsActive) = Nil) or 657 | (pointer(SimpleBleAdapterScanFor) = Nil) or 658 | (pointer(SimpleBleAdapterScanGetResultsCount) = Nil) or 659 | (pointer(SimpleBleAdapterScanGetResultsHandle) = Nil) or 660 | (pointer(SimpleBleAdapterGetPairedPeripheralsCount) = Nil) or 661 | (pointer(SimpleBleAdapterGetPairedPeripheralsHandle) = Nil) or 662 | (pointer(SimpleBleAdapterSetCallbackOnScanStart) = Nil) or 663 | (pointer(SimpleBleAdapterSetCallbackOnScanStop) = Nil) or 664 | (pointer(SimpleBleAdapterSetCallbackOnScanUpdated) = Nil) or 665 | (pointer(SimpleBleAdapterSetCallbackOnScanFound) = Nil) or 666 | 667 | { functions from SimpleBLE peripheral.h } 668 | (pointer(SimpleBlePeripheralReleaseHandle) = Nil) or 669 | (pointer(SimpleBlePeripheralIdentifier) = Nil) or 670 | (pointer(SimpleBlePeripheralAddress) = Nil) or 671 | (pointer(SimpleBlePeripheralAddressType) = Nil) or 672 | (pointer(SimpleBlePeripheralRssi) = Nil) or 673 | (pointer(SimpleBlePeripheralTxPower) = Nil) or 674 | (pointer(SimpleBlePeripheralMtu) = Nil) or 675 | (pointer(SimpleBlePeripheralConnect) = Nil) or 676 | (pointer(SimpleBlePeripheralDisconnect) = Nil) or 677 | (pointer(SimpleBlePeripheralIsConnected) = Nil) or 678 | (pointer(SimpleBlePeripheralIsConnectable) = Nil) or 679 | (pointer(SimpleBlePeripheralIsPaired) = Nil) or 680 | (pointer(SimpleBlePeripheralUnpair) = Nil) or 681 | (pointer(SimpleBlePeripheralServicesCount) = Nil) or 682 | (pointer(SimpleBlePeripheralServicesGet) = Nil) or 683 | (pointer(SimpleBlePeripheralManufacturerDataCount) = Nil) or 684 | (pointer(SimpleBlePeripheralManufacturerDataGet) = Nil) or 685 | (pointer(SimpleBlePeripheralRead) = Nil) or 686 | (pointer(SimpleBlePeripheralWriteRequest) = Nil) or 687 | (pointer(SimpleBlePeripheralWriteCommand) = Nil) or 688 | (pointer(SimpleBlePeripheralNotify) = Nil) or 689 | (pointer(SimpleBlePeripheralIndicate) = Nil) or 690 | (pointer(SimpleBlePeripheralUnsubscribe) = Nil) or 691 | (pointer(SimpleBlePeripheralReadDescriptor) = Nil) or 692 | (pointer(SimpleBlePeripheralWriteDescriptor) = Nil) or 693 | (pointer(SimpleBlePeripheralSetCallbackOnConnected) = Nil) or 694 | (pointer(SimpleBlePeripheralSetCallbackOnDisconnected) = Nil) or 695 | 696 | { functions from SimpleBLE simpleble.h } 697 | (pointer(SimpleBleFree) = Nil) or 698 | 699 | { functions from SimpleBLE logging.h } 700 | (pointer(SimpleBleLoggingSetLevel) = Nil) or 701 | (pointer(SimpleBleloggingSetCallback) = Nil) or 702 | 703 | { functions from SimpleBLE utils.h } 704 | (pointer(SimpleBleGetOperatingSystem) = Nil) or 705 | (pointer(SimpleBleGetVersion) = Nil) 706 | 707 | then 708 | begin 709 | //writeln('Fail'); 710 | SimpleBleUnloadLibrary; 711 | exit; 712 | end; 713 | //writeln('Success'); 714 | result:=true; 715 | end; 716 | 717 | 718 | { Unload the DLL } 719 | procedure SimpleBleUnloadLibrary(); 720 | begin 721 | ClearPointers; 722 | if hLib <> 0 then 723 | begin 724 | UnloadLibrary(hLib); 725 | hLib := 0; 726 | end; 727 | end; 728 | 729 | {$ENDIF} 730 | 731 | end. 732 | 733 | -------------------------------------------------------------------------------- /src/uartterminal.lfm: -------------------------------------------------------------------------------- 1 | object TerminalForm: TTerminalForm 2 | Left = 199 3 | Height = 560 4 | Top = 683 5 | Width = 801 6 | VertScrollBar.Visible = False 7 | Caption = 'InsideBlue - Virtual Serial Port Terminal' 8 | ClientHeight = 560 9 | ClientWidth = 801 10 | Constraints.MinHeight = 320 11 | Constraints.MinWidth = 800 12 | OnChangeBounds = FormChangeBounds 13 | OnClose = FormClose 14 | LCLVersion = '3.99.0.0' 15 | object TextBoxDeviceName: TEdit 16 | Left = 8 17 | Height = 28 18 | Top = 8 19 | Width = 248 20 | AutoSelect = False 21 | Font.Height = -15 22 | Font.Style = [fsBold] 23 | ParentFont = False 24 | ReadOnly = True 25 | TabOrder = 0 26 | Text = 'TextBoxDeviceName' 27 | end 28 | object LabelMacAddress: TLabel 29 | Left = 264 30 | Height = 15 31 | Top = 8 32 | Width = 184 33 | Caption = 'MAC Address [AA:BB:CC:DD:EE:FF]' 34 | ParentFont = False 35 | end 36 | object ButtonSend: TButton 37 | Left = 689 38 | Height = 24 39 | Top = 64 40 | Width = 104 41 | Anchors = [akTop, akRight] 42 | Caption = 'Send' 43 | OnClick = ButtonSendClick 44 | TabOrder = 6 45 | end 46 | object CheckBoxReceiveCR: TCheckBox 47 | Left = 224 48 | Height = 19 49 | Top = 40 50 | Width = 49 51 | Caption = '<CR>' 52 | Checked = True 53 | State = cbChecked 54 | TabOrder = 3 55 | end 56 | object CheckBoxReceiveLF: TCheckBox 57 | Left = 280 58 | Height = 19 59 | Top = 40 60 | Width = 46 61 | Caption = '<LF>' 62 | TabOrder = 4 63 | end 64 | object LabelLineEndingReceive: TLabel 65 | Left = 168 66 | Height = 19 67 | Top = 40 68 | Width = 48 69 | AutoSize = False 70 | Caption = 'Receive:' 71 | Layout = tlCenter 72 | end 73 | object ComboBoxSendLine: TComboBox 74 | Left = 8 75 | Height = 25 76 | Top = 64 77 | Width = 665 78 | Anchors = [akTop, akLeft, akRight] 79 | Font.CharSet = ANSI_CHARSET 80 | Font.Height = -13 81 | Font.Name = 'Source Code Pro' 82 | Font.Pitch = fpFixed 83 | Font.Quality = fqDraft 84 | ItemHeight = 17 85 | ParentFont = False 86 | TabOrder = 5 87 | end 88 | object MemoReceiveData: TMemo 89 | Left = 8 90 | Height = 457 91 | Top = 96 92 | Width = 785 93 | Anchors = [akTop, akLeft, akRight, akBottom] 94 | Font.CharSet = ANSI_CHARSET 95 | Font.Height = -13 96 | Font.Name = 'Source Code Pro' 97 | Font.Pitch = fpFixed 98 | Font.Quality = fqDraft 99 | Lines.Strings = ( 100 | 'MemoReceiveData' 101 | ) 102 | ParentFont = False 103 | ReadOnly = True 104 | ScrollBars = ssBoth 105 | TabOrder = 7 106 | WordWrap = False 107 | end 108 | object LabelLineEndingSend: TLabel 109 | Left = 8 110 | Height = 19 111 | Top = 40 112 | Width = 32 113 | AutoSize = False 114 | Caption = 'Send:' 115 | Layout = tlCenter 116 | end 117 | object CheckBoxSendCR: TCheckBox 118 | Left = 48 119 | Height = 19 120 | Top = 40 121 | Width = 49 122 | Caption = '<CR>' 123 | Checked = True 124 | State = cbChecked 125 | TabOrder = 1 126 | end 127 | object CheckBoxSendLF: TCheckBox 128 | Left = 104 129 | Height = 19 130 | Top = 40 131 | Width = 46 132 | Caption = '<LF>' 133 | TabOrder = 2 134 | end 135 | object LabelHeader: TLabel 136 | Left = 529 137 | Height = 30 138 | Top = 4 139 | Width = 265 140 | Alignment = taRightJustify 141 | Anchors = [akTop, akRight] 142 | Caption = 'Virtual Serial Port Terminal' 143 | Font.CharSet = ANSI_CHARSET 144 | Font.Color = 13402176 145 | Font.Height = -21 146 | Font.Pitch = fpVariable 147 | Font.Quality = fqDraft 148 | Font.Style = [fsBold] 149 | ParentFont = False 150 | end 151 | object TextBoxCharLen: TEdit 152 | Left = 625 153 | Height = 20 154 | Top = 39 155 | Width = 48 156 | Anchors = [akTop, akRight] 157 | AutoSize = False 158 | NumbersOnly = True 159 | OnEditingDone = TextBoxCharLenEditingDone 160 | TabOrder = 8 161 | Text = 'TextBoxCharLen' 162 | end 163 | object LabelCharLength: TLabel 164 | Left = 489 165 | Height = 19 166 | Top = 40 167 | Width = 136 168 | Anchors = [akTop, akRight] 169 | AutoSize = False 170 | Caption = 'RX Characteristic Length:' 171 | Layout = tlCenter 172 | end 173 | object CheckBoxReceiveJson: TCheckBox 174 | Left = 332 175 | Height = 19 176 | Top = 40 177 | Width = 70 178 | Caption = '...} (JSON)' 179 | TabOrder = 9 180 | end 181 | object CheckBoxUseWriteReq: TCheckBox 182 | Left = 691 183 | Height = 19 184 | Top = 40 185 | Width = 103 186 | Alignment = taLeftJustify 187 | Anchors = [akTop, akRight] 188 | Caption = 'Use WR Request' 189 | OnChange = CheckBoxUseWriteReqChange 190 | TabOrder = 10 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /src/uartterminal.pas: -------------------------------------------------------------------------------- 1 | unit UartTerminal; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | interface 6 | 7 | uses 8 | Classes, SysUtils, Forms, StdCtrls, Controls, Graphics, Dialogs, ExtCtrls, 9 | SimpleBle, Util, Ble; 10 | 11 | type 12 | { TTerminalForm } 13 | TTerminalForm = class(TForm) 14 | ButtonSend: TButton; 15 | CheckBoxReceiveCR: TCheckBox; 16 | CheckBoxReceiveJson: TCheckBox; 17 | CheckBoxUseWriteReq: TCheckBox; 18 | CheckBoxSendCR: TCheckBox; 19 | CheckBoxReceiveLF: TCheckBox; 20 | CheckBoxSendLF: TCheckBox; 21 | ComboBoxSendLine: TComboBox; 22 | LabelCharLength: TLabel; 23 | TextBoxCharLen: TEdit; 24 | LabelHeader: TLabel; 25 | LabelLineEndingReceive: TLabel; 26 | LabelLineEndingSend: TLabel; 27 | LabelMacAddress: TLabel; 28 | MemoReceiveData: TMemo; 29 | TextBoxDeviceName: TEdit; 30 | procedure ButtonSendClick(Sender: TObject); 31 | procedure CheckBoxUseWriteReqChange(Sender: TObject); 32 | procedure FormChangeBounds(Sender: TObject); 33 | procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); 34 | procedure TextBoxCharLenEditingDone(Sender: TObject); 35 | 36 | private 37 | 38 | public 39 | 40 | end; 41 | 42 | procedure UartTerminalStart(PerHandle: TSimpleBlePeripheral; DevName: string; MacAddr: string; SvUuid: TSimpleBleUuid; HasWrCmd: Boolean; HasWrReq: Boolean; restore: TPanel); 43 | procedure UartTerminalStop(PerHandle: TSimpleBlePeripheral); 44 | function UartTerminalIsActive(PerHandle: TSimpleBlePeripheral): Boolean; 45 | 46 | var 47 | TerminalForm: array of TTerminalForm; 48 | 49 | 50 | implementation 51 | 52 | {$R *.lfm} 53 | 54 | type 55 | TBleVspTerminal = record 56 | Handle: TSimpleBlePeripheral; 57 | DeviceName: string; 58 | MacAddress: string; 59 | UuidService: TSimpleBleUuid; 60 | ServiceName: string; 61 | UuidRx: TSimpleBleUuid; 62 | UuidTx: TSimpleBleUuid; 63 | UuidModemIn: TSimpleBleUuid; 64 | HasModemIn: Boolean; 65 | UuidModemOut: TSimpleBleUuid; 66 | HasModemOut: Boolean; 67 | RxCharLength: Integer; 68 | HasVspWriteCmd: Boolean; 69 | HasVspWriteReq: Boolean; 70 | IsActive: Boolean; 71 | end; 72 | 73 | var 74 | VspTerminal: array of TBleVspTerminal; 75 | RestorePanel: TPanel; 76 | FlagUseWriteReq: Boolean; 77 | 78 | { TTerminalForm } 79 | 80 | { Close vsp terminal } 81 | procedure TTerminalForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); 82 | var 83 | i: Integer; 84 | begin 85 | i := TForm(Sender).Tag; 86 | 87 | // unsubscribe from uart characteristics 88 | if SimpleBlePeripheralUnsubscribe(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidTx) = SIMPLEBLE_FAILURE then 89 | UtilLog('Unsubscribing from UART TX characteristic failed.') 90 | else 91 | UtilLog('Unsubscribed from UART TX characteristic.'); 92 | if VspTerminal[i].HasModemOut then 93 | if SimpleBlePeripheralUnsubscribe(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidModemOut) = SIMPLEBLE_FAILURE then 94 | UtilLog('Unsubscribing from UART ModemOut characteristic failed.') 95 | else 96 | UtilLog('Unsubscribed from UART ModemOut characteristic.'); 97 | 98 | // clean up 99 | UtilSetNextFormLeft(TerminalForm[i], true); 100 | Delete(VspTerminal, i, 1); 101 | RestorePanel.Enabled := true; 102 | end; 103 | 104 | 105 | { Get RX characteristic length from edit box } 106 | procedure TTerminalForm.TextBoxCharLenEditingDone(Sender: TObject); 107 | var 108 | i: Integer; 109 | begin 110 | if TextBoxCharLen.Caption <> '' then begin 111 | i := StrToInt(TextBoxCharLen.Caption); 112 | if i <= 0 then begin 113 | ShowMessage('RX Characteristic length must be >= 0'); 114 | exit; 115 | end; 116 | VspTerminal[0].RxCharLength := StrToInt(TextBoxCharLen.Caption); 117 | end; 118 | end; 119 | 120 | 121 | { Button send line clicked } 122 | procedure TTerminalForm.ButtonSendClick(Sender: TObject); 123 | var 124 | i, j: Integer; 125 | Buffer: array of Byte; 126 | ChData: PByte; 127 | ChLen: NativeUInt; 128 | s: string; 129 | exist: Boolean; 130 | begin 131 | SetLength(Buffer, CharDescMaxLength); 132 | ChData := PByte(Buffer); 133 | 134 | // read string from edit field 135 | s := TerminalForm[0].ComboBoxSendLine.Caption; 136 | 137 | // append line endings 138 | if TerminalForm[0].CheckBoxSendCR.State = cbChecked then 139 | s := s + #13; 140 | if TerminalForm[0].CheckBoxSendLF.State = cbChecked then 141 | s := s + #10; 142 | 143 | // send data and split into chunks according to rx characteristic length 144 | ChLen := Length(s); 145 | i := 0; 146 | j := 0; 147 | while i < ChLen do begin 148 | Buffer[j] := Byte(s[i+1]); 149 | i := i + 1; 150 | j := j + 1; 151 | if (j = VspTerminal[0].RxCharLength) or (i = ChLen) then begin 152 | if FlagUseWriteReq then begin 153 | if SimpleBlePeripheralWriteRequest(VspTerminal[0].Handle, VspTerminal[0].UuidService, VspTerminal[0].UuidRx, ChData, j) = SIMPLEBLE_FAILURE then begin 154 | ShowMessage('Failed to send data (WR request). Check TX max characters.'); 155 | UtilLog('Failed to send uart terminal data.'); 156 | exit; 157 | end; 158 | end else begin 159 | if SimpleBlePeripheralWriteCommand(VspTerminal[0].Handle, VspTerminal[0].UuidService, VspTerminal[0].UuidRx, ChData, j) = SIMPLEBLE_FAILURE then begin 160 | ShowMessage('Failed to send data (WR command). Check TX max characters.'); 161 | UtilLog('Failed to send uart terminal data.'); 162 | exit; 163 | end; 164 | end; 165 | j := 0; 166 | end; 167 | end; 168 | 169 | // append string to history if not existing 170 | if TerminalForm[0].ComboBoxSendLine.Items[0] = '' then 171 | TerminalForm[0].ComboBoxSendLine.Items.Clear; 172 | exist := false; 173 | for i := 0 to TerminalForm[0].ComboBoxSendLine.Items.Count-1 do 174 | if TerminalForm[0].ComboBoxSendLine.Items[i] = TerminalForm[0].ComboBoxSendLine.Caption then begin 175 | exist := true; 176 | break; 177 | end; 178 | if not exist then 179 | TerminalForm[0].ComboBoxSendLine.Items.Add(TerminalForm[0].ComboBoxSendLine.Caption); 180 | end; 181 | 182 | procedure TTerminalForm.CheckBoxUseWriteReqChange(Sender: TObject); 183 | begin 184 | if TerminalForm[0].CheckBoxUseWriteReq.State = cbChecked then 185 | FlagUseWriteReq := true 186 | else 187 | FlagUseWriteReq := false; 188 | end; 189 | 190 | procedure TTerminalForm.FormChangeBounds(Sender: TObject); 191 | var 192 | i: Integer; 193 | begin 194 | i := TForm(Sender).Tag; 195 | 196 | UtilSetNextFormTop(TerminalForm[i]); 197 | UtilSetNextFormLeft(TerminalForm[i]); 198 | end; 199 | 200 | 201 | { Callback function on notification from tx characteristic } 202 | procedure UartTxOnNotify(SvUuid: TSimpleBleUuid; ChUuid: TSimpleBleUuid; Data: PByte; Len: NativeUInt; UserData: PPointer); 203 | var 204 | i: Integer; 205 | c: Char; 206 | s: String; 207 | begin 208 | s := ''; 209 | for i := 0 to Len-1 do begin 210 | c := Char(Data[i]); 211 | if (c <> #13) and (c <> #10) then // check for <cr> or <lf> 212 | s := s + c 213 | else begin // check different line ending charcters 214 | if (TerminalForm[0].CheckBoxReceiveCR.State = cbChecked) and (c = #13) then 215 | s := s + LineEnding; 216 | if (TerminalForm[0].CheckBoxReceiveLF.State = cbChecked) and (c = #10) then 217 | s := s + LineEnding; 218 | end; 219 | 220 | // if JSON closing bracket, then append line ending 221 | if (TerminalForm[0].CheckBoxReceiveJson.State = cbChecked) and (c = #125) then 222 | s := s + LineEnding; 223 | end; 224 | TerminalForm[0].MemoReceiveData.Lines[Pred(TerminalForm[0].MemoReceiveData.Lines.Count)] := TerminalForm[0].MemoReceiveData.Lines[Pred(TerminalForm[0].MemoReceiveData.Lines.Count)] + s; 225 | end; 226 | 227 | 228 | { Callback function on notification from modem out characteristic } 229 | procedure UartModemOutOnNotify(SvUuid: TSimpleBleUuid; ChUuid: TSimpleBleUuid; Data: PByte; Len: NativeUInt; UserData: PPointer); 230 | begin 231 | // disable send button if Modem Out = 0 232 | if(Data[0] = Byte($00)) then begin 233 | UtilLog('Received ModemOut notification: STOP.'); 234 | TerminalForm[0].ButtonSend.Caption := 'Stopped...'; 235 | TerminalForm[0].ButtonSend.Enabled := false; 236 | // enable send button if Modem Out = 1 237 | end else if(Data[0] = Byte($01)) then begin 238 | UtilLog('Received ModemOut notification: CLEAR.'); 239 | TerminalForm[0].ButtonSend.Caption := 'Send'; 240 | TerminalForm[0].ButtonSend.Enabled := true; 241 | end else begin 242 | UtilLog('Received undefined ModemOut notification: ' + IntToStr(Data[0])); 243 | end; 244 | end; 245 | 246 | 247 | { Start a vsp uart terminal to the device with given peripheral handle and service uuid } 248 | procedure UartTerminalStart(PerHandle: TSimpleBlePeripheral; DevName: string; MacAddr: string; SvUuid: TSimpleBleUuid; HasWrCmd: Boolean; HasWrReq: Boolean; restore: TPanel); 249 | var 250 | i, j: Integer; 251 | Buffer: array of Byte; 252 | ChData: PByte; 253 | ChLen: NativeUInt; 254 | begin 255 | 256 | SetLength(Buffer, CharDescMaxLength); 257 | ChData := PByte(Buffer); 258 | 259 | // need to limit to one terminal due to missing support in SimpleBle library 260 | if Length(VspTerminal) = 1 then begin 261 | ShowMessage('Currently only one uart terminal is supported.'); 262 | exit; 263 | end; 264 | 265 | // increment terminals and increase some arrays 266 | i := Length(VspTerminal); 267 | SetLength(VspTerminal, i+1); 268 | SetLength(TerminalForm, i+1); 269 | 270 | VspTerminal[i].Handle := PerHandle; 271 | VspTerminal[i].UuidService := SvUuid; 272 | VspTerminal[i].DeviceName := DevName; 273 | VspTerminal[i].MacAddress := MacAddr; 274 | VspTerminal[i].IsActive := true; 275 | VspTerminal[i].RxCharLength := SimpleBlePeripheralMtu(VspTerminal[i].Handle); 276 | VspTerminal[i].HasVspWriteCmd := HasWrCmd; 277 | VspTerminal[i].HasVspWriteReq := HasWrReq; 278 | RestorePanel := restore; 279 | 280 | // search rx/tx/modem uart characteristics of service 281 | for j := 0 to Length(VspServiceUuids)-1 do begin 282 | if CompareChar(SvUuid.Value, VspServiceUuids[j].Uuid.Value, SIMPLEBLE_UUID_STR_LEN-1) = 0 then begin 283 | VspTerminal[i].ServiceName := VspServiceUuids[j].Name; 284 | VspTerminal[i].UuidRx := VspCharacteristicUuids[VspServiceUuids[j].ChRx].Uuid; 285 | VspTerminal[i].UuidTx := VspCharacteristicUuids[VspServiceUuids[j].ChTx].Uuid; 286 | if VspServiceUuids[j].ChModemIn < 0 then // modem characteristics are optional 287 | VspTerminal[i].HasModemIn := false 288 | else begin 289 | VspTerminal[i].HasModemIn := True; 290 | VspTerminal[i].UuidModemIn := VspCharacteristicUuids[VspServiceUuids[j].ChModemIn].Uuid; 291 | end; 292 | if VspServiceUuids[j].ChModemOut < 0 then // modem characteristics are optional 293 | VspTerminal[i].HasModemOut := false 294 | else begin 295 | VspTerminal[i].HasModemOut := True; 296 | VspTerminal[i].UuidModemOut := VspCharacteristicUuids[VspServiceUuids[j].ChModemOut].Uuid; 297 | end; 298 | end; 299 | end; 300 | 301 | // create the form 302 | Application.CreateForm(TTerminalForm, TerminalForm[i]); 303 | TerminalForm[i].Tag := i; 304 | TerminalForm[i].Top := UtilGetNextFormTop(); 305 | TerminalForm[i].Left := UtilGetNextFormLeft(TerminalForm[i]); 306 | if VspTerminal[i].DeviceName = '' then begin 307 | UtilLog('Open UART terminal: "<unknown name>" [' + UpperCase(VspTerminal[i].MacAddress) + '] - ' + VspTerminal[i].ServiceName); 308 | TerminalForm[i].Caption := '"<unknown name>" [' + UpperCase(VspTerminal[i].MacAddress) + '] - ' + VspTerminal[i].ServiceName + ' - Virtual Uart Terminal'; 309 | end else begin 310 | UtilLog('Open UART terminal: "' + VspTerminal[i].DeviceName + '" [' + UpperCase(VspTerminal[i].MacAddress) + '] - ' + VspTerminal[i].ServiceName); 311 | TerminalForm[i].Caption := '"' + VspTerminal[i].DeviceName + '" [' + UpperCase(VspTerminal[i].MacAddress) + '] - ' + VspTerminal[i].ServiceName + ' - Virtual Uart Terminal'; 312 | end; 313 | TerminalForm[i].TextBoxDeviceName.Caption := VspTerminal[i].DeviceName; 314 | TerminalForm[i].LabelMacAddress.Caption := 'MAC Address [' + UpperCase(VspTerminal[i].MacAddress) + ']'; 315 | TerminalForm[i].TextBoxCharLen.Caption := IntToStr(VspTerminal[i].RxCharLength); 316 | if VspTerminal[i].HasVspWriteCmd and VspTerminal[i].HasVspWriteReq then 317 | FlagUseWriteReq := false 318 | else begin 319 | if VspTerminal[i].HasVspWriteCmd then begin 320 | TerminalForm[i].CheckBoxUseWriteReq.Visible := false; 321 | FlagUseWriteReq := false; 322 | end; 323 | if VspTerminal[i].HasVspWriteReq then begin 324 | TerminalForm[i].CheckBoxUseWriteReq.Visible := false; 325 | FlagUseWriteReq := true; 326 | end; 327 | end; 328 | TerminalForm[i].Show; 329 | 330 | // subscribe to uart tx characteristic notifications 331 | if SimpleBlePeripheralNotify(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidTx, @UartTxOnNotify, Nil) = SIMPLEBLE_FAILURE then begin 332 | UtilLog('Subscribing to UART TX characteristic failed.'); 333 | ShowMessage('Cannot subsribe to UART TX characteristic.'); 334 | TerminalForm[i].Close; 335 | Exit; 336 | end; 337 | UtilLog('Subscribed to UART TX characteristic.'); 338 | 339 | // subscribe to uart ModemOut characteristic notifications 340 | if VspTerminal[i].HasModemOut then begin 341 | if SimpleBlePeripheralNotify(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidModemOut, @UartModemOutOnNotify, Nil) = SIMPLEBLE_FAILURE then begin 342 | UtilLog('Subscribing to UART ModemOut characteristic failed, will continue without.'); 343 | VspTerminal[i].HasModemOut := false; 344 | end else 345 | UtilLog('Subscribed to UART ModemOut characteristic'); 346 | end; 347 | 348 | // set position for next form 349 | UtilSetNextFormTop(TerminalForm[i]); 350 | UtilSetNextFormLeft(TerminalForm[i]); 351 | 352 | // clear send line and memobox 353 | TerminalForm[i].ComboBoxSendLine.Items.Clear; 354 | TerminalForm[i].ComboBoxSendLine.Items.Add(''); 355 | TerminalForm[i].MemoReceiveData.Clear; 356 | 357 | // signal CTS over ModemIn characteristic if available 358 | if VspTerminal[i].HasModemIn then begin 359 | Buffer[0] := Byte($01); 360 | ChLen := 1; 361 | if VspTerminal[i].HasVspWriteReq then begin 362 | if SimpleBlePeripheralWriteRequest(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidModemIn, ChData, ChLen) = SIMPLEBLE_FAILURE then begin 363 | ShowMessage('Failed to signal ModemIn CTS with WR request.'); 364 | UtilLog('Failed to signal ModemIn CTS with WR request.'); 365 | end; 366 | end else if VspTerminal[i].HasVspWriteCmd then begin 367 | if SimpleBlePeripheralWriteCommand(VspTerminal[i].Handle, VspTerminal[i].UuidService, VspTerminal[i].UuidModemIn, ChData, ChLen) = SIMPLEBLE_FAILURE then begin 368 | ShowMessage('Failed to signal ModemIn CTS with WR command.'); 369 | UtilLog('Failed to signal ModemIn CTS with WR command.'); 370 | end; 371 | end; 372 | end; 373 | 374 | end; 375 | 376 | 377 | { Stop vsp terminal, unsubsribe characteristics, clean up } 378 | procedure UartTerminalStop(PerHandle: TSimpleBlePeripheral); 379 | var 380 | i: Integer; 381 | begin 382 | // find vsp terminal and terminal form array index for given peripheral handle 383 | i := 0; 384 | while i < Length(VspTerminal) do begin 385 | if VspTerminal[i].Handle = PerHandle then 386 | break; 387 | Inc(i); 388 | end; 389 | 390 | // close form (also unsubscribes from notifications etc. 391 | TerminalForm[i].Close; 392 | end; 393 | 394 | 395 | { Check if there is a vsp terminal active for given device } 396 | function UartTerminalIsActive(PerHandle: TSimpleBlePeripheral): Boolean; 397 | var 398 | i: Integer; 399 | begin 400 | Result := false; 401 | i := 0; 402 | while i < Length(VspTerminal) do begin 403 | if (VspTerminal[i].Handle = PerHandle) and (VspTerminal[i].IsActive) then begin 404 | Result := true; 405 | Exit; 406 | end; 407 | Inc(i); 408 | end; 409 | end; 410 | 411 | 412 | end. 413 | 414 | -------------------------------------------------------------------------------- /src/util.pas: -------------------------------------------------------------------------------- 1 | unit Util; 2 | 3 | {$mode ObjFPC}{$H+} 4 | 5 | 6 | interface 7 | 8 | uses 9 | Classes, SysUtils, StrUtils, Forms, StdCtrls, 10 | Ble; 11 | 12 | type 13 | TArrayOfByte = array of Byte; 14 | 15 | procedure UtilInit(memo: TMemo); 16 | procedure UtilLog(s: string); 17 | 18 | procedure UtilSetNextFormLeft(f: TForm; reset: Boolean = false); 19 | function UtilGetNextFormLeft(f: TForm): Integer; 20 | procedure UtilSetNextFormTop(f: TForm); 21 | function UtilGetNextFormTop(): Integer; 22 | 23 | function UtilDataToAscii(data: array of Byte; len: Integer): String; 24 | function UtilDataToHex(data: array of Byte; len: Integer): String; 25 | function UtilAsciiToData(data: string; var len: Integer): TArrayOfByte; 26 | function UtilHexToData(data: string; var len: Integer): TArrayOfByte; 27 | function UtilStringIsHex(s: string): Boolean; 28 | 29 | 30 | implementation 31 | 32 | const 33 | NextFormMargin = 8; 34 | 35 | var 36 | Log: TMemo; 37 | NextFormTop : Integer; 38 | NextFormLeft: Integer; 39 | 40 | 41 | { Initialize util functions } 42 | procedure UtilInit(memo: TMemo); 43 | begin 44 | Log := memo; 45 | end; 46 | 47 | 48 | { expose log output function to other units } 49 | procedure UtilLog(s: string); 50 | begin 51 | Log.Append(s); 52 | end; 53 | 54 | 55 | { Functions handling start coordinates of upcoming form windows } 56 | procedure UtilSetNextFormLeft(f: TForm; reset: Boolean = false); 57 | begin 58 | if not reset then 59 | NextFormLeft := f.Left + f.Width + NextFormMargin 60 | else 61 | NextFormLeft := NextFormLeft - f.Width - NextFormMargin; 62 | end; 63 | 64 | function UtilGetNextFormLeft(f: TForm): Integer; 65 | begin 66 | Result := NextFormLeft; 67 | if (NextFormLeft + f.Width) > f.Monitor.Width then 68 | Result := f.Monitor.Width - f.Width - NextFormMargin; 69 | end; 70 | 71 | procedure UtilSetNextFormTop(f: TForm); 72 | begin 73 | NextFormTop := f.Top; 74 | end; 75 | 76 | function UtilGetNextFormTop(): Integer; 77 | begin 78 | Result := NextFormTop; 79 | end; 80 | 81 | 82 | { Convert data bytes to ASCII string } 83 | function UtilDataToAscii(data: array of Byte; len: Integer): String; 84 | var 85 | i: Integer; 86 | c: Byte; 87 | s: String; 88 | begin 89 | s := ''; 90 | i := 0; 91 | while i < len do begin 92 | c := data[i]; 93 | if (c >= 32) and (c <= 127) then 94 | s := s + AnsiChar(c) 95 | else 96 | s := s + '■'; // ALT-254 should be a solid square block to indicate non-ascii bytes 97 | Inc(i); 98 | end; 99 | Result := s; 100 | end; 101 | 102 | 103 | { Convert data bytes to HEX string } 104 | function UtilDataToHex(data: array of Byte; len: Integer): String; 105 | var 106 | i: Integer; 107 | s: String; 108 | begin 109 | s := ''; 110 | i := 0; 111 | while i < len do begin 112 | s := s + IntToHex(data[i], 2); 113 | Inc(i); 114 | end; 115 | Result := s; 116 | end; 117 | 118 | 119 | { Convert ASCII string to array of bytes} 120 | function UtilAsciiToData(data: string; var len: Integer): TArrayOfByte; 121 | var 122 | i: Integer; 123 | s: TArrayOfByte; 124 | begin 125 | SetLength(s, CharDescMaxLength); 126 | i := 0; 127 | while i < Length(data) do begin 128 | s[i] := Byte(data[i+1]); 129 | Inc(i); 130 | end; 131 | len := i; 132 | Result := s; 133 | end; 134 | 135 | 136 | { Convert HEX string to array of bytes } 137 | function UtilHexToData(data: string; var len: Integer): TArrayOfByte; 138 | var 139 | i: Integer; 140 | Buffer: TArrayOfByte; 141 | t: string; 142 | begin 143 | SetLength(Buffer, CharDescMaxLength); 144 | len := Length(data) div 2; 145 | i := 0; 146 | while i < len do begin 147 | t := Copy(data, (i*2)+1, 2); // get two characters (one hex number) from the string (index starts at one!) 148 | Buffer[i] := Byte(Hex2Dec(t)); 149 | Inc(i); 150 | end; 151 | Result := Buffer; 152 | end; 153 | 154 | 155 | { Test if string only contains valid hex numbers and is of even length } 156 | function UtilStringIsHex(s: string): Boolean; 157 | var 158 | i, len: Integer; 159 | c: Byte; 160 | begin 161 | len := Length(s); 162 | if Odd(len) then begin // string must of even length 163 | Result := false; 164 | Exit; 165 | end; 166 | i := 1; 167 | while i <= len do begin 168 | c := Byte(s[i]); 169 | if not ((c >= 48) and (c <= 57) or (c >= 65) and (c <= 70) or (c >= 97) and (c <= 102)) then begin // 0-9 or A-F or a-f 170 | Result := false; 171 | Exit; 172 | end; 173 | Inc(i); 174 | end; 175 | Result := true; 176 | end; 177 | 178 | 179 | end. 180 | 181 | -------------------------------------------------------------------------------- /utils/BtAssignedNumbersToPascalUnit.py: -------------------------------------------------------------------------------- 1 | # BtAssignedNumbersToPascal.py 2 | # 3 | # This Python script converts the files 4 | # - service_uuids.json 5 | # - characteristic_uuids.json 6 | # - descriptor_uuids.json 7 | # - company_ids.json 8 | # into a Pascal unit with constant array definitions for that information. 9 | # 10 | # The original files are available from https://github.com/NordicSemiconductor/bluetooth-numbers-database/tree/master/v1 11 | # 12 | # Erik Lins, 2023, (https://github.com/eriklins) 13 | 14 | import sys 15 | import re 16 | import json 17 | from urllib.request import urlopen 18 | 19 | # open again for writing and parse line by line 20 | outfile = open("assignednumbers.pas", "w", encoding="utf-8") 21 | 22 | # print header and start of Pascal unit 23 | print("""unit AssignedNumbers; 24 | 25 | {$mode ObjFPC}{$H+} 26 | 27 | { Bluetooth SiG Assigned Numbers for 28 | - Service UUIDs 29 | - Characteristic UUIDs 30 | - Descriptor UUIDs 31 | - Company IDs 32 | 33 | Converted from https://github.com/NordicSemiconductor/bluetooth-numbers-database/tree/master/v1 34 | into a Pascal unit with BtAssignedNumbersToPascal.py Python script. 35 | 36 | Erik Lins, 2023, (https://github.com/eriklins) 37 | } 38 | 39 | interface 40 | 41 | uses 42 | Classes, SysUtils, SimpleBle; 43 | 44 | type 45 | TServiceUuids = record 46 | Uuid: TSimpleBleUuid; 47 | Name: String; 48 | end; 49 | 50 | TCharacteristicUuids = record 51 | Uuid: TSimpleBleUuid; 52 | Name: String; 53 | end; 54 | 55 | TDescriptorUuids = record 56 | Uuid: TSimpleBleUuid; 57 | Name: String; 58 | end; 59 | 60 | TCompanyIds = record 61 | Code: String; 62 | Name: String; 63 | end; 64 | 65 | const 66 | """, file=outfile) 67 | 68 | # service_uuids.json 69 | url = urlopen("https://raw.githubusercontent.com/NordicSemiconductor/bluetooth-numbers-database/master/v1/service_uuids.json") 70 | data = json.loads(url.read()) 71 | print(" ServiceUuids: array of TServiceUuids = (", file=outfile) 72 | for i in data: 73 | print(" (Uuid: (Value: '"+i['uuid'].lower()+"'); Name: '"+i['name']+"'),", file=outfile) 74 | print(" (Uuid: (Value: ''); Name: '')", file=outfile) 75 | print(" );", file=outfile) 76 | print("", file=outfile) 77 | 78 | # characteristic_uuids.json 79 | url = urlopen("https://raw.githubusercontent.com/NordicSemiconductor/bluetooth-numbers-database/master/v1/characteristic_uuids.json") 80 | data = json.loads(url.read()) 81 | print(" CharacteristicUuids: array of TCharacteristicUuids = (", file=outfile) 82 | for i in data: 83 | print(" (Uuid: (Value: '"+i['uuid'].lower()+"'); Name: '"+i['name']+"'),", file=outfile) 84 | print(" (Uuid: (Value: ''); Name: '')", file=outfile) 85 | print(" );", file=outfile) 86 | print("", file=outfile) 87 | 88 | # descriptor_uuids.json 89 | url = urlopen("https://raw.githubusercontent.com/NordicSemiconductor/bluetooth-numbers-database/master/v1/descriptor_uuids.json") 90 | data = json.loads(url.read()) 91 | print(" DescriptorUuids: array of TDescriptorUuids = (", file=outfile) 92 | for i in data: 93 | print(" (Uuid: (Value: '"+i['uuid'].lower()+"'); Name: '"+i['name']+"'),", file=outfile) 94 | print(" (Uuid: (Value: ''); Name: '')", file=outfile) 95 | print(" );", file=outfile) 96 | print("", file=outfile) 97 | 98 | # company_ids.json 99 | url = urlopen("https://raw.githubusercontent.com/NordicSemiconductor/bluetooth-numbers-database/master/v1/company_ids.json") 100 | data = json.loads(url.read()) 101 | print(" CompanyIds: array of TCompanyIds = (", file=outfile) 102 | for i in data: 103 | print(" (Code: '"+format(i['code'],'04x')+"'; Name: '"+re.sub(r"(?i)\'"," ",i['name'])+"'),", file=outfile) 104 | print(" (Code: ''; Name: '')", file=outfile) 105 | print(" );", file=outfile) 106 | print("", file=outfile) 107 | 108 | # print footer 109 | print(""" 110 | implementation 111 | 112 | end. 113 | 114 | """, file=outfile) 115 | 116 | 117 | # close output file 118 | outfile.close() 119 | 120 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | # Python Script 2 | This is a Python script which grabs the list of Bluetooth Assigned Numbers for GAP/GATT services, characteristics, descriptors and company IDs from [this github repo](https://github.com/NordicSemiconductor/bluetooth-numbers-database) and converts the data into a Pascal unit with records. 3 | 4 | InsideBlue BLE Tool uses this unit to map UUIDs from BLE devices to service/characteristic/descriptor names and company names. 5 | 6 | ## License 7 | Copyright (C) 2023 Erik Lins 8 | 9 | This project is released under the MIT License. 10 | --------------------------------------------------------------------------------