├── LICENSE ├── README.md ├── img ├── ble-scout_connect.png ├── ble-scout_log.png └── ble-scout_main.png └── src ├── CMakeLists.txt ├── ble.c ├── ble.h ├── ble.hxx ├── blescout.c ├── blescout.log ├── bt_assigned_numbers.h ├── connect.c ├── connect.h ├── connect.hxx ├── help.c ├── help.h ├── help.hxx ├── help_rtf.h ├── logwin.c ├── logwin.h ├── logwin.hxx ├── scan.h ├── util.c ├── util.h ├── util.hxx └── vsp_services.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ER!K 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 | # BLE Scout - Bluetooth LE Tool 2 | 3 | BLE Scout is a cross-platform desktop application leveraging the PC integrated Bluetooth LE controller for accessing Bluetooth LE devices. It supports scanning for BLE advertisements, displaying the exposed advert data, connecting to devices, reading their GATT table and read/write/notify/indicate charcteristics. 4 | 5 | ## Main Scan Window 6 | After start the main scan window opens and provides common controls for BLE device discovery. 7 | 8 | ![Main Scan Window](img/ble-scout_main.png) 9 | 10 | ### Start/Stop Scanning 11 | 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. 12 | 13 | ### Filters 14 | Several 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 (not case-sensitive). Filtering 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). Filtering on the RSSI value of advertisments which – to some extent – represents the distance of a device. Values have to be entered as negative numbers. Two check boxes allow filtering filtering for only connectable devices or paired devices. 15 | 16 | ### Log Output 17 | A more detailed log output can be enabled by ticking the Show Log Output box. A second log window will open with more verbose logging information on device scanning and during connection with a device. It's possible to add time stamps to the logging as well as copy the log content to the clipboard or save the log output into a file. The file is automatically named with timestamp information. 18 | 19 | ![Log Window](img/ble-scout_log.png) 20 | 21 | ### Scan Data Panel 22 | 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, service data and manufacturer specific data. 23 | If the UUID of a service or the company ID in the manufacturer data is in the official Bluetooth list of assigned numbers, the official name will be shown. 24 | Manufacturer data can be displayed either as HEX numbers or ASCII string. For ASCII non printable characters are displayed as '#'. 25 | 26 | ### Connect Device 27 | If a device is connectable you can engage a connection with the Connect button. 28 | 29 | ## Device Connect Window 30 | On connect the GATT table of the device is being read and services, corresponding characteristics and descriptors are shown in a panel structure. 31 | If the UUID of a service, characteristic or descriptor is in the official Bluetooth list of assigned numbers, the official name will be shown. 32 | 33 | ![Device Connect Window](img/ble-scout_connect.png) 34 | 35 | Depending on the properties of each characteristic different buttons get added to the text field. Those are Read, WriteCommand, WriteRequest, Notify and Indicate. 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. 36 | 37 | ### Virtual Serial Port (VSP) 38 | Beside the official Bluetooth list of services, there exist proprietary services to expose a virtual serial port service (similar to SPP service from Bluetooth Classic). BLE-Scout 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. 39 | 40 | * Laird Connectivity with the Virtual Serial Port (VSP) 41 | * Nordic Semiconductors with the Nordic UART Service (NUS) 42 | * u-blox with the u-connectXpress BLE Serial Port Service 43 | 44 | ### Length of RX Characteristic 45 | The length of the peripheral RX characteristic is not exactly known to the central, hence BLE-Scout assumes MTU size minus 3 as best guess. It can be adjusted manually to another value. If the entered data is longer than the characteristic length, it will automatically split into multiple write packets. 46 | 47 | ### WriteCommand/WriteRequest 48 | Depending on the write properties of the RX characteristic you can select to use write request or write command, if both are exposed. 49 | Pairing Devices 50 | 51 | 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. 52 | 53 | This has the following implications: 54 | * The user will need to manually confirm pairing, until that happens no successful access to protected characteristics is possible. 55 | * Removing a device can only be done from the OS Bluetooth settings page. 56 | * There is no programmatic way of controlling this process. 57 | 58 | This applies at least for Windows OS and might be different on other OS platforms (not yet available). 59 | 60 | ## Limitations 61 | * Since BLE-Scout leverages the PC’s integrated Bluetooth Controller, this ultimately limits what’s achievable from a BT hardware perspective (e.g., number of simultaneous connections). 62 | * It is not possible to tweak BLE settings like scan interval and/or scan window or similar low-level BLE parameters. 63 | * Pairing of BLE devices is not yet implmented. 64 | * 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). 65 | * The underlying BLE library does not yet expose properties of the descriptors and hence it’s not yet possible to directly read/write them. 66 | 67 | ## Libraries 68 | 69 | ### NAppGUI 70 | BLE-Scout uses NAppGUI ([Homepage](https://nappgui.com/en/home/web/home.html), [GitHub](https://github.com/frang75/nappgui_src)) as GUI toolkit and cross-platform SDK. 71 | 72 | ### SimpleBLE 73 | BLE-Scout BLE Tool uses the [SimpleBLE cross-platform Bluetooth LE library](https://github.com/OpenBluetoothToolbox/SimpleBLE) for BLE connectivity with the native built-in Bluetooth controller. 74 | 75 | ## License 76 | BLE-Scout BLE Tool is released under the MIT License. 77 | Copyright (C) 2024 Erik Lins 78 | 79 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 80 | 81 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /img/ble-scout_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/BLE-Scout/1a5afc5ed5e3d3ab415671c657c338549faa62db/img/ble-scout_connect.png -------------------------------------------------------------------------------- /img/ble-scout_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/BLE-Scout/1a5afc5ed5e3d3ab415671c657c338549faa62db/img/ble-scout_log.png -------------------------------------------------------------------------------- /img/ble-scout_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eriklins/BLE-Scout/1a5afc5ed5e3d3ab415671c657c338549faa62db/img/ble-scout_main.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(BLE-Scout) 3 | 4 | find_package(nappgui REQUIRED) 5 | include("${NAPPGUI_ROOT_PATH}/prj/NAppCompilers.cmake") 6 | nap_config_compiler() 7 | 8 | # Remove WIN32 in Linux and macOS 9 | add_executable(BLEScout WIN32 blescout.c util.c logwin.c ble.c connect.c help.c) 10 | target_link_libraries(BLEScout ${NAPPGUI_LIBRARIES}) 11 | 12 | # simpleble 13 | add_subdirectory(simpleble/simpleble ${CMAKE_BINARY_DIR}/simpleble) 14 | target_link_libraries(BLEScout simpleble::simpleble-c) 15 | 16 | -------------------------------------------------------------------------------- /src/ble.c: -------------------------------------------------------------------------------- 1 | /* BLE basics */ 2 | 3 | #include 4 | #include 5 | #include "util.h" 6 | #include "logwin.h" 7 | #include "ble.h" 8 | 9 | 10 | struct _ble_t 11 | { 12 | ArrSt(BlePeripheral)* peripheral; 13 | }; 14 | 15 | static Ble* i_ble = NULL; 16 | 17 | 18 | /*---------------------------------------------------------------------------*/ 19 | 20 | static int i_compare_mac(const BlePeripheral* p1, const String* p2) 21 | { 22 | return str_scmp(p1->mac_address, p2); 23 | } 24 | 25 | static int i_compare_service(const BleService* p1, const char_t* p2) 26 | { 27 | return str_cmp_c(p1->uuid.value, p2); 28 | } 29 | 30 | static int i_compare_comp_id(const BleManufacturerData* p1, const uint16_t* p2) 31 | { 32 | if (p1->comp_id == *p2) 33 | return 0; 34 | return 1; 35 | } 36 | 37 | void ble_update_peripheral(void* handle, const char_t* mac_address, const uint8_t address_type, 38 | const int16_t rssi, const int16_t tx_power, const DevState is_connectable, const DevState is_paired, const char_t* identifier) 39 | { 40 | uint32_t pos; 41 | 42 | String* mac = str_c(mac_address); 43 | BlePeripheral* per = arrst_search(i_ble->peripheral, i_compare_mac, mac, &pos, BlePeripheral, String); 44 | str_destroy(&mac); 45 | 46 | if (per == NULL) 47 | { 48 | per = arrst_new0(i_ble->peripheral, BlePeripheral); 49 | per->services = arrst_create(BleService); 50 | per->service_count = 0; 51 | per->manufacturer_data = arrst_create(BleManufacturerData); 52 | per->manufacturer_data_count = 0; 53 | per->handle = handle; 54 | per->mac_address = str_c(mac_address); 55 | } 56 | else 57 | { 58 | str_destroy(&per->identifier); 59 | } 60 | 61 | per->identifier = str_c(identifier); 62 | per->address_type = address_type; 63 | per->rssi = rssi; 64 | per->tx_power = tx_power; 65 | per->is_connectable = is_connectable; 66 | per->is_paired = is_paired; 67 | per->is_updated = TRUE; 68 | } 69 | 70 | void ble_update_service_data(const char_t* mac_address, const char_t* uuid, const byte_t len, const uint8_t* data) 71 | { 72 | uint32_t pos; 73 | 74 | String* mac = str_c(mac_address); 75 | BlePeripheral* per = arrst_search(i_ble->peripheral, i_compare_mac, mac, &pos, BlePeripheral, String); 76 | str_destroy(&mac); 77 | 78 | if (per == NULL) 79 | { 80 | return; 81 | } 82 | 83 | BleService* svc = arrst_search(per->services, i_compare_service, uuid, &pos, BleService, char_t); 84 | if (svc == NULL) 85 | { 86 | svc = arrst_new0(per->services, BleService); 87 | bmem_copy_n(svc->uuid.value, uuid, SIMPLEBLE_UUID_STR_LEN, char); 88 | per->service_count = per->service_count + 1; 89 | } 90 | 91 | svc->data_len = len; 92 | if (len > 0) 93 | bmem_copy(svc->data, data, len); 94 | per->is_updated = TRUE; 95 | } 96 | 97 | void ble_update_manufacturer_data(const char_t* mac_address, const uint16_t comp_id, const byte_t len, const uint8_t* data) 98 | { 99 | uint32_t pos; 100 | 101 | String* mac = str_c(mac_address); 102 | BlePeripheral* per = arrst_search(i_ble->peripheral, i_compare_mac, mac, &pos, BlePeripheral, String); 103 | str_destroy(&mac); 104 | 105 | if (per == NULL) 106 | return; 107 | 108 | BleManufacturerData* mfd = arrst_search(per->manufacturer_data, i_compare_comp_id, &comp_id, &pos, BleManufacturerData, uint16_t); 109 | if (mfd == NULL) 110 | { 111 | mfd = arrst_new0(per->manufacturer_data, BleManufacturerData); 112 | mfd->comp_id = comp_id; 113 | per->manufacturer_data_count = per->manufacturer_data_count + 1; 114 | } 115 | 116 | mfd->data_len = len; 117 | if (len > 0) 118 | bmem_copy(mfd->data, data, len); 119 | per->is_updated = TRUE; 120 | } 121 | 122 | /*---------------------------------------------------------------------------*/ 123 | 124 | BlePeripheral* ble_get_peripheral(const char_t* mac_address) 125 | { 126 | uint32_t pos; 127 | 128 | String* mac = str_c(mac_address); 129 | BlePeripheral* per = arrst_search(i_ble->peripheral, i_compare_mac, mac, &pos, BlePeripheral, String); 130 | str_destroy(&mac); 131 | 132 | if (per == NULL) 133 | return NULL; 134 | 135 | return per; 136 | } 137 | 138 | static void i_remove_peripheral(BlePeripheral* per) 139 | { 140 | str_destroy(&per->mac_address); 141 | str_destroy(&per->identifier); 142 | arrst_destroy(&per->services, NULL, BleService); 143 | arrst_destroy(&per->manufacturer_data, NULL, BleManufacturerData); 144 | } 145 | 146 | void ble_clear_device_list(void) 147 | { 148 | arrst_clear(i_ble->peripheral, i_remove_peripheral, BlePeripheral); 149 | } 150 | 151 | /*---------------------------------------------------------------------------*/ 152 | 153 | Ble* ble_create(void) 154 | { 155 | Ble* ble = heap_new0(Ble); 156 | 157 | i_ble = ble; 158 | 159 | ble->peripheral = arrst_create(BlePeripheral); 160 | 161 | return ble; 162 | } 163 | 164 | void ble_destroy(Ble** ble) 165 | { 166 | arrst_destroy(&(*ble)->peripheral, i_remove_peripheral, BlePeripheral); 167 | heap_delete(ble, Ble); 168 | } 169 | -------------------------------------------------------------------------------- /src/ble.h: -------------------------------------------------------------------------------- 1 | /* BLE basics */ 2 | 3 | #include "ble.hxx" 4 | 5 | 6 | Ble* ble_create(void); 7 | 8 | void ble_destroy(Ble** ble); 9 | 10 | void ble_update_peripheral(void* handle, const char_t* mac_address, const uint8_t address_type, 11 | const int16_t rssi, const int16_t tx_power, const DevState is_connectable, const DevState is_paired, const char_t* identifier); 12 | 13 | void ble_update_service_data(const char_t* mac_address, const char_t* uuid, const byte_t len, const uint8_t* data); 14 | 15 | void ble_update_manufacturer_data(const char_t* mac_address, const uint16_t comp_id, const byte_t len, const uint8_t* data); 16 | 17 | BlePeripheral* ble_get_peripheral(const char_t* mac_address); 18 | 19 | void ble_clear_device_list(void); 20 | -------------------------------------------------------------------------------- /src/ble.hxx: -------------------------------------------------------------------------------- 1 | /* BLE basics */ 2 | 3 | #include 4 | 5 | 6 | typedef struct _ble_t Ble; 7 | 8 | 9 | typedef enum _dev_state_t DevState; 10 | 11 | enum _dev_state_t { YES, NO, UNDEF }; 12 | 13 | 14 | typedef struct _ble_service_t BleService; 15 | 16 | struct _ble_service_t { 17 | simpleble_uuid_t uuid; 18 | byte_t data_len; 19 | byte_t data[27]; 20 | }; 21 | 22 | DeclSt(BleService); 23 | 24 | 25 | typedef struct _ble_manufacturer_data_t BleManufacturerData; 26 | 27 | struct _ble_manufacturer_data_t { 28 | uint16_t comp_id; 29 | byte_t data_len; 30 | byte_t data[27]; 31 | }; 32 | 33 | DeclSt(BleManufacturerData); 34 | 35 | 36 | typedef struct _ble_peripheral_t BlePeripheral; 37 | 38 | struct _ble_peripheral_t { 39 | simpleble_peripheral_t handle; 40 | String* mac_address; 41 | uint8_t address_type; 42 | int16_t rssi; 43 | int16_t tx_power; 44 | int16_t mtu_size; 45 | DevState is_connectable; 46 | DevState is_paired; 47 | String* identifier; 48 | uint32_t service_count; 49 | ArrSt(BleService)* services; 50 | uint32_t manufacturer_data_count; 51 | ArrSt(BleManufacturerData)* manufacturer_data; 52 | bool_t is_updated; 53 | }; 54 | 55 | DeclSt(BlePeripheral); 56 | -------------------------------------------------------------------------------- /src/blescout.c: -------------------------------------------------------------------------------- 1 | /* BLE scanning */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "util.h" 7 | #include "logwin.h" 8 | #include "help.h" 9 | #include "ble.h" 10 | #include "connect.h" 11 | 12 | #define BLESCOUT_VERSION "0.3" 13 | 14 | #define UDPATE_RATE_sec 0.05 15 | #define BT_CHECK_RATE_sec 1.0 16 | 17 | #define SH_DEVICE 24 18 | #define SH_SVC_DATA 16 19 | #define SH_MNF_DATA 8 20 | 21 | 22 | 23 | typedef struct _devicepanel_t DevicePanel; 24 | 25 | struct _devicepanel_t 26 | { 27 | uint32_t row_index; 28 | String* mac_address; 29 | String* dev_name; 30 | 31 | Layout* sublayout; 32 | Edit* edit_dev_name; 33 | Button* button_connect; 34 | Cell* cell_connect; 35 | Label* label_connectable; 36 | Label* label_tx_power; 37 | Label* label_rssi; 38 | Label* label_mac_address; 39 | 40 | uint32_t svc_count; 41 | Edit* edit_svc_data[14]; 42 | Button* check_svc_ascii[14]; 43 | Cell* cell_svc_ascii[14]; 44 | uint32_t mnf_count; 45 | Edit* edit_mnf_data[14]; 46 | Button* check_mnf_ascii[14]; 47 | Cell* cell_mnf_ascii[14]; 48 | 49 | bool_t do_create; 50 | bool_t do_update; 51 | }; 52 | 53 | DeclSt(DevicePanel); 54 | 55 | typedef struct _app_t App; 56 | 57 | struct _app_t 58 | { 59 | Util* util; 60 | LogWin* logwin; 61 | Help* help; 62 | Ble* ble; 63 | Connect* connect; 64 | 65 | Window* window; 66 | Label* label_nof_devices; 67 | Button* button_scan_startstop; 68 | Cell* cell_scan_startstop; 69 | Button* button_scan_clear; 70 | Cell* cell_scan_clear; 71 | Button* button_open_close_log; 72 | Edit* edit_device_name; 73 | Cell* cell_device_name; 74 | Edit* edit_mac_address; 75 | Cell* cell_mac_address; 76 | Edit* edit_rssi; 77 | Cell* cell_rssi; 78 | Button* check_connectable; 79 | Cell* cell_connectable; 80 | Button* check_paired; 81 | Cell* cell_paired; 82 | 83 | Layout* dev_layout; 84 | 85 | ArrSt(DevicePanel)* device_panel; 86 | 87 | simpleble_adapter_t adapter_handle; 88 | bool_t scanning_is_active; 89 | bool_t logwin_is_open; 90 | bool_t logwin_is_open_prev; 91 | bool_t help_is_open; 92 | bool_t bt_initialized; 93 | bool bt_enabled; 94 | bool bt_enabled_prev; 95 | int16_t rssi_threshold; 96 | uint32_t bt_check_count; 97 | }; 98 | 99 | static App* i_app = NULL; 100 | 101 | enum simpleble_init_result { BT_SUCCESS, BT_NO_ADAPTER, BT_DISABLED, BT_NO_HANDLE, BT_NO_CALLBACKS }; 102 | 103 | 104 | /*---------------------------------------------------------------------------*/ 105 | 106 | static void i_SimpleBleOnScanStart(simpleble_adapter_t adapter, void* userdata) 107 | { 108 | char* id = simpleble_adapter_identifier(adapter); 109 | 110 | if (id != NULL) 111 | { 112 | log_printf("> Scanning started.\n"); 113 | } 114 | i_app->scanning_is_active = TRUE; 115 | 116 | simpleble_free(id); 117 | unref(userdata); 118 | } 119 | 120 | static void i_SimpleBleOnScanStop(simpleble_adapter_t adapter, void* userdata) 121 | { 122 | char* id = simpleble_adapter_identifier(adapter); 123 | 124 | if (id != NULL) 125 | { 126 | log_printf("> Scanning stopped.\n"); 127 | } 128 | i_app->scanning_is_active = FALSE; 129 | 130 | simpleble_free(id); 131 | unref(userdata); 132 | } 133 | 134 | static int i_compare_mac(const DevicePanel* dev_panel, const String* s) 135 | { 136 | return str_scmp(dev_panel->mac_address, s); 137 | } 138 | 139 | static void i_UpdateDevice(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata, bool_t updated) 140 | { 141 | char* adapter_identifier = simpleble_adapter_identifier(adapter); 142 | char* peripheral_identifier = simpleble_peripheral_identifier(peripheral); 143 | char* peripheral_address = simpleble_peripheral_address(peripheral); 144 | 145 | if (adapter_identifier != NULL && peripheral_identifier != NULL && peripheral_address != NULL) 146 | { 147 | String* mac_address = str_c(peripheral_address); 148 | 149 | bool b = false; 150 | DevState is_connectable = UNDEF; 151 | DevState is_paired = UNDEF; 152 | if (simpleble_peripheral_is_connectable(peripheral, &b) == SIMPLEBLE_SUCCESS) 153 | is_connectable = b ? YES : NO; 154 | if (simpleble_peripheral_is_paired(peripheral, &b) == SIMPLEBLE_SUCCESS) 155 | is_paired = b ? YES : NO; 156 | 157 | bool_t match_filter = TRUE; 158 | 159 | if (!str_empty_c(edit_get_text(i_app->edit_device_name)) && 160 | str_str(peripheral_identifier, edit_get_text(i_app->edit_device_name)) == NULL) 161 | match_filter = FALSE; 162 | 163 | String* cmp = str_c(edit_get_text(i_app->edit_mac_address)); 164 | str_lower(cmp); 165 | if (!str_empty_c(edit_get_text(i_app->edit_mac_address)) && 166 | str_str(tc(mac_address), tc(cmp)) == NULL) 167 | match_filter = FALSE; 168 | str_destroy(&cmp); 169 | 170 | if (simpleble_peripheral_rssi(peripheral) < i_app->rssi_threshold) 171 | match_filter = FALSE; 172 | 173 | if ((button_get_state(i_app->check_connectable) == ekGUI_ON) && (is_connectable == NO)) 174 | match_filter = FALSE; 175 | 176 | if ((button_get_state(i_app->check_paired) == ekGUI_ON) && (is_paired == NO)) 177 | match_filter = FALSE; 178 | 179 | if (match_filter) 180 | { 181 | if (!updated) 182 | ble_update_peripheral(peripheral, tc(mac_address), (uint8_t)simpleble_peripheral_address_type(peripheral), 183 | simpleble_peripheral_rssi(peripheral), simpleble_peripheral_tx_power(peripheral), 184 | (DevState)is_connectable, (DevState)is_paired, peripheral_identifier); 185 | else 186 | ble_update_peripheral(NULL, tc(mac_address), (uint8_t)simpleble_peripheral_address_type(peripheral), 187 | simpleble_peripheral_rssi(peripheral), simpleble_peripheral_tx_power(peripheral), 188 | (DevState)is_connectable, (DevState)is_paired, peripheral_identifier); 189 | 190 | bmutex_lock(log_mutex); 191 | log_printf("%s \a2\"%s\" \a3%ddB", tc(mac_address), peripheral_identifier, simpleble_peripheral_rssi(peripheral)); 192 | 193 | if (simpleble_peripheral_tx_power(peripheral) != INT16_MIN) 194 | { 195 | log_printf(" \a4tx=%ddBm", simpleble_peripheral_tx_power(peripheral)); 196 | } 197 | else 198 | { 199 | log_printf(" \a4tx=n/a"); 200 | } 201 | 202 | if (is_connectable == YES) 203 | { 204 | log_printf(" \a5Connectable"); 205 | } 206 | else 207 | { 208 | log_printf(" \a5Non-Connectable"); 209 | } 210 | 211 | if (is_paired == YES) 212 | { 213 | log_printf(" \a5Paired"); 214 | } 215 | else 216 | { 217 | log_printf(" \a5Non-Paired"); 218 | } 219 | 220 | bmutex_unlock(log_mutex); 221 | 222 | if (simpleble_peripheral_services_count(peripheral) > 0) 223 | { 224 | size_t i; 225 | simpleble_service_t svc; 226 | 227 | for (i = 0; i < simpleble_peripheral_services_count(peripheral); ++i) 228 | { 229 | simpleble_peripheral_services_get(peripheral, i, &svc); 230 | ble_update_service_data(tc(mac_address), svc.uuid.value, (uint8_t)svc.data_length, svc.data); 231 | bmutex_lock(log_mutex); 232 | log_printf(" \a6svc-uuid=%s", svc.uuid.value); 233 | 234 | if (svc.data_length > 0) 235 | { 236 | char d[55]; 237 | util_data_to_hex(svc.data, d, (byte_t)svc.data_length); 238 | log_printf(" \a6svc-data=%s", d); 239 | } 240 | 241 | String* name = util_service_uuid_to_name(svc.uuid.value); 242 | if (!str_empty(name)) 243 | log_printf(" \a7(%s)", tc(name)); 244 | bmutex_unlock(log_mutex); 245 | str_destroy(&name); 246 | } 247 | } 248 | 249 | if (simpleble_peripheral_manufacturer_data_count(peripheral) > 0) 250 | { 251 | size_t i; 252 | simpleble_manufacturer_data_t manuf_data; 253 | 254 | for (i = 0; i < simpleble_peripheral_manufacturer_data_count(peripheral); ++i) 255 | { 256 | simpleble_peripheral_manufacturer_data_get(peripheral, i, &manuf_data); 257 | ble_update_manufacturer_data(tc(mac_address), manuf_data.manufacturer_id, (uint8_t)manuf_data.data_length, manuf_data.data); 258 | bmutex_lock(log_mutex); 259 | log_printf(" \a6company-id=0x%04X", manuf_data.manufacturer_id); 260 | 261 | if (manuf_data.data_length > 0) 262 | { 263 | char d[55]; 264 | util_data_to_hex(manuf_data.data, d, (byte_t)manuf_data.data_length); 265 | log_printf(" \a6manuf-data=%s", d); 266 | } 267 | 268 | String* name = util_company_id_to_name(manuf_data.manufacturer_id); 269 | if (!str_empty(name)) 270 | log_printf(" \a7(%s)", tc(name)); 271 | bmutex_unlock(log_mutex); 272 | str_destroy(&name); 273 | } 274 | } 275 | 276 | uint32_t pos; 277 | DevicePanel* device_panel = arrst_search(i_app->device_panel, i_compare_mac, mac_address, &pos, DevicePanel, String); 278 | 279 | if (device_panel == NULL) 280 | { 281 | device_panel = arrst_new0(i_app->device_panel, DevicePanel); 282 | device_panel->mac_address = mac_address; 283 | device_panel->dev_name = str_c(peripheral_identifier); 284 | device_panel->do_create = TRUE; 285 | } 286 | device_panel->do_update = TRUE; 287 | 288 | bmutex_lock(log_mutex); 289 | log_printf("\n"); 290 | bmutex_unlock(log_mutex); 291 | } 292 | str_destroy(&mac_address); 293 | } 294 | 295 | simpleble_free(peripheral_address); 296 | simpleble_free(peripheral_identifier); 297 | 298 | unref(userdata); 299 | } 300 | 301 | static void i_SimpleBleOnFound(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata) 302 | { 303 | i_UpdateDevice(adapter, peripheral, userdata, FALSE); 304 | } 305 | 306 | static void i_SimpleBleOnUpdated(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, void* userdata) 307 | { 308 | i_UpdateDevice(adapter, peripheral, userdata, TRUE); 309 | 310 | simpleble_peripheral_release_handle(peripheral); 311 | } 312 | 313 | /*---------------------------------------------------------------------------*/ 314 | 315 | static void i_enable_controls(App* app, bool_t state) 316 | { 317 | cell_enabled(app->cell_scan_clear, state); 318 | cell_enabled(app->cell_connectable, state); 319 | cell_enabled(app->cell_paired, state); 320 | cell_enabled(app->cell_device_name, state); 321 | cell_enabled(app->cell_mac_address, state); 322 | cell_enabled(app->cell_rssi, state); 323 | } 324 | 325 | static void i_OnScanStartStopButton(App* app, Event* e) 326 | { 327 | Button* button = event_sender(e, Button); 328 | 329 | if (app->scanning_is_active) 330 | { 331 | if (simpleble_adapter_scan_stop(app->adapter_handle) != SIMPLEBLE_SUCCESS) 332 | { 333 | log_printf("> \a1Could not stop scanning.\n"); 334 | } 335 | else 336 | { 337 | button_text(button, "Start Scan"); 338 | i_enable_controls(app, TRUE); 339 | } 340 | } 341 | else 342 | { 343 | if (simpleble_adapter_scan_start(app->adapter_handle) != SIMPLEBLE_SUCCESS) 344 | { 345 | log_printf("> \a1Could not start scanning.\n"); 346 | } 347 | else 348 | { 349 | button_text(button, "Stop Scan"); 350 | i_enable_controls(app, FALSE); 351 | } 352 | } 353 | 354 | unref(e); 355 | } 356 | 357 | static void i_remove_mac_address(DevicePanel* device_panel) 358 | { 359 | str_destroy(&device_panel->mac_address); 360 | } 361 | 362 | static void i_OnScanClearButton(App* app, Event* e) 363 | { 364 | int i; 365 | int32_t nrows = layout_nrows(app->dev_layout); 366 | for (i = nrows-1; i > 0; --i) 367 | { 368 | layout_remove_row(app->dev_layout, i); 369 | } 370 | arrst_clear(app->device_panel, i_remove_mac_address, DevicePanel); 371 | 372 | ble_clear_device_list(); 373 | 374 | log_printf("> Cleared BLE device list.\n"); 375 | 376 | label_text(app->label_nof_devices, "Press Start Scan to Discover Devices..."); 377 | 378 | layout_update(app->dev_layout); 379 | 380 | unref(e); 381 | } 382 | 383 | static void i_OnHelpButton(App* app, Event* e) 384 | { 385 | Button* b = event_sender(e, Button); 386 | 387 | if (help_is_open()) 388 | { 389 | help_hide(app->help); 390 | } 391 | else 392 | { 393 | V2Df origin = window_get_origin(i_app->window); 394 | S2Df size = window_get_size(i_app->window); 395 | help_show(app->help, v2df(origin.x + size.width, origin.y)); 396 | } 397 | 398 | unref(e); 399 | } 400 | 401 | static void i_OnShowLogWindow(App* app, Event* e) 402 | { 403 | Button* b = event_sender(e, Button); 404 | 405 | if (logwin_is_open()) 406 | { 407 | logwin_hide(app->logwin); 408 | app->logwin_is_open = TRUE; 409 | button_text(b, "Hide Log"); 410 | } 411 | else 412 | { 413 | V2Df origin = window_get_origin(i_app->window); 414 | S2Df size = window_get_size(i_app->window); 415 | logwin_show(app->logwin, v2df(origin.x + size.width, origin.y)); 416 | app->logwin_is_open = FALSE; 417 | button_text(b, "Show Log"); 418 | } 419 | 420 | unref(e); 421 | } 422 | 423 | static void i_OnChangeRssi(App* app, Event* e) 424 | { 425 | bool_t err; 426 | 427 | app->rssi_threshold = str_to_i16(edit_get_text(app->edit_rssi), 10, &err); 428 | if (err || (app->rssi_threshold > 0) || (app->rssi_threshold < -100)) 429 | { 430 | app->rssi_threshold = -100; 431 | log_printf("> \a1Invalid rssi (-100 <= rssi <= 0).\n"); 432 | } 433 | 434 | unref(e); 435 | } 436 | 437 | static void i_OnChangeMacAddress(App* app, Event* e) 438 | { 439 | String* mac = str_c(edit_get_text(app->edit_mac_address)); 440 | 441 | if (util_buffer_is_mac_address(tc(mac), (size_t)str_len(mac)) != TRUE) 442 | { 443 | edit_bgcolor(app->edit_mac_address, color_html("#F1948A")); 444 | log_printf("> \a1Invalid MAC address in filter.\n"); 445 | } 446 | else 447 | { 448 | edit_bgcolor(app->edit_mac_address, kCOLOR_DEFAULT); 449 | } 450 | 451 | // edit_text(app->edit_mac_address, tc(mac)); 452 | str_destroy(&mac); 453 | 454 | unref(e); 455 | } 456 | 457 | /*---------------------------------------------------------------------------*/ 458 | 459 | static Panel* i_panel(App* app) 460 | { 461 | Panel* panel_static = panel_create(); 462 | 463 | Layout* layout = layout_create(1, 6); 464 | Layout* layout_progname = layout_create(2, 1); 465 | Layout* layout_buttons = layout_create(3, 1); 466 | Layout* layout_filtertitle = layout_create(1, 1); 467 | Layout* layout_filtervalues = layout_create(4, 3); 468 | Layout* layout_nof_devices = layout_create(1, 1); 469 | 470 | 471 | layout_layout(layout, layout_progname, 0, 0); 472 | layout_layout(layout, layout_buttons, 0, 1); 473 | layout_layout(layout, layout_filtertitle, 0, 2); 474 | layout_layout(layout, layout_filtervalues, 0, 3); 475 | layout_layout(layout, layout_nof_devices, 0, 4); 476 | layout_vexpand(layout, 5); 477 | 478 | 479 | Font* font_progname = font_system(20, ekFBOLD); 480 | Label* label_progname = label_create(); 481 | label_text(label_progname, "BLE Scout - Bluetooth LE Tool"); 482 | label_font(label_progname, font_progname); 483 | label_color(label_progname, color_html("#2E86C1")); 484 | layout_label(layout_progname, label_progname, 0, 0); 485 | layout_hexpand(layout_progname, 0); 486 | cell_padding4(layout_cell(layout_progname, 0, 0), 0, 0, 5, 10); 487 | 488 | Button* button_help = button_push(); 489 | button_text(button_help, "Help"); 490 | button_OnClick(button_help, listener(app, i_OnHelpButton, App)); 491 | layout_button(layout_progname, button_help, 1, 0); 492 | cell_padding4(layout_cell(layout_progname, 1, 0), 0, 10, 5, 0); 493 | 494 | app->button_scan_startstop = button_push(); 495 | button_text(app->button_scan_startstop, "Start Scan"); 496 | button_OnClick(app->button_scan_startstop, listener(app, i_OnScanStartStopButton, App)); 497 | layout_button(layout_buttons, app->button_scan_startstop, 0, 0); 498 | app->cell_scan_startstop = layout_cell(layout_buttons, 0, 0); 499 | cell_padding4(layout_cell(layout_buttons, 0, 0), 0, 10, 0, 0); 500 | 501 | app->button_scan_clear = button_push(); 502 | button_text(app->button_scan_clear, "Clear"); 503 | button_OnClick(app->button_scan_clear, listener(app, i_OnScanClearButton, App)); 504 | layout_button(layout_buttons, app->button_scan_clear, 1, 0); 505 | app->cell_scan_clear = layout_cell(layout_buttons, 1, 0); 506 | cell_padding4(layout_cell(layout_buttons, 1, 0), 0, 10, 0, 10); 507 | 508 | app->button_open_close_log = button_push(); 509 | button_text(app->button_open_close_log, "Show Log"); 510 | button_OnClick(app->button_open_close_log, listener(app, i_OnShowLogWindow, App)); 511 | layout_button(layout_buttons, app->button_open_close_log, 2, 0); 512 | cell_padding4(layout_cell(layout_buttons, 2, 0), 0, 0, 0, 10); 513 | 514 | layout_margin4(layout_buttons, 5, 10, 10, 10); 515 | 516 | 517 | Font* font_filters = font_system(14, ekFBOLD); 518 | Label* label_filters = label_create(); 519 | label_text(label_filters, "Filter Devices by"); 520 | label_font(label_filters, font_filters); 521 | label_color(label_filters, color_html("#566573")); 522 | layout_label(layout_filtertitle, label_filters, 0, 0); 523 | cell_padding4(layout_cell(layout_filtertitle, 0, 0), 0, 0, 5, 10); 524 | layout_halign(layout, 0, 2, ekCENTER); 525 | 526 | 527 | Label* label_devicename = label_create(); 528 | label_text(label_devicename, "Device Name"); 529 | layout_label(layout_filtervalues, label_devicename, 0, 0); 530 | cell_padding4(layout_cell(layout_filtervalues, 0, 0), 0, 5, 0, 0); 531 | 532 | Label* label_macaddress = label_create(); 533 | label_text(label_macaddress, "MAC Address"); 534 | layout_label(layout_filtervalues, label_macaddress, 1, 0); 535 | cell_padding4(layout_cell(layout_filtervalues, 1, 0), 0, 5, 0, 5); 536 | 537 | Label* label_rssi = label_create(); 538 | label_text(label_rssi, "RSSI"); 539 | layout_label(layout_filtervalues, label_rssi, 2, 0); 540 | cell_padding4(layout_cell(layout_filtervalues, 2, 0), 0, 5, 0, 5); 541 | 542 | app->edit_device_name = edit_create(); 543 | layout_edit(layout_filtervalues, app->edit_device_name, 0, 1); 544 | app->cell_device_name = layout_cell(layout_filtervalues, 0, 1); 545 | cell_padding4(layout_cell(layout_filtervalues, 0, 1), 0, 5, 0, 0); 546 | 547 | app->edit_mac_address = edit_create(); 548 | edit_OnChange(app->edit_mac_address, listener(app, i_OnChangeMacAddress, App)); 549 | layout_edit(layout_filtervalues, app->edit_mac_address, 1, 1); 550 | app->cell_mac_address = layout_cell(layout_filtervalues, 1, 1); 551 | cell_padding4(layout_cell(layout_filtervalues, 1, 1), 0, 5, 0, 5); 552 | 553 | app->edit_rssi = edit_create(); 554 | edit_text(app->edit_rssi, "-100"); 555 | edit_OnChange(app->edit_rssi, listener(app, i_OnChangeRssi, App)); 556 | layout_edit(layout_filtervalues, app->edit_rssi, 2, 1); 557 | app->cell_rssi = layout_cell(layout_filtervalues, 2, 1); 558 | cell_padding4(layout_cell(layout_filtervalues, 2, 1), 0, 5, 0, 5); 559 | 560 | app->check_connectable = button_check(); 561 | button_text(app->check_connectable, "connectable"); 562 | layout_button(layout_filtervalues, app->check_connectable, 3, 0); 563 | app->cell_connectable = layout_cell(layout_filtervalues, 3, 0); 564 | cell_padding4(layout_cell(layout_filtervalues, 3, 0), 0, 5, 0, 0); 565 | 566 | app->check_paired = button_check(); 567 | button_text(app->check_paired, "paired"); 568 | layout_button(layout_filtervalues, app->check_paired, 3, 1); 569 | app->cell_paired = layout_cell(layout_filtervalues, 3, 1); 570 | cell_padding4(layout_cell(layout_filtervalues, 3, 1), 0, 5, 0, 0); 571 | 572 | layout_margin4(layout_filtervalues, 0, 10, 5, 10); 573 | 574 | Font* font_nof_devices = font_system(14, ekFBOLD); 575 | app->label_nof_devices = label_create(); 576 | label_text(app->label_nof_devices, "Press Start Scan to Discover Devices..."); 577 | label_font(app->label_nof_devices, font_nof_devices); 578 | label_color(app->label_nof_devices, color_html("#566573")); 579 | label_align(app->label_nof_devices, ekCENTER); 580 | layout_label(layout_nof_devices, app->label_nof_devices, 0, 0); 581 | layout_halign(layout_nof_devices, 0, 0, ekCENTER); 582 | layout_valign(layout_nof_devices, 0, 0, ekTOP); 583 | cell_padding4(layout_cell(layout_nof_devices, 0, 0), 5, 5, 5, 5); 584 | 585 | 586 | Panel* panel_dynamic = panel_scroll(FALSE, TRUE); 587 | real32_t scrw = panel_scroll_width(panel_dynamic); 588 | 589 | layout_panel(layout, panel_dynamic, 0, 5); 590 | 591 | Layout* layout_devices = layout_create(1, 2); 592 | app->dev_layout = layout_create(1, 1); 593 | layout_layout(layout_devices, app->dev_layout, 0, 0); 594 | layout_margin4(layout_devices, 0, scrw, 0, 0); 595 | layout_vexpand(layout_devices, 1); 596 | 597 | 598 | panel_size(panel_dynamic, s2df(400, 500)); 599 | 600 | panel_layout(panel_static, layout); 601 | panel_layout(panel_dynamic, layout_devices); 602 | 603 | 604 | return panel_static; 605 | } 606 | 607 | /*---------------------------------------------------------------------------*/ 608 | 609 | static void i_OnClose(App* app, Event* e) 610 | { 611 | logwin_hide(app->logwin); 612 | 613 | osapp_finish(); 614 | 615 | unref(app); 616 | unref(e); 617 | } 618 | 619 | /*---------------------------------------------------------------------------*/ 620 | 621 | static int i_init_simpleble(App* app) 622 | { 623 | if (simpleble_adapter_get_count() == 0) 624 | return BT_NO_ADAPTER; 625 | 626 | if (simpleble_adapter_is_bluetooth_enabled() != true) 627 | return BT_DISABLED; 628 | 629 | app->adapter_handle = simpleble_adapter_get_handle(0); 630 | if (app->adapter_handle == 0) 631 | return BT_NO_HANDLE; 632 | 633 | int i = simpleble_adapter_set_callback_on_scan_start(app->adapter_handle, i_SimpleBleOnScanStart, NULL); 634 | i += simpleble_adapter_set_callback_on_scan_stop(app->adapter_handle, i_SimpleBleOnScanStop, NULL); 635 | i += simpleble_adapter_set_callback_on_scan_found(app->adapter_handle, i_SimpleBleOnFound, NULL); 636 | i += simpleble_adapter_set_callback_on_scan_updated(app->adapter_handle, i_SimpleBleOnUpdated, NULL); 637 | if (i > 0) 638 | return BT_NO_CALLBACKS; 639 | 640 | app->bt_initialized = TRUE; 641 | 642 | return BT_SUCCESS; 643 | } 644 | 645 | static App* i_create(void) 646 | { 647 | log_file("blescout.log"); 648 | 649 | App* app = heap_new0(App); 650 | i_app = app; 651 | 652 | app->scanning_is_active = FALSE; 653 | app->logwin_is_open = FALSE; 654 | app->bt_initialized = FALSE; 655 | app->bt_check_count = (uint32_t)(BT_CHECK_RATE_sec / UDPATE_RATE_sec); 656 | app->rssi_threshold = -100; 657 | 658 | app->util = util_create(); 659 | app->logwin = logwin_create(); 660 | app->help = help_create(); 661 | app->ble = ble_create(); 662 | app->connect = connect_create(); 663 | 664 | app->device_panel = arrst_create(DevicePanel); 665 | 666 | Panel* panel = i_panel(app); 667 | 668 | app->window = window_create(ekWINDOW_STD | ekWINDOW_RESIZE); 669 | window_panel(app->window, panel); 670 | String* title = str_printf("BLE Scout V%s", BLESCOUT_VERSION); 671 | window_title(app->window, tc(title)); 672 | str_destroy(&title); 673 | window_origin(app->window, v2df(200, 200)); 674 | window_OnClose(app->window, listener(app, i_OnClose, App)); 675 | window_show(app->window); 676 | 677 | cell_enabled(app->cell_scan_startstop, FALSE); 678 | 679 | log_printf("> BLE Scout - Bluetooth LE Tool version %s (https://github.com/eriklins/BLE-Scout)\n", BLESCOUT_VERSION); 680 | 681 | log_printf("> Uses SimpleBLE library version %s (https://github.com/OpenBluetoothToolbox/SimpleBLE)\n", simpleble_get_version()); 682 | 683 | int ret = i_init_simpleble(app); 684 | if (ret != BT_SUCCESS) 685 | { 686 | switch (ret) 687 | { 688 | case BT_NO_ADAPTER: 689 | log_printf("> \a1No Bluetooth adapter found.\n"); 690 | label_text(app->label_nof_devices, "No Bluetooth adapter found."); 691 | label_color(app->label_nof_devices, kCOLOR_RED); 692 | app->bt_enabled = false; 693 | break; 694 | case BT_DISABLED: 695 | log_printf("> \a1Bluetooth is disabled.\n"); 696 | label_text(app->label_nof_devices, "Bluetooth is disabled."); 697 | label_color(app->label_nof_devices, kCOLOR_RED); 698 | app->bt_enabled = false; 699 | break; 700 | case BT_NO_HANDLE: 701 | log_printf("> \a1Could not get Bluetooth adapter handle.\n"); 702 | label_text(app->label_nof_devices, "Could not get Bluetooth adapter handle."); 703 | label_color(app->label_nof_devices, kCOLOR_RED); 704 | break; 705 | case BT_NO_CALLBACKS: 706 | log_printf("> \a1Could not register BLE callback functions.\n"); 707 | label_text(app->label_nof_devices, "Could not register BLE callback functions."); 708 | label_color(app->label_nof_devices, kCOLOR_RED); 709 | break; 710 | } 711 | log_printf("> SimpleBLE not initialized.\n"); 712 | } 713 | else 714 | { 715 | log_printf("> SimpleBLE initialized.\n"); 716 | app->bt_initialized = TRUE; 717 | app->bt_enabled = true; 718 | cell_enabled(app->cell_scan_startstop, TRUE); 719 | } 720 | 721 | app->bt_enabled_prev = app->bt_enabled; 722 | app->logwin_is_open_prev = app->logwin_is_open; 723 | 724 | return app; 725 | } 726 | 727 | static void i_destroy(App** app) 728 | { 729 | arrst_destroy(&(*app)->device_panel, i_remove_mac_address, DevicePanel); 730 | window_destroy(&(*app)->window); 731 | connect_destroy(&(*app)->connect); 732 | ble_destroy(&(*app)->ble); 733 | logwin_destroy(&(*app)->logwin); 734 | util_destroy(&(*app)->util); 735 | heap_delete(app, App); 736 | } 737 | 738 | /*---------------------------------------------------------------------------*/ 739 | 740 | static void i_OnCheckButtonHexAscii(App* app, Event* e) 741 | { 742 | Button* b = event_sender(e, Button); 743 | gui_state_t btn_state = button_get_state(b); 744 | uint32_t tag = button_get_tag(b); 745 | uint32_t dev_idx = (tag >> SH_DEVICE) & 0xff; 746 | uint32_t svc_idx = (tag >> SH_SVC_DATA) & 0xff; 747 | uint32_t mnf_idx = (tag >> SH_MNF_DATA) & 0xff; 748 | 749 | DevicePanel* device_panel = arrst_get(app->device_panel, dev_idx, DevicePanel); 750 | cassert(device_panel != NULL); 751 | 752 | BlePeripheral* per = ble_get_peripheral(tc(device_panel->mac_address)); 753 | cassert(per != NULL); 754 | 755 | char_t d[55]; 756 | 757 | if (svc_idx > 0) 758 | { 759 | --svc_idx; 760 | BleService* svc = arrst_get(per->services, svc_idx, BleService); 761 | cassert(svc != NULL); 762 | 763 | if (btn_state == ekGUI_ON) 764 | { 765 | util_data_to_ascii(svc->data, d, svc->data_len); 766 | edit_text(device_panel->edit_svc_data[svc_idx], d); 767 | } 768 | else 769 | { 770 | util_data_to_hex(svc->data, d, svc->data_len); 771 | edit_text(device_panel->edit_svc_data[svc_idx], d); 772 | 773 | } 774 | } 775 | else if (mnf_idx > 0) 776 | { 777 | --mnf_idx; 778 | BleManufacturerData* mnf = arrst_get(per->manufacturer_data, mnf_idx, BleManufacturerData); 779 | if (btn_state == ekGUI_ON) 780 | { 781 | util_data_to_ascii(mnf->data, d, mnf->data_len); 782 | edit_text(device_panel->edit_mnf_data[mnf_idx], d); 783 | } 784 | else 785 | { 786 | util_data_to_hex(mnf->data, d, mnf->data_len); 787 | edit_text(device_panel->edit_mnf_data[mnf_idx], d); 788 | 789 | } 790 | } 791 | 792 | unref(e); 793 | } 794 | 795 | static int i_compare_tag(const DevicePanel* device_panel, const uint32_t *tag) 796 | { 797 | if (device_panel->row_index == *tag) 798 | return 0; 799 | return 1; 800 | } 801 | 802 | static void i_OnClickConnectDevice(DevicePanel* dev, Event* e) 803 | { 804 | cell_enabled(dev->cell_connect, FALSE); 805 | 806 | V2Df origin = window_get_origin(i_app->window); 807 | S2Df size = window_get_size(i_app->window); 808 | 809 | if(connect_device(dev->mac_address, v2df(origin.x + size.width, origin.y)) != TRUE) 810 | cell_enabled(dev->cell_connect, TRUE); 811 | 812 | unref(e); 813 | } 814 | 815 | static void i_create_device(App* app, DevicePanel* device_panel, BlePeripheral* peripheral, int32_t row) 816 | { 817 | int32_t sub_rows = layout_nrows(app->dev_layout); 818 | cassert(sub_rows >= 1); 819 | 820 | if (row == -1) 821 | { 822 | layout_insert_row(app->dev_layout, sub_rows); 823 | device_panel->row_index = sub_rows; 824 | } 825 | else 826 | { 827 | layout_remove_row(app->dev_layout, row); 828 | layout_insert_row(app->dev_layout, row); 829 | device_panel->row_index = row; 830 | } 831 | 832 | device_panel->sublayout = layout_create(1, 3); 833 | layout_margin(device_panel->sublayout, 10); 834 | layout_skcolor(device_panel->sublayout, color_html("#ABB2B9")); 835 | layout_bgcolor(device_panel->sublayout, color_html("#EBF5FB")); 836 | 837 | Layout* layout_name_connect = layout_create(3, 1); 838 | Layout* layout_conn_tx_rssi = layout_create(3, 1); 839 | Layout* layout_mac_address = layout_create(1, 1); 840 | 841 | 842 | Font* font_dev_name = font_system(16, ekFBOLD); 843 | device_panel->edit_dev_name = edit_create(); 844 | edit_font(device_panel->edit_dev_name, font_dev_name); 845 | edit_text(device_panel->edit_dev_name, "\"Device Name\""); 846 | edit_editable(device_panel->edit_dev_name, FALSE); 847 | layout_edit(layout_name_connect, device_panel->edit_dev_name, 0, 0); 848 | layout_hsize(layout_name_connect, 0, 250.0); 849 | 850 | device_panel->button_connect = button_push(); 851 | button_text(device_panel->button_connect, "Connect"); 852 | button_tag(device_panel->button_connect, device_panel->row_index); 853 | button_OnClick(device_panel->button_connect, listener(device_panel, i_OnClickConnectDevice, DevicePanel)); 854 | 855 | layout_button(layout_name_connect, device_panel->button_connect, 2, 0); 856 | device_panel->cell_connect = layout_cell(layout_name_connect, 2, 0); 857 | 858 | layout_hexpand(layout_name_connect, 1); 859 | layout_layout(device_panel->sublayout, layout_name_connect, 0, 0); 860 | layout_margin4(layout_name_connect, 0, 0, 5, 0); 861 | 862 | 863 | device_panel->label_connectable = label_create(); 864 | label_text(device_panel->label_connectable, "Connectable: xxx"); 865 | layout_label(layout_conn_tx_rssi, device_panel->label_connectable, 0, 0); 866 | device_panel->label_tx_power = label_create(); 867 | label_text(device_panel->label_tx_power, "TX Power: xxdB"); 868 | layout_label(layout_conn_tx_rssi, device_panel->label_tx_power, 1, 0); 869 | device_panel->label_rssi = label_create(); 870 | label_text(device_panel->label_rssi, "RSSI: -xxdBm"); 871 | layout_label(layout_conn_tx_rssi, device_panel->label_rssi, 2, 0); 872 | 873 | layout_layout(device_panel->sublayout, layout_conn_tx_rssi, 0, 1); 874 | 875 | 876 | device_panel->label_mac_address = label_create(); 877 | label_text(device_panel->label_mac_address, "MAC Address: XX:XX:XX:XX:XX:XX"); 878 | layout_label(layout_mac_address, device_panel->label_mac_address, 0, 0); 879 | 880 | layout_layout(device_panel->sublayout, layout_mac_address, 0, 2); 881 | 882 | 883 | device_panel->svc_count = (uint32_t)peripheral->service_count; 884 | device_panel->mnf_count = (uint32_t)peripheral->manufacturer_data_count; 885 | 886 | if (device_panel->svc_count > 0) 887 | { 888 | uint32_t rows = layout_nrows(device_panel->sublayout); 889 | cassert(rows >= 1); 890 | 891 | layout_insert_row(device_panel->sublayout, rows); 892 | 893 | Layout* layout_svc = layout_create(3, device_panel->svc_count + 1); 894 | 895 | Font* font_title = font_system(12, ekFBOLD); 896 | 897 | Label* label_title = label_create(); 898 | label_text(label_title, "Advertised Services:"); 899 | label_font(label_title, font_title); 900 | layout_label(layout_svc, label_title, 0, 0); 901 | layout_margin4(layout_svc, 5, 0, 0, 0); 902 | 903 | Label* label_svc_uuid[14]; 904 | uint32_t i; 905 | for (i = 0; i < device_panel->svc_count; ++i) 906 | { 907 | label_svc_uuid[i] = label_create(); 908 | BleService* svc = arrst_get(peripheral->services, i, BleService); 909 | 910 | String* name = util_service_uuid_to_name(svc->uuid.value); 911 | if (str_empty(name)) 912 | { 913 | str_destroy(&name); 914 | name = util_vsp_service_uuid_to_name(svc->uuid.value); 915 | if (str_empty(name)) 916 | { 917 | str_destroy(&name); 918 | name = str_c(svc->uuid.value); 919 | } 920 | } 921 | label_text(label_svc_uuid[i], tc(name)); 922 | str_destroy(&name); 923 | layout_label(layout_svc, label_svc_uuid[i], 0, i + 1); 924 | layout_hsize(layout_svc, 0, 220.0); 925 | 926 | if (svc->data_len > 0) 927 | { 928 | device_panel->edit_svc_data[i] = edit_create(); 929 | edit_editable(device_panel->edit_svc_data[i], FALSE); 930 | layout_edit(layout_svc, device_panel->edit_svc_data[i], 1, i + 1); 931 | device_panel->check_svc_ascii[i] = button_check(); 932 | button_tag(device_panel->check_svc_ascii[i], (device_panel->row_index << SH_DEVICE) | ((i+1) << SH_SVC_DATA)); 933 | button_OnClick(device_panel->check_svc_ascii[i], listener(app, i_OnCheckButtonHexAscii, App)); 934 | button_text(device_panel->check_svc_ascii[i], "ASCII"); 935 | layout_button(layout_svc, device_panel->check_svc_ascii[i], 2, i + 1); 936 | device_panel->cell_svc_ascii[i] = layout_cell(layout_svc, 2, i + 1); 937 | } 938 | layout_hmargin(layout_svc, 0, 5); 939 | layout_hmargin(layout_svc, 1, 5); 940 | } 941 | 942 | layout_hexpand(layout_svc, 0); 943 | layout_layout(device_panel->sublayout, layout_svc, 0, rows); 944 | } 945 | 946 | if (device_panel->mnf_count > 0) 947 | { 948 | uint32_t rows = layout_nrows(device_panel->sublayout); 949 | cassert(rows >= 1); 950 | 951 | layout_insert_row(device_panel->sublayout, rows); 952 | 953 | Layout* layout_mnf = layout_create(2, 2* device_panel->mnf_count + 1); 954 | 955 | Font* font_title = font_system(12, ekFBOLD); 956 | 957 | Label* label_title = label_create(); 958 | label_text(label_title, "Manufacturer Specific Data:"); 959 | label_font(label_title, font_title); 960 | layout_label(layout_mnf, label_title, 0, 0); 961 | layout_margin4(layout_mnf, 5, 0, 0, 0); 962 | 963 | Label* label_mnf_uuid[14]; 964 | uint32_t i; 965 | for (i = 0; i < device_panel->mnf_count; ++i) 966 | { 967 | label_mnf_uuid[i] = label_create(); 968 | BleManufacturerData* mnf = arrst_get(peripheral->manufacturer_data, i, BleManufacturerData); 969 | String* name = util_company_id_to_name(mnf->comp_id); 970 | if (str_empty(name)) 971 | { 972 | String* s = str_printf("Company ID: 0x%04X", mnf->comp_id); 973 | label_text(label_mnf_uuid[i], tc(s)); 974 | str_destroy(&s); 975 | } 976 | else 977 | label_text(label_mnf_uuid[i], tc(name)); 978 | str_destroy(&name); 979 | layout_label(layout_mnf, label_mnf_uuid[i], 0, 2*i + 1); 980 | layout_hsize(layout_mnf, 0, 200.0); 981 | 982 | if (mnf->data_len > 0) 983 | { 984 | device_panel->edit_mnf_data[i] = edit_create(); 985 | edit_editable(device_panel->edit_mnf_data[i], FALSE); 986 | layout_edit(layout_mnf, device_panel->edit_mnf_data[i], 0, 2*i + 2); 987 | device_panel->check_mnf_ascii[i] = button_check(); 988 | button_tag(device_panel->check_mnf_ascii[i], (device_panel->row_index << SH_DEVICE) | ((i+1) << SH_MNF_DATA)); 989 | button_OnClick(device_panel->check_mnf_ascii[i], listener(app, i_OnCheckButtonHexAscii, App)); 990 | button_text(device_panel->check_mnf_ascii[i], "ASCII"); 991 | layout_button(layout_mnf, device_panel->check_mnf_ascii[i], 1, 2*i + 2); 992 | device_panel->cell_mnf_ascii[i] = layout_cell(layout_mnf, 2, i + 1); 993 | } 994 | layout_hmargin(layout_mnf, 0, 5); 995 | } 996 | 997 | layout_hexpand(layout_mnf, 0); 998 | layout_layout(device_panel->sublayout, layout_mnf, 0, rows); 999 | } 1000 | 1001 | layout_layout(app->dev_layout, device_panel->sublayout, 0, device_panel->row_index); 1002 | 1003 | cell_padding4(layout_cell(app->dev_layout, 0, device_panel->row_index), 5, 10, 5, 5); 1004 | 1005 | layout_hsize(app->dev_layout, device_panel->row_index, 400); 1006 | 1007 | layout_update(app->dev_layout); 1008 | } 1009 | 1010 | static void i_update(App* app, const real64_t prtime, const real64_t ctime) 1011 | { 1012 | logwin_update(); 1013 | 1014 | if (!app->bt_initialized) 1015 | { 1016 | if (app->bt_check_count == 0) 1017 | { 1018 | int i = i_init_simpleble(app); 1019 | 1020 | if (i == 0) 1021 | { 1022 | app->bt_initialized = TRUE; 1023 | cell_enabled(app->cell_scan_startstop, TRUE); 1024 | log_printf("> SimpleBLE initialized.\n"); 1025 | label_text(app->label_nof_devices, "Press Start Scan to Discover Devices..."); 1026 | label_color(app->label_nof_devices, color_html("#566573")); 1027 | layout_update(app->dev_layout); 1028 | } 1029 | app->bt_check_count = (uint32_t)(BT_CHECK_RATE_sec / UDPATE_RATE_sec); 1030 | } 1031 | else 1032 | app->bt_check_count--; 1033 | } 1034 | 1035 | app->bt_enabled = simpleble_adapter_is_bluetooth_enabled(); 1036 | if (app->bt_enabled != app->bt_enabled_prev) 1037 | { 1038 | if (!app->bt_enabled) { 1039 | app->scanning_is_active = FALSE; 1040 | button_text(app->button_scan_startstop, "Start Scan"); 1041 | i_enable_controls(app, TRUE); 1042 | cell_enabled(app->cell_scan_startstop, FALSE); 1043 | log_printf("> \a1Bluetooth is disabled.\n"); 1044 | label_text(app->label_nof_devices, "Bluetooth is disabled."); 1045 | label_color(app->label_nof_devices, kCOLOR_RED); 1046 | } 1047 | else 1048 | { 1049 | cell_enabled(app->cell_scan_startstop, TRUE); 1050 | log_printf("> Bluetooth is enabled.\n"); 1051 | label_text(app->label_nof_devices, "Press Start Scan to Discover Devices..."); 1052 | label_color(app->label_nof_devices, color_html("#566573")); 1053 | layout_update(app->dev_layout); 1054 | } 1055 | app->bt_enabled_prev = app->bt_enabled; 1056 | } 1057 | 1058 | app->logwin_is_open = logwin_is_open(); 1059 | if (app->logwin_is_open != app->logwin_is_open_prev) 1060 | { 1061 | if (logwin_is_open()) 1062 | { 1063 | app->logwin_is_open = TRUE; 1064 | button_text(app->button_open_close_log, "Hide Log"); 1065 | } 1066 | else 1067 | { 1068 | app->logwin_is_open = FALSE; 1069 | button_text(app->button_open_close_log, "Show Log"); 1070 | } 1071 | app->logwin_is_open_prev = app->logwin_is_open; 1072 | } 1073 | 1074 | if (app->scanning_is_active) 1075 | { 1076 | arrst_foreach(device_panel, app->device_panel, DevicePanel) 1077 | 1078 | if (device_panel->do_update) 1079 | { 1080 | BlePeripheral* ble_peripheral = ble_get_peripheral(tc(device_panel->mac_address)); 1081 | if (ble_peripheral != NULL) 1082 | { 1083 | if (device_panel->do_create) 1084 | { 1085 | i_create_device(app, device_panel, ble_peripheral, -1); 1086 | device_panel->do_create = FALSE; 1087 | } 1088 | else 1089 | { 1090 | if (ble_peripheral->service_count > device_panel->svc_count) 1091 | { 1092 | i_create_device(app, device_panel, ble_peripheral, device_panel->row_index); 1093 | device_panel->do_create = FALSE; 1094 | } 1095 | if (ble_peripheral->manufacturer_data_count > device_panel->mnf_count) 1096 | { 1097 | i_create_device(app, device_panel, ble_peripheral, device_panel->row_index); 1098 | device_panel->do_create = FALSE; 1099 | } 1100 | } 1101 | 1102 | if (str_empty(ble_peripheral->identifier)) 1103 | edit_text(device_panel->edit_dev_name, "unknown"); 1104 | else 1105 | edit_text(device_panel->edit_dev_name, tc(ble_peripheral->identifier)); 1106 | String* msg = str_printf("MAC Address: %s", tc(ble_peripheral->mac_address)); 1107 | label_text(device_panel->label_mac_address, tc(msg)); 1108 | str_destroy(&msg); 1109 | if (ble_peripheral->is_connectable == YES) 1110 | { 1111 | label_text(device_panel->label_connectable, "Connectable: Yes"); 1112 | cell_enabled(device_panel->cell_connect, TRUE); 1113 | } 1114 | else if (ble_peripheral->is_connectable == NO) 1115 | { 1116 | label_text(device_panel->label_connectable, "Connectable: No"); 1117 | cell_enabled(device_panel->cell_connect, FALSE); 1118 | } 1119 | else 1120 | { 1121 | label_text(device_panel->label_connectable, "Connectable: n/a"); 1122 | cell_enabled(device_panel->cell_connect, FALSE); 1123 | } 1124 | if (ble_peripheral->tx_power != INT16_MIN) 1125 | msg = str_printf("TX Power: %ddBm", ble_peripheral->tx_power); 1126 | else 1127 | msg = str_printf("TX Power: n/a"); 1128 | label_text(device_panel->label_tx_power, tc(msg)); 1129 | str_destroy(&msg); 1130 | msg = str_printf("RSSI: %ddB", ble_peripheral->rssi); 1131 | label_text(device_panel->label_rssi, tc(msg)); 1132 | str_destroy(&msg); 1133 | 1134 | uint32_t i; 1135 | char data[55]; 1136 | for (i = 0; i < ble_peripheral->service_count; ++i) 1137 | { 1138 | BleService* svc = arrst_get(ble_peripheral->services, i, BleService); 1139 | if (svc->data_len > 0) 1140 | { 1141 | if (button_get_state(device_panel->check_svc_ascii[i]) == ekGUI_ON) 1142 | util_data_to_ascii(svc->data, data, svc->data_len); 1143 | else 1144 | util_data_to_hex(svc->data, data, svc->data_len); 1145 | edit_text(device_panel->edit_svc_data[i], data); 1146 | } 1147 | } 1148 | 1149 | for (i = 0; i < ble_peripheral->manufacturer_data_count; ++i) 1150 | { 1151 | BleManufacturerData* mnf = arrst_get(ble_peripheral->manufacturer_data, i, BleManufacturerData); 1152 | if (mnf->data_len > 0) 1153 | { 1154 | if (button_get_state(device_panel->check_mnf_ascii[i]) == ekGUI_ON) 1155 | util_data_to_ascii(mnf->data, data, mnf->data_len); 1156 | else 1157 | util_data_to_hex(mnf->data, data, mnf->data_len); 1158 | edit_text(device_panel->edit_mnf_data[i], data); 1159 | } 1160 | } 1161 | } 1162 | device_panel->do_update = FALSE; 1163 | } 1164 | 1165 | arrst_end() 1166 | 1167 | if (arrst_size(app->device_panel, DevicePanel) > 0) 1168 | { 1169 | String* msg = str_printf("%d Devices Discovered.", arrst_size(app->device_panel, DevicePanel)); 1170 | label_text(app->label_nof_devices, tc(msg)); 1171 | str_destroy(&msg); 1172 | } 1173 | } 1174 | 1175 | unref(prtime); 1176 | unref(ctime); 1177 | } 1178 | 1179 | /*---------------------------------------------------------------------------*/ 1180 | 1181 | #include "osmain.h" 1182 | osmain_sync(UDPATE_RATE_sec, i_create, i_destroy, i_update, "", App) 1183 | -------------------------------------------------------------------------------- /src/blescout.log: -------------------------------------------------------------------------------- 1 | [16:46:24] [FAIL] Heap Global Memory Leaks!!! 2 | [16:46:24] ================================== 3 | [16:46:24] Total a/dellocations: 442, 396 (46 leaks) 4 | [16:46:24] Total bytes a/dellocated: 47317, 44093 (3224 bytes) 5 | [16:46:24] Max bytes allocated: 45251 6 | [16:46:24] ================================== 7 | [16:46:24] Config: Release 8 | [16:46:24] You have an execution log in: 'blescout.log' 9 | -------------------------------------------------------------------------------- /src/connect.c: -------------------------------------------------------------------------------- 1 | /* Connect to BLE peripheral */ 2 | 3 | #include 4 | #include "logwin.h" 5 | #include "ble.h" 6 | #include "connect.h" 7 | #include "util.h" 8 | #include 9 | 10 | 11 | #define SHIFT_DEV 24 12 | #define SHIFT_SVC 16 13 | #define SHIFT_CHR 8 14 | #define SHIFT_DSC 0 15 | 16 | 17 | typedef struct _conn_descriptor_t ConnDescriptor; 18 | 19 | struct _conn_descriptor_t { 20 | simpleble_uuid_t uuid; 21 | size_t data_len; 22 | byte_t data[512]; 23 | 24 | Edit* edit; 25 | }; 26 | 27 | DeclSt(ConnDescriptor); 28 | 29 | 30 | typedef struct _conn_characteristic_t ConnCharacteristic; 31 | 32 | struct _conn_characteristic_t { 33 | simpleble_uuid_t uuid; 34 | bool_t can_read; 35 | bool_t can_write_request; 36 | bool_t can_write_command; 37 | bool_t can_notify; 38 | bool_t can_indicate; 39 | size_t data_len; 40 | byte_t data[512]; 41 | uint32_t descriptor_count; 42 | ArrSt(ConnDescriptor)* descriptor; 43 | 44 | Button* check_ascii; 45 | Edit* edit; 46 | Cell* cell_edit; 47 | GuiControl* control_edit; 48 | Button* button_read; 49 | Cell* cell_read; 50 | Button* button_write_cmd; 51 | Cell* cell_write_cmd; 52 | Button* button_write_req; 53 | Cell* cell_write_req; 54 | Button* button_notify; 55 | Cell* cell_notify; 56 | Button* button_indicate; 57 | Cell* cell_indicate; 58 | 59 | bool_t notification_active; 60 | bool_t indication_active; 61 | }; 62 | 63 | DeclSt(ConnCharacteristic); 64 | 65 | 66 | typedef struct _conn_service_t ConnService; 67 | 68 | struct _conn_service_t { 69 | simpleble_uuid_t uuid; 70 | uint32_t characteristic_count; 71 | ArrSt(ConnCharacteristic)* characteristic; 72 | 73 | Button* button_terminal; 74 | Cell* cell_terminal; 75 | 76 | bool_t is_vsp_service; 77 | }; 78 | 79 | DeclSt(ConnService); 80 | 81 | 82 | typedef struct _conndevice_t ConnDevice; 83 | 84 | struct _conndevice_t 85 | { 86 | bool_t is_connected; 87 | 88 | Window* window; 89 | Layout* layout_gatt; 90 | Button* button_disconnect; 91 | Cell* cell_disconnect; 92 | Label* label_mtu_size; 93 | 94 | simpleble_peripheral_t handle; 95 | String* device_name; 96 | String* mac_address; 97 | int16_t mtu_size; 98 | 99 | uint32_t service_count; 100 | ArrSt(ConnService)* service; 101 | }; 102 | 103 | DeclSt(ConnDevice); 104 | 105 | 106 | struct _connect_t 107 | { 108 | ArrSt(ConnDevice)* device; 109 | V2Df origin; 110 | }; 111 | 112 | static Connect* i_connect = NULL; 113 | 114 | 115 | /*---------------------------------------------------------------------------*/ 116 | 117 | static int i_compare_mac(const ConnDevice* conn_device, const String* s) 118 | { 119 | return str_scmp(conn_device->mac_address, s); 120 | } 121 | 122 | static void i_SimpleBleOnConnected(simpleble_peripheral_t peripheral, void* userdata) 123 | { 124 | char* peripheral_address = simpleble_peripheral_address(peripheral); 125 | char* peripheral_identifier = simpleble_peripheral_identifier(peripheral); 126 | 127 | String* mac = str_c(peripheral_address); 128 | uint32_t pos; 129 | ConnDevice* conn_device = arrst_search(i_connect->device, i_compare_mac, mac, &pos, ConnDevice, String); 130 | 131 | if (conn_device != NULL) 132 | { 133 | String* msg = str_printf("BLE connected to \"%s\"", peripheral_identifier); 134 | window_title(conn_device->window, tc(msg)); 135 | str_destroy(&msg); 136 | 137 | button_text(conn_device->button_disconnect, "Disconnect"); 138 | conn_device->is_connected = TRUE; 139 | 140 | arrst_foreach(con_svc, conn_device->service, ConnService) 141 | 142 | arrst_foreach(con_chr, con_svc->characteristic, ConnCharacteristic) 143 | 144 | if (con_chr->button_read != NULL) 145 | cell_enabled(con_chr->cell_read, TRUE); 146 | if (con_chr->button_write_cmd != NULL) 147 | cell_enabled(con_chr->cell_write_cmd, TRUE); 148 | if (con_chr->button_write_req != NULL) 149 | cell_enabled(con_chr->cell_write_req, TRUE); 150 | if (con_chr->button_notify != NULL) 151 | cell_enabled(con_chr->cell_notify, TRUE); 152 | if (con_chr->button_indicate != NULL) 153 | cell_enabled(con_chr->cell_indicate, TRUE); 154 | 155 | arrst_end() 156 | 157 | arrst_end() 158 | } 159 | 160 | log_printf("> Connected to \"%s\" %s\n", peripheral_identifier, tc(mac)); 161 | 162 | str_destroy(&mac); 163 | 164 | unref(userdata); 165 | } 166 | 167 | static void i_SimpleBleOnDisconnected(simpleble_peripheral_t peripheral, void* userdata) 168 | { 169 | char* peripheral_address = simpleble_peripheral_address(peripheral); 170 | char* peripheral_identifier = simpleble_peripheral_identifier(peripheral); 171 | 172 | String* mac = str_c(peripheral_address); 173 | uint32_t pos; 174 | ConnDevice* conn_device = arrst_search(i_connect->device, i_compare_mac, mac, &pos, ConnDevice, String); 175 | 176 | if (conn_device != NULL) 177 | { 178 | String* msg = str_printf("BLE disconnected from %s", tc(mac)); 179 | window_title(conn_device->window, tc(msg)); 180 | str_destroy(&msg); 181 | 182 | button_text(conn_device->button_disconnect, "Connect"); 183 | conn_device->is_connected = FALSE; 184 | 185 | arrst_foreach(con_svc, conn_device->service, ConnService) 186 | 187 | arrst_foreach(con_chr, con_svc->characteristic, ConnCharacteristic) 188 | 189 | con_chr->notification_active = FALSE; 190 | con_chr->indication_active = FALSE; 191 | 192 | if (con_chr->button_read != NULL) 193 | cell_enabled(con_chr->cell_read, FALSE); 194 | if (con_chr->button_write_cmd != NULL) 195 | cell_enabled(con_chr->cell_write_cmd, FALSE); 196 | if (con_chr->button_write_req != NULL) 197 | cell_enabled(con_chr->cell_write_req, FALSE); 198 | if (con_chr->button_notify != NULL) 199 | { 200 | Font* font = font_system(10, ekFNORMAL); 201 | button_font(con_chr->button_notify, font); 202 | cell_enabled(con_chr->cell_notify, FALSE); 203 | } 204 | if (con_chr->button_indicate != NULL) 205 | { 206 | Font* font = font_system(10, ekFNORMAL); 207 | button_font(con_chr->button_indicate, font); 208 | cell_enabled(con_chr->cell_indicate, FALSE); 209 | } 210 | 211 | arrst_end() 212 | 213 | arrst_end() 214 | } 215 | 216 | log_printf("> Disconnected from \"%s\" %s\n", peripheral_identifier, tc(mac)); 217 | 218 | str_destroy(&mac); 219 | 220 | unref(userdata); 221 | } 222 | 223 | static int i_compare_service_uuid(const ConnService* con_svc, const simpleble_uuid_t* uuid) 224 | { 225 | return str_cmp_c(con_svc->uuid.value, uuid->value); 226 | } 227 | 228 | static int i_compare_characteristic_uuid(const ConnCharacteristic* con_chr, const simpleble_uuid_t* uuid) 229 | { 230 | return str_cmp_c(con_chr->uuid.value, uuid->value); 231 | } 232 | 233 | static void i_SimpleBleOnNotification(simpleble_uuid_t service, simpleble_uuid_t characteristic, 234 | const uint8_t* data, size_t data_length, void* userdata) 235 | { 236 | ConnDevice* con_dev = NULL; 237 | ConnService* con_svc = NULL; 238 | ConnCharacteristic* con_chr = NULL; 239 | 240 | uint32_t pos; 241 | uint32_t i; 242 | for (i = 0; i < arrst_size(i_connect->device, ConnDevice); ++i) 243 | { 244 | con_dev = arrst_get(i_connect->device, i, ConnDevice); 245 | con_svc = arrst_search(con_dev->service, i_compare_service_uuid, &service, &pos, ConnService, simpleble_uuid_t); 246 | if (con_svc != NULL) 247 | { 248 | con_chr = arrst_search(con_svc->characteristic, i_compare_characteristic_uuid, &characteristic, &pos, ConnCharacteristic, simpleble_uuid_t); 249 | if (con_chr != NULL) 250 | { 251 | break; 252 | } 253 | } 254 | } 255 | 256 | if ((con_svc != NULL) && (con_chr != NULL)) 257 | { 258 | bmem_copy(con_chr->data, (byte_t*)data, (uint32_t)data_length); 259 | con_chr->data_len = data_length; 260 | 261 | char_t d[1024]; 262 | gui_state_t btn_state = button_get_state(con_chr->check_ascii); 263 | if (btn_state == ekGUI_ON) 264 | util_data_to_ascii(con_chr->data, d, con_chr->data_len); 265 | else 266 | util_data_to_hex(con_chr->data, d, con_chr->data_len); 267 | edit_text(con_chr->edit, d); 268 | } 269 | 270 | unref(userdata); 271 | } 272 | 273 | static void i_SimpleBleOnIndication(simpleble_uuid_t service, simpleble_uuid_t characteristic, 274 | const uint8_t* data, size_t data_length, void* userdata) 275 | { 276 | ConnDevice* con_dev = NULL; 277 | ConnService* con_svc = NULL; 278 | ConnCharacteristic* con_chr = NULL; 279 | 280 | uint32_t pos; 281 | uint32_t i; 282 | for (i = 0; i < arrst_size(i_connect->device, ConnDevice); ++i) 283 | { 284 | con_dev = arrst_get(i_connect->device, i, ConnDevice); 285 | con_svc = arrst_search(con_dev->service, i_compare_service_uuid, &service, &pos, ConnService, simpleble_uuid_t); 286 | if (con_svc != NULL) 287 | { 288 | con_chr = arrst_search(con_svc->characteristic, i_compare_characteristic_uuid, &characteristic, &pos, ConnCharacteristic, simpleble_uuid_t); 289 | if (con_chr != NULL) 290 | { 291 | break; 292 | } 293 | } 294 | } 295 | 296 | if ((con_svc != NULL) && (con_chr != NULL)) 297 | { 298 | bmem_copy(con_chr->data, (byte_t*)data, (uint32_t)data_length); 299 | con_chr->data_len = data_length; 300 | 301 | char_t d[1024]; 302 | gui_state_t btn_state = button_get_state(con_chr->check_ascii); 303 | if (btn_state == ekGUI_ON) 304 | util_data_to_ascii(con_chr->data, d, con_chr->data_len); 305 | else 306 | util_data_to_hex(con_chr->data, d, con_chr->data_len); 307 | edit_text(con_chr->edit, d); 308 | } 309 | 310 | unref(userdata); 311 | } 312 | 313 | /*---------------------------------------------------------------------------*/ 314 | 315 | static void i_OnCheckButtonHexAscii(Connect* connect, Event* e) 316 | { 317 | Button* b = event_sender(e, Button); 318 | gui_state_t btn_state = button_get_state(b); 319 | 320 | uint32_t tag = button_get_tag(b); 321 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 322 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 323 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 324 | 325 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 326 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 327 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 328 | 329 | char_t d[1024]; 330 | if (btn_state == ekGUI_ON) 331 | { 332 | if (con_chr->cell_write_cmd != NULL) 333 | cell_enabled(con_chr->cell_write_cmd, TRUE); 334 | if (con_chr->cell_write_req != NULL) 335 | cell_enabled(con_chr->cell_write_req, TRUE); 336 | edit_bgcolor(con_chr->edit, kCOLOR_DEFAULT); 337 | 338 | util_data_to_ascii(con_chr->data, d, con_chr->data_len); 339 | } 340 | else 341 | util_data_to_hex(con_chr->data, d, con_chr->data_len); 342 | 343 | edit_text(con_chr->edit, d); 344 | 345 | unref(e); 346 | } 347 | 348 | static void i_OnChangeEditCharacteristic(Connect* connect, Event* e) 349 | { 350 | Edit* edit = event_sender(e, Edit); 351 | GuiControl* control = guicontrol(edit); 352 | 353 | uint32_t tag = guicontrol_get_tag(control); 354 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 355 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 356 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 357 | 358 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 359 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 360 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 361 | 362 | const char_t* buf = edit_get_text(edit); 363 | uint32_t buf_len = str_len_c(buf); 364 | if (buf_len > 1024) 365 | buf_len = 1024; 366 | 367 | if (button_get_state(con_chr->check_ascii) == ekGUI_OFF) 368 | { 369 | if ((util_buffer_is_hex(buf, buf_len) == TRUE) || (buf_len == 0)) 370 | { 371 | if (con_chr->cell_write_cmd != NULL) 372 | cell_enabled(con_chr->cell_write_cmd, TRUE); 373 | if (con_chr->cell_write_req != NULL) 374 | cell_enabled(con_chr->cell_write_req, TRUE); 375 | edit_bgcolor(edit, kCOLOR_DEFAULT); 376 | 377 | util_hex_to_data(buf, con_chr->data, buf_len); 378 | con_chr->data_len = buf_len / 2; 379 | } 380 | else 381 | { 382 | if (con_chr->cell_write_cmd != NULL) 383 | cell_enabled(con_chr->cell_write_cmd, FALSE); 384 | if (con_chr->cell_write_req != NULL) 385 | cell_enabled(con_chr->cell_write_req, FALSE); 386 | edit_bgcolor(edit, color_html("#F1948A")); 387 | } 388 | } 389 | else 390 | { 391 | if (buf_len > 512) 392 | buf_len = 512; 393 | bmem_copy(con_chr->data, (byte_t*)buf, buf_len); 394 | con_chr->data_len = buf_len; 395 | } 396 | 397 | unref(e); 398 | } 399 | 400 | static void i_OnClickCharacteristicRead(Connect* connect, Event* e) 401 | { 402 | Button* b = event_sender(e, Button); 403 | 404 | uint32_t tag = button_get_tag(b); 405 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 406 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 407 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 408 | 409 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 410 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 411 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 412 | 413 | byte_t* data[1]; 414 | size_t data_len; 415 | 416 | if (simpleble_peripheral_read(con_dev->handle, con_svc->uuid, con_chr->uuid, data, &data_len) != SIMPLEBLE_SUCCESS) 417 | { 418 | log_printf("> \a1Read failed: DEV \"%s\" %s -> SVC %s -> CHR %s\n", 419 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 420 | 421 | return; 422 | } 423 | 424 | if (data_len > 512) 425 | data_len = 512; 426 | bmem_copy(con_chr->data, data[0], (uint32_t)data_len); 427 | con_chr->data_len = data_len; 428 | 429 | char_t d[1024]; 430 | if (button_get_state(con_chr->check_ascii) == ekGUI_ON) 431 | util_data_to_ascii(con_chr->data, d, data_len); 432 | else 433 | util_data_to_hex(con_chr->data, d, data_len); 434 | 435 | edit_text(con_chr->edit, d); 436 | 437 | util_data_to_hex(con_chr->data, d, data_len); 438 | log_printf("> Read %s from DEV \"%s\" %s -> SVC %s -> CHR %s\n", d, 439 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 440 | 441 | unref(e); 442 | } 443 | 444 | static void i_OnClickCharacteristicWriteCommand(Connect* connect, Event* e) 445 | { 446 | Button* b = event_sender(e, Button); 447 | 448 | uint32_t tag = button_get_tag(b); 449 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 450 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 451 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 452 | 453 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 454 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 455 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 456 | 457 | if (simpleble_peripheral_write_command(con_dev->handle, con_svc->uuid, con_chr->uuid, con_chr->data, con_chr->data_len) != SIMPLEBLE_SUCCESS) 458 | { 459 | log_printf("> \a1Write-command failed: DEV \"%s\" %s -> SVC %s -> CHR %s\n", 460 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 461 | 462 | return; 463 | } 464 | 465 | char_t d[1024]; 466 | util_data_to_hex(con_chr->data, d, con_chr->data_len); 467 | log_printf("> Write-command %s to DEV \"%s\" %s -> SVC %s -> CHR %s\n", d, 468 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 469 | 470 | unref(e); 471 | } 472 | 473 | static void i_OnClickCharacteristicWriteRequest(Connect* connect, Event* e) 474 | { 475 | Button* b = event_sender(e, Button); 476 | 477 | uint32_t tag = button_get_tag(b); 478 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 479 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 480 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 481 | 482 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 483 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 484 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 485 | 486 | if (simpleble_peripheral_write_request(con_dev->handle, con_svc->uuid, con_chr->uuid, con_chr->data, con_chr->data_len) != SIMPLEBLE_SUCCESS) 487 | { 488 | log_printf("> \a1Write-request failed: DEV \"%s\" %s -> SVC %s -> CHR %s\n", 489 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 490 | 491 | return; 492 | } 493 | 494 | char_t d[1024]; 495 | util_data_to_hex(con_chr->data, d, con_chr->data_len); 496 | log_printf("> Write-request %s to DEV \"%s\" %s -> SVC %s -> CHR %s\n", d, 497 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 498 | 499 | unref(e); 500 | } 501 | 502 | static void i_OnClickCharacteristicSubscribeNotification(Connect* connect, Event* e) 503 | { 504 | Button* b = event_sender(e, Button); 505 | 506 | uint32_t tag = button_get_tag(b); 507 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 508 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 509 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 510 | 511 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 512 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 513 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 514 | 515 | void* userdata = NULL; 516 | 517 | if (simpleble_peripheral_notify(con_dev->handle, con_svc->uuid, con_chr->uuid, i_SimpleBleOnNotification, userdata) != SIMPLEBLE_SUCCESS) 518 | { 519 | log_printf("> \a1Notifications failed: DEV \"%s\" %s -> SVC %s -> CHR %s\n", 520 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 521 | 522 | return; 523 | } 524 | 525 | Font* font = font_system(10, ekFBOLD); 526 | button_font(con_chr->button_notify, font); 527 | 528 | log_printf("> Notifications active for DEV \"%s\" %s -> SVC %s -> CHR %s\n", 529 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 530 | 531 | unref(e); 532 | } 533 | 534 | static void i_OnClickCharacteristicSubscribeIndication(Connect* connect, Event* e) 535 | { 536 | Button* b = event_sender(e, Button); 537 | 538 | uint32_t tag = button_get_tag(b); 539 | uint32_t dev_idx = (tag >> SHIFT_DEV) & 0xff; 540 | uint32_t svc_idx = (tag >> SHIFT_SVC) & 0xff; 541 | uint32_t chr_idx = (tag >> SHIFT_CHR) & 0xff; 542 | 543 | ConnDevice* con_dev = arrst_get(connect->device, dev_idx, ConnDevice); 544 | ConnService* con_svc = arrst_get(con_dev->service, svc_idx, ConnService); 545 | ConnCharacteristic* con_chr = arrst_get(con_svc->characteristic, chr_idx, ConnCharacteristic); 546 | 547 | void* userdata = NULL; 548 | 549 | if (simpleble_peripheral_indicate(con_dev->handle, con_svc->uuid, con_chr->uuid, i_SimpleBleOnNotification, userdata) != SIMPLEBLE_SUCCESS) 550 | { 551 | log_printf("> \a1Indications failed: DEV \"%s\" %s -> SVC %s -> CHR %s\n", 552 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 553 | 554 | return; 555 | } 556 | 557 | Font* font = font_system(10, ekFBOLD); 558 | button_font(con_chr->button_indicate, font); 559 | 560 | log_printf("> Indications active for DEV \"%s\" %s -> SVC %s -> CHR %s\n", 561 | tc(con_dev->device_name), tc(con_dev->mac_address), con_svc->uuid.value, con_chr->uuid.value); 562 | 563 | unref(e); 564 | } 565 | 566 | /*---------------------------------------------------------------------------*/ 567 | 568 | static bool_t i_connect_peripheral(ConnDevice* conn_device) 569 | { 570 | uint32_t dev_idx; 571 | arrst_search(i_connect->device, i_compare_mac, conn_device->mac_address, &dev_idx, ConnDevice, String); 572 | 573 | if (simpleble_peripheral_connect(conn_device->handle) != SIMPLEBLE_SUCCESS) 574 | { 575 | log_printf("> \a1Could not connect to \"%s\" %s.\n", tc(conn_device->device_name), tc(conn_device->mac_address)); 576 | return FALSE; 577 | } 578 | 579 | button_text(conn_device->button_disconnect, "Disconnect"); 580 | cell_enabled(conn_device->cell_disconnect, TRUE); 581 | 582 | conn_device->mtu_size = simpleble_peripheral_mtu(conn_device->handle) + 3; 583 | String* msg = str_printf("MTU Size: %d", conn_device->mtu_size); 584 | label_text(conn_device->label_mtu_size, tc(msg)); 585 | str_destroy(&msg); 586 | 587 | log_printf("> Reading GATT table from \"%s\" %s\n", tc(conn_device->device_name), tc(conn_device->mac_address)); 588 | 589 | conn_device->service_count = (uint32_t)simpleble_peripheral_services_count(conn_device->handle); 590 | uint32_t svc_idx; 591 | for (svc_idx = 0; svc_idx < conn_device->service_count; ++svc_idx) 592 | { 593 | ConnService* conn_svc = arrst_new0(conn_device->service, ConnService); 594 | conn_svc->characteristic = arrst_create(ConnCharacteristic); 595 | 596 | simpleble_service_t svc; 597 | simpleble_peripheral_services_get(conn_device->handle, svc_idx, &svc); 598 | 599 | bmem_copy_n(conn_svc->uuid.value, svc.uuid.value, SIMPLEBLE_UUID_STR_LEN, char); 600 | 601 | uint32_t nrows = layout_nrows(conn_device->layout_gatt); 602 | cassert(nrows >= 1); 603 | layout_insert_row(conn_device->layout_gatt, nrows); 604 | 605 | 606 | Layout* layout_service = layout_create(1, 2); 607 | Layout* layout_uuid_terminal = layout_create(2, 1); 608 | layout_hexpand(layout_uuid_terminal, 0); 609 | layout_skcolor(layout_service, color_html("#ABB2B9")); 610 | layout_bgcolor(layout_service, color_html("#EBF5FB")); 611 | 612 | Font* font_service = font_system(14, ekFBOLD); 613 | Label* label_service = label_create(); 614 | label_font(label_service, font_service); 615 | String* svc_name = util_service_uuid_to_name(conn_svc->uuid.value); 616 | if (str_empty(svc_name)) 617 | { 618 | str_destroy(&svc_name); 619 | svc_name = util_vsp_service_uuid_to_name(conn_svc->uuid.value); 620 | if (str_empty(svc_name)) 621 | { 622 | str_destroy(&svc_name); 623 | svc_name = str_c(conn_svc->uuid.value); 624 | conn_svc->is_vsp_service = FALSE; 625 | } 626 | else 627 | { 628 | conn_svc->is_vsp_service = TRUE; 629 | } 630 | } 631 | label_text(label_service, tc(svc_name)); 632 | log_printf("> \a2SVC_____ %s\n", tc(svc_name)); 633 | str_destroy(&svc_name); 634 | layout_label(layout_uuid_terminal, label_service, 0, 0); 635 | cell_padding4(layout_cell(layout_uuid_terminal, 0, 0), 5, 5, 0, 10); 636 | 637 | if (conn_svc->is_vsp_service == TRUE) 638 | { 639 | Font* font_button = font_system(10, ekFNORMAL); 640 | conn_svc->button_terminal = button_push(); 641 | button_font(conn_svc->button_terminal, font_button); 642 | button_text(conn_svc->button_terminal, "VSP Terminal"); 643 | layout_button(layout_uuid_terminal, conn_svc->button_terminal, 1, 0); 644 | conn_svc->cell_terminal = layout_cell(layout_uuid_terminal, 1, 0); 645 | cell_enabled(conn_svc->cell_terminal, FALSE); 646 | } 647 | cell_padding4(layout_cell(layout_uuid_terminal, 1, 0), 5, 10, 0, 5); 648 | layout_layout(layout_service, layout_uuid_terminal, 0, 0); 649 | 650 | conn_svc->characteristic_count = (uint32_t)svc.characteristic_count; 651 | 652 | if (conn_svc->characteristic_count > 0) 653 | { 654 | Layout* layout_chr_list = layout_create(2, 1); 655 | layout_layout(layout_service, layout_chr_list, 0, 1); 656 | cell_padding4(layout_cell(layout_service, 0, 1), 5, 0, 0, 20); 657 | 658 | uint32_t chr_rows = 0; 659 | uint32_t chr_idx; 660 | for (chr_idx = 0; chr_idx < conn_svc->characteristic_count; ++chr_idx) 661 | { 662 | ConnCharacteristic* conn_chr = arrst_new0(conn_svc->characteristic, ConnCharacteristic); 663 | conn_chr->descriptor = arrst_create(ConnDescriptor); 664 | bmem_copy_n(conn_chr->uuid.value, svc.characteristics[chr_idx].uuid.value, SIMPLEBLE_UUID_STR_LEN, char); 665 | conn_chr->can_read = svc.characteristics[chr_idx].can_read ? TRUE : FALSE; 666 | conn_chr->can_write_command = svc.characteristics[chr_idx].can_write_command ? TRUE : FALSE; 667 | conn_chr->can_write_request = svc.characteristics[chr_idx].can_write_request ? TRUE : FALSE; 668 | conn_chr->can_notify = svc.characteristics[chr_idx].can_notify ? TRUE : FALSE; 669 | conn_chr->can_indicate = svc.characteristics[chr_idx].can_indicate ? TRUE : FALSE; 670 | conn_chr->data_len = 0; 671 | conn_chr->notification_active = FALSE; 672 | conn_chr->indication_active = FALSE; 673 | 674 | cell_padding4(layout_cell(layout_chr_list, 0, chr_rows), 5, 10, 0, 0); 675 | cell_padding4(layout_cell(layout_chr_list, 1, chr_rows), 5, 0, 0, 0); 676 | 677 | Font* font_buttons = font_system(10, ekFNORMAL); 678 | 679 | Label* label_characteristic = label_create(); 680 | String* chr_name = util_characteristic_uuid_to_name(conn_chr->uuid.value); 681 | if (str_empty(chr_name)) 682 | { 683 | str_destroy(&chr_name); 684 | chr_name = util_vsp_characteristic_uuid_to_name(conn_chr->uuid.value); 685 | if (str_empty(chr_name)) 686 | { 687 | str_destroy(&chr_name); 688 | chr_name = str_c(conn_chr->uuid.value); 689 | } 690 | } 691 | label_text(label_characteristic, tc(chr_name)); 692 | log_printf("> \a3CHR___ %s\n", tc(chr_name)); 693 | str_destroy(&chr_name); 694 | layout_label(layout_chr_list, label_characteristic, 0, chr_rows); 695 | 696 | conn_chr->check_ascii = button_check(); 697 | button_text(conn_chr->check_ascii, "ASCII"); 698 | button_font(conn_chr->check_ascii, font_buttons); 699 | button_tag(conn_chr->check_ascii, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 700 | button_OnClick(conn_chr->check_ascii, listener(i_connect, i_OnCheckButtonHexAscii, Connect)); 701 | layout_button(layout_chr_list, conn_chr->check_ascii, 1, chr_rows); 702 | 703 | chr_rows++; 704 | layout_insert_row(layout_chr_list, chr_rows); 705 | cell_padding4(layout_cell(layout_chr_list, 0, chr_rows), 0, 10, 0, 0); 706 | cell_padding4(layout_cell(layout_chr_list, 1, chr_rows), 0, 0, 0, 0); 707 | 708 | conn_chr->edit = edit_create(); 709 | edit_editable(conn_chr->edit, FALSE); 710 | conn_chr->control_edit = guicontrol(conn_chr->edit); 711 | guicontrol_tag(conn_chr->control_edit, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 712 | edit_OnChange(conn_chr->edit, listener(i_connect, i_OnChangeEditCharacteristic, Connect)); 713 | layout_edit(layout_chr_list, conn_chr->edit, 0, chr_rows); 714 | layout_hsize(layout_chr_list, 0, 220.0); 715 | 716 | uint8_t* data[1]; 717 | if (simpleble_peripheral_read(conn_device->handle, conn_svc->uuid, conn_chr->uuid, data, &conn_chr->data_len) == SIMPLEBLE_SUCCESS) 718 | { 719 | bmem_copy(conn_chr->data, data[0], (uint32_t)conn_chr->data_len); 720 | char_t d[1024]; 721 | util_data_to_hex(conn_chr->data, d, conn_chr->data_len); 722 | edit_text(conn_chr->edit, d); 723 | } 724 | 725 | conn_chr->cell_write_cmd = NULL; 726 | conn_chr->cell_write_req = NULL; 727 | 728 | Layout* layout_buttons = layout_create(5, 1); 729 | 730 | uint32_t col = 0; 731 | if (conn_chr->can_read) 732 | { 733 | conn_chr->button_read = button_push(); 734 | button_text(conn_chr->button_read, "RD"); 735 | button_font(conn_chr->button_read, font_buttons); 736 | button_tag(conn_chr->button_read, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 737 | button_OnClick(conn_chr->button_read, listener(i_connect, i_OnClickCharacteristicRead, Connect)); 738 | layout_button(layout_buttons, conn_chr->button_read, col, 0); 739 | layout_hsize(layout_buttons, col, 20.0); 740 | conn_chr->cell_read = layout_cell(layout_buttons, col, 0); 741 | col++; 742 | } 743 | if (conn_chr->can_write_command) 744 | { 745 | edit_editable(conn_chr->edit, TRUE); 746 | conn_chr->button_write_cmd = button_push(); 747 | button_text(conn_chr->button_write_cmd, "WRc"); 748 | button_font(conn_chr->button_write_cmd, font_buttons); 749 | button_tag(conn_chr->button_write_cmd, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 750 | button_OnClick(conn_chr->button_write_cmd, listener(i_connect, i_OnClickCharacteristicWriteCommand, Connect)); 751 | layout_button(layout_buttons, conn_chr->button_write_cmd, col, 0); 752 | layout_hsize(layout_buttons, col, 20.0); 753 | conn_chr->cell_write_cmd = layout_cell(layout_buttons, col, 0); 754 | col++; 755 | } 756 | if (conn_chr->can_write_request) 757 | { 758 | edit_editable(conn_chr->edit, TRUE); 759 | conn_chr->button_write_req = button_push(); 760 | button_text(conn_chr->button_write_req, "WRr"); 761 | button_font(conn_chr->button_write_req, font_buttons); 762 | button_tag(conn_chr->button_write_req, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 763 | button_OnClick(conn_chr->button_write_req, listener(i_connect, i_OnClickCharacteristicWriteRequest, Connect)); 764 | layout_button(layout_buttons, conn_chr->button_write_req, col, 0); 765 | layout_hsize(layout_buttons, col, 20.0); 766 | conn_chr->cell_write_req = layout_cell(layout_buttons, col, 0); 767 | col++; 768 | } 769 | if (conn_chr->can_notify) 770 | { 771 | conn_chr->button_notify = button_push(); 772 | button_text(conn_chr->button_notify, "NOT"); 773 | button_font(conn_chr->button_notify, font_buttons); 774 | button_tag(conn_chr->button_notify, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 775 | button_OnClick(conn_chr->button_notify, listener(i_connect, i_OnClickCharacteristicSubscribeNotification, Connect)); 776 | layout_button(layout_buttons, conn_chr->button_notify, col, 0); 777 | layout_hsize(layout_buttons, col, 20.0); 778 | conn_chr->cell_notify = layout_cell(layout_buttons, col, 0); 779 | col++; 780 | } 781 | if (conn_chr->can_indicate) 782 | { 783 | conn_chr->button_indicate = button_push(); 784 | button_text(conn_chr->button_indicate, "IND"); 785 | button_font(conn_chr->button_indicate, font_buttons); 786 | button_tag(conn_chr->button_indicate, (dev_idx << SHIFT_DEV) | (svc_idx << SHIFT_SVC) | (chr_idx << SHIFT_CHR)); 787 | button_OnClick(conn_chr->button_indicate, listener(i_connect, i_OnClickCharacteristicSubscribeIndication, Connect)); 788 | layout_button(layout_buttons, conn_chr->button_indicate, col, 0); 789 | conn_chr->cell_indicate = layout_cell(layout_buttons, col, 0); 790 | layout_hsize(layout_buttons, col, 20.0); 791 | col++; 792 | } 793 | layout_layout(layout_chr_list, layout_buttons, 1, chr_rows); 794 | 795 | chr_rows++; 796 | layout_insert_row(layout_chr_list, chr_rows); 797 | 798 | 799 | conn_chr->descriptor_count = (uint32_t)svc.characteristics[chr_idx].descriptor_count; 800 | 801 | if (conn_chr->descriptor_count > 0) 802 | { 803 | Layout* layout_dsc_list = layout_create(2, 1); 804 | layout_hexpand(layout_dsc_list, 1); 805 | layout_layout(layout_chr_list, layout_dsc_list, 0, chr_rows); 806 | 807 | uint32_t dsc_rows = 0; 808 | uint32_t dsc_idx; 809 | for (dsc_idx = 0; dsc_idx < conn_chr->descriptor_count; ++dsc_idx) 810 | { 811 | ConnDescriptor* conn_dsc = arrst_new0(conn_chr->descriptor, ConnDescriptor); 812 | bmem_copy_n(conn_dsc->uuid.value, svc.characteristics[chr_idx].descriptors[dsc_idx].uuid.value, SIMPLEBLE_UUID_STR_LEN, char); 813 | conn_dsc->data_len = 0; 814 | 815 | cell_padding4(layout_cell(layout_dsc_list, 0, dsc_rows), 5, 10, 0, 20); 816 | 817 | Label* label_descriptor = label_create(); 818 | String* dsc_name = util_descriptor_uuid_to_name(conn_dsc->uuid.value); 819 | if (str_empty(dsc_name)) 820 | label_text(label_descriptor, conn_dsc->uuid.value); 821 | else 822 | label_text(label_descriptor, tc(dsc_name)); 823 | layout_label(layout_dsc_list, label_descriptor, 0, dsc_rows); 824 | log_printf("> \a4DSC_ %s\n", tc(dsc_name)); 825 | str_destroy(&dsc_name); 826 | 827 | dsc_rows++; 828 | layout_insert_row(layout_dsc_list, dsc_rows); 829 | cell_padding4(layout_cell(layout_dsc_list, 0, dsc_rows), 0, 10, 0, 20); 830 | 831 | conn_dsc->edit = edit_create(); 832 | edit_editable(conn_dsc->edit, FALSE); 833 | layout_edit(layout_dsc_list, conn_dsc->edit, 0, dsc_rows); 834 | 835 | if (simpleble_peripheral_read_descriptor(conn_device->handle, conn_svc->uuid, conn_chr->uuid, conn_dsc->uuid, data, &conn_dsc->data_len) == SIMPLEBLE_SUCCESS) 836 | { 837 | bmem_copy(conn_dsc->data, data[0], (uint32_t)conn_dsc->data_len); 838 | char_t d[1024]; 839 | util_data_to_hex(conn_dsc->data, d, conn_dsc->data_len); 840 | edit_text(conn_dsc->edit, d); 841 | } 842 | 843 | dsc_rows++; 844 | layout_insert_row(layout_dsc_list, dsc_rows); 845 | } 846 | 847 | } 848 | chr_rows++; 849 | layout_insert_row(layout_chr_list, chr_rows); 850 | } 851 | 852 | cell_padding4(layout_cell(layout_chr_list, 0, chr_rows), 0, 10, 10, 0); 853 | cell_padding4(layout_cell(layout_chr_list, 1, chr_rows), 0, 0, 10, 0); 854 | } 855 | 856 | if(conn_svc->characteristic_count == 0) 857 | cell_padding4(layout_cell(layout_service, 0, 1), 0, 10, 10, 10); 858 | 859 | layout_layout(conn_device->layout_gatt, layout_service, 0, nrows); 860 | cell_padding4(layout_cell(conn_device->layout_gatt, 0, nrows), 5, 5, 5, 5); 861 | 862 | layout_update(conn_device->layout_gatt); 863 | } 864 | 865 | return TRUE; 866 | } 867 | 868 | /*---------------------------------------------------------------------------*/ 869 | 870 | static void i_OnClickConnectDisconnectDevice(ConnDevice* conn_device, Event* e) 871 | { 872 | if (conn_device->is_connected) 873 | { 874 | if (simpleble_peripheral_disconnect(conn_device->handle) != SIMPLEBLE_SUCCESS) 875 | { 876 | log_printf("> \a1Could not disconnect from \"%s\" %s.\n", tc(conn_device->device_name), tc(conn_device->mac_address)); 877 | } 878 | } 879 | else 880 | { 881 | if (simpleble_peripheral_connect(conn_device->handle) != SIMPLEBLE_SUCCESS) 882 | { 883 | log_printf("> \a1Could not connect to \"%s\" %s.\n", tc(conn_device->device_name), tc(conn_device->mac_address)); 884 | } 885 | } 886 | 887 | unref(e); 888 | } 889 | 890 | /*---------------------------------------------------------------------------*/ 891 | 892 | static Panel* i_panel(ConnDevice* conn_device) 893 | { 894 | Panel* panel = panel_create(); 895 | 896 | Layout* layout = layout_create(1, 3); 897 | layout_vexpand(layout, 2); 898 | 899 | Layout* layout_name_discon = layout_create(3, 1); 900 | layout_hexpand(layout_name_discon, 1); 901 | Layout* layout_mac_mtu = layout_create(3, 1); 902 | layout_hexpand(layout_mac_mtu, 1); 903 | 904 | layout_layout(layout, layout_name_discon, 0, 0); 905 | layout_layout(layout, layout_mac_mtu, 0, 1); 906 | 907 | 908 | Font* font_dev_name = font_system(16, ekFBOLD); 909 | Edit* edit_dev_name = edit_create(); 910 | edit_font(edit_dev_name, font_dev_name); 911 | edit_text(edit_dev_name, tc(conn_device->device_name)); 912 | edit_editable(edit_dev_name, FALSE); 913 | layout_edit(layout_name_discon, edit_dev_name, 0, 0); 914 | layout_hsize(layout_name_discon, 0, 220.0); 915 | 916 | conn_device->button_disconnect = button_push(); 917 | button_text(conn_device->button_disconnect, "Connecting..."); 918 | conn_device->cell_disconnect = layout_cell(layout_name_discon, 2, 0); 919 | cell_enabled(conn_device->cell_disconnect, FALSE); 920 | button_OnClick(conn_device->button_disconnect, listener(conn_device, i_OnClickConnectDisconnectDevice, ConnDevice)); 921 | layout_button(layout_name_discon, conn_device->button_disconnect, 2, 0); 922 | cell_padding4(layout_cell(layout, 0, 0), 10, 20, 5, 10); 923 | 924 | 925 | Label* label_mac = label_create(); 926 | String* msg = str_printf("MAC Address: %s", tc(conn_device->mac_address)); 927 | label_text(label_mac, tc(msg)); 928 | str_destroy(&msg); 929 | layout_label(layout_mac_mtu, label_mac, 0, 0); 930 | 931 | conn_device->label_mtu_size = label_create(); 932 | label_text(conn_device->label_mtu_size, "MTU Size: n/a"); 933 | layout_label(layout_mac_mtu, conn_device->label_mtu_size, 2, 0); 934 | cell_padding4(layout_cell(layout, 0, 1), 10, 20, 10, 10); 935 | 936 | 937 | Panel* panel_gatt = panel_scroll(FALSE, TRUE); 938 | real32_t scrw = panel_scroll_width(panel_gatt); 939 | 940 | layout_panel(layout, panel_gatt, 0, 2); 941 | 942 | Layout* layout_services = layout_create(1, 2); 943 | conn_device->layout_gatt = layout_create(1, 1); 944 | layout_layout(layout_services, conn_device->layout_gatt, 0, 0); 945 | layout_margin4(layout_services, 0, scrw, 0, 0); 946 | layout_vexpand(layout_services, 1); 947 | 948 | panel_size(panel_gatt, s2df(420, 500)); 949 | panel_layout(panel, layout); 950 | panel_layout(panel_gatt, layout_services); 951 | 952 | 953 | return panel; 954 | } 955 | 956 | /*---------------------------------------------------------------------------*/ 957 | 958 | static void i_OnClose(ConnDevice* conn_device, Event* e) 959 | { 960 | unref(conn_device); 961 | unref(e); 962 | } 963 | 964 | /*---------------------------------------------------------------------------*/ 965 | 966 | bool_t connect_device(String* mac_address, const V2Df pos) 967 | { 968 | ConnDevice* conn_dev = arrst_new0(i_connect->device, ConnDevice); 969 | 970 | BlePeripheral* per = ble_get_peripheral(tc(mac_address)); 971 | 972 | if (per == 0) 973 | { 974 | log_printf("> \a1Cannot connect unknown device %s.\n", tc(mac_address)); 975 | 976 | return FALSE; 977 | } 978 | 979 | if (str_empty(per->identifier)) 980 | conn_dev->device_name = str_c("unknown"); 981 | else 982 | conn_dev->device_name = per->identifier; 983 | conn_dev->mac_address = per->mac_address; 984 | conn_dev->handle = per->handle; 985 | conn_dev->mtu_size = 0; 986 | conn_dev->service_count = 0; 987 | conn_dev->service = arrst_create(ConnService); 988 | 989 | 990 | Panel* panel = i_panel(conn_dev); 991 | 992 | conn_dev->window = window_create(ekWINDOW_STD | ekWINDOW_RESIZE); 993 | window_panel(conn_dev->window, panel); 994 | String* s = str_printf("BLE connecting to %s...", tc(conn_dev->mac_address)); 995 | window_title(conn_dev->window, tc(s)); 996 | str_destroy(&s); 997 | window_OnClose(conn_dev->window, listener(conn_dev, i_OnClose, ConnDevice)); 998 | 999 | window_origin(conn_dev->window, pos); 1000 | window_show(conn_dev->window); 1001 | 1002 | conn_dev->is_connected = FALSE; 1003 | 1004 | uint32_t i = simpleble_peripheral_set_callback_on_connected(conn_dev->handle, i_SimpleBleOnConnected, NULL); 1005 | i += simpleble_peripheral_set_callback_on_disconnected(conn_dev->handle, i_SimpleBleOnDisconnected, NULL); 1006 | if (i > 0) 1007 | log_printf("> \a1Cannot register BLE connect/disconnect callback functions.\n"); 1008 | 1009 | return i_connect_peripheral(conn_dev); 1010 | } 1011 | 1012 | /*---------------------------------------------------------------------------*/ 1013 | 1014 | Connect* connect_create(void) 1015 | { 1016 | Connect* connect = heap_new0(Connect); 1017 | i_connect = connect; 1018 | 1019 | connect->device = arrst_create(ConnDevice); 1020 | 1021 | return connect; 1022 | } 1023 | 1024 | static void i_remove_characteristic(ConnCharacteristic* conn_chr) 1025 | { 1026 | arrst_destroy(&conn_chr->descriptor, NULL, ConnDescriptor); 1027 | } 1028 | 1029 | static void i_remove_service(ConnService* conn_svc) 1030 | { 1031 | arrst_destroy(&conn_svc->characteristic, i_remove_characteristic, ConnCharacteristic); 1032 | } 1033 | 1034 | static void i_remove_device(ConnDevice* conn_device) 1035 | { 1036 | str_destroy(&conn_device->device_name); 1037 | str_destroy(&conn_device->mac_address); 1038 | arrst_destroy(&conn_device->service, i_remove_service, ConnService); 1039 | window_destroy(&conn_device->window); 1040 | } 1041 | 1042 | void connect_destroy(Connect** connect) 1043 | { 1044 | arrst_destroy(&(*connect)->device, i_remove_device, ConnDevice); 1045 | heap_delete(connect, Connect); 1046 | } 1047 | -------------------------------------------------------------------------------- /src/connect.h: -------------------------------------------------------------------------------- 1 | /* Connect to BLE peripheral */ 2 | 3 | #include "connect.hxx" 4 | 5 | 6 | Connect* connect_create(void); 7 | 8 | void connect_destroy(Connect** con); 9 | 10 | bool_t connect_device(String* mac, const V2Df pos); 11 | -------------------------------------------------------------------------------- /src/connect.hxx: -------------------------------------------------------------------------------- 1 | /* Connect to BLE peripheral */ 2 | 3 | typedef struct _connect_t Connect; 4 | -------------------------------------------------------------------------------- /src/help.c: -------------------------------------------------------------------------------- 1 | /* Help window */ 2 | 3 | #include 4 | #include "help.h" 5 | #include "help_rtf.h" 6 | 7 | 8 | struct _help_t 9 | { 10 | Window* window; 11 | Stream* stream; 12 | TextView* text; 13 | 14 | bool_t is_open; 15 | }; 16 | 17 | static Help* i_help = NULL; 18 | 19 | /*---------------------------------------------------------------------------*/ 20 | 21 | static void i_OnClose(Help* help, Event* e) 22 | { 23 | window_hide(help->window); 24 | 25 | help->is_open = FALSE; 26 | 27 | unref(e); 28 | } 29 | 30 | static Panel* i_panel(Help* help) 31 | { 32 | Panel* panel = panel_create(); 33 | 34 | Layout* layout = layout_create(1, 1); 35 | 36 | help->text = textview_create(); 37 | layout_textview(layout, help->text, 0, 0); 38 | 39 | layout_hsize(layout, 0, 600); 40 | layout_vsize(layout, 0, 600); 41 | layout_margin(layout, 5); 42 | 43 | panel_layout(panel, layout); 44 | 45 | return panel; 46 | 47 | unref(help); 48 | } 49 | 50 | Help* help_create(void) 51 | { 52 | Help* help = heap_new0(Help); 53 | 54 | i_help = help; 55 | 56 | Panel* panel = i_panel(help); 57 | 58 | help->window = window_create(ekWINDOW_STD | ekWINDOW_RESIZE); 59 | window_panel(help->window, panel); 60 | window_title(help->window, "BLE Scout - Help"); 61 | window_OnClose(help->window, listener(help, i_OnClose, Help)); 62 | 63 | help->stream = stm_from_block((byte_t*)help_txt, str_len_c(help_txt)); 64 | textview_rtf(help->text, help->stream); 65 | 66 | return help; 67 | } 68 | 69 | void help_destroy(Help** help) 70 | { 71 | heap_delete(help, Help); 72 | } 73 | 74 | /*---------------------------------------------------------------------------*/ 75 | 76 | void help_show(Help* help, const V2Df pos) 77 | { 78 | window_origin(help->window, pos); 79 | window_show(help->window); 80 | 81 | help->is_open = TRUE; 82 | } 83 | 84 | void help_hide(Help* help) 85 | { 86 | window_hide(help->window); 87 | 88 | help->is_open = FALSE; 89 | } 90 | 91 | bool_t help_is_open(void) 92 | { 93 | return i_help->is_open; 94 | } 95 | -------------------------------------------------------------------------------- /src/help.h: -------------------------------------------------------------------------------- 1 | /* Help Window */ 2 | 3 | #include "help.hxx" 4 | 5 | Help* help_create(void); 6 | 7 | void help_destroy(Help** help); 8 | 9 | void help_show(Help* help, const V2Df pos); 10 | 11 | void help_hide(Help* help); 12 | 13 | bool_t help_is_open(void); 14 | -------------------------------------------------------------------------------- /src/help.hxx: -------------------------------------------------------------------------------- 1 | /* Help Window */ 2 | 3 | typedef struct _help_t Help; 4 | -------------------------------------------------------------------------------- /src/help_rtf.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const char_t help_txt[] = { 4 | "{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1031{\\fonttbl{\\f0\\fnil\\fcharset0 Calibri;}{\\f1\\fnil Calibri;}{\\f2\\fnil\\fcharset2 Symbol;}} \ 5 | {\\colortbl ;\\red0\\green128\\blue192;\\red0\\green0\\blue255;} \ 6 | {\\*\\generator Riched20 10.0.22621}\\viewkind4\\uc1 \ 7 | \\pard\\sa200\\sl276\\slmult1\\cf1\\b\\f0\\fs32\\lang9 BLE Scout - Bluetooth LE Tool - V0.3\\par \ 8 | \\cf0\\b0\\fs22 BLE Scout is a cross-platform desktop application leveraging the PC integrated Bluetooth controller for accessing Bluetooth LE devices. It supports scanning for BLE advertisements, displaying the exposed advert data, connecting to devices, reading their GATT table and read/write/notify/indicate charcteristics.\\par \ 9 | \\b\\fs28 Main Scan Window\\par \ 10 | \\b0\\fs22 After start the main scan window opens and provides common controls for BLE device discovery.\\par \ 11 | \\b\\fs24 Start/Stop Scanning\\par \ 12 | \\b0\\fs22 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.\\par \ 13 | \\b\\fs24 Filters\\b0\\fs22\\par \ 14 | Several 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 (not case-sensitive). Filtering 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). Filtering on the RSSI value of advertisments which \\f1\\endash\\f0 to some extent \\f1\\endash\\f0 represents the distance of a device. Values have to be entered as negative numbers. Two check boxes allow filtering filtering for only connectable devices or paired devices.\\par \ 15 | \\b\\fs24 Log Output\\par \ 16 | \\b0\\fs22 A more detailed log output can be enabled by ticking the Show Log Output box. A second log window will open with more verbose logging information on device scanning and during connection with a device. It's possible to add time stamps to the logging as well as copy the log content to the clipboard or save the log output into a file. The file is automatically named with timestamp information.\\par \ 17 | \\b\\fs24 Scan Data Panel\\par \ 18 | \\b0\\fs22 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, service data and manufacturer specific data.\\par \ 19 | If the UUID of a service or the company ID in the manufacturer data is in the official Bluetooth list of assigned numbers, the official name will be shown.\\par \ 20 | Manufacturer data can be displayed either as HEX numbers or ASCII string. For ASCII non printable characters are displayed as '#'.\\par \ 21 | \\b\\fs24 Connect Device\\par \ 22 | \\b0\\fs22 If a device is connectable you can engage a connection with the Connect button.\\par \ 23 | \\b\\fs28 Device Connect Window\\par \ 24 | \\b0\\fs22 On connect the GATT table of the device is being read and services, corresponding characteristics and descriptors are shown in a panel structure.\\par \ 25 | If the UUID of a service, characteristic or descriptor is 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 UUID number.\\par \ 26 | Depending on the properties of each characteristic different buttons get added to the text field. Those are Read, WriteCommand, WriteRequest, Notify and Indicate. 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.\\par \ 27 | \\b\\fs24 Virtual Serial Port (VSP)\\par \ 28 | \\b0\\fs22 Beside the official Bluetooth list of services, there exist proprietary services to expose a virtual serial port service (similar to SPP service from Bluetooth Classic). BLE-Scout 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.\\par \ 29 | \ 30 | \\pard{\\pntext\\f2\\'B7\\tab}{\\*\\pn\\pnlvlblt\\pnf2\\pnindent0{\\pntxtb\\'B7}}\\fi-360\\li720\\sa200\\sl276\\slmult1 Laird Connectivity with the Virtual Serial Port (VSP)\\par \ 31 | {\\pntext\\f2\\'B7\\tab}u-blox with the \\tab u-connectXpress BLE Serial Port Service\\par \ 32 | {\\pntext\\f2\\'B7\\tab}Nordic Semiconductors with the Nordic UART Service (NUS)\\par \ 33 | \ 34 | \\pard\\sa200\\sl276\\slmult1\\b\\fs24 Length of RX Characteristic\\par \ 35 | \\b0\\fs22 The length of the peripheral RX characteristic is not exactly known to the central, hence BLE-Scout assumes MTU size minus 3 as best guess. It can be adjusted manually to another value. If the entered data is longer than the characteristic length, it will automatically split into multiple write packets.\\par \ 36 | \\b\\fs24 WriteCommand/WriteRequest\\par \ 37 | \\b0\\fs22 Depending on the write properties of the RX characteristic you can select to use write request or write command, if both are exposed.\\par \ 38 | \\b\\fs24 Pairing Devices\\par \ 39 | \\b0\\fs22 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.\\par \ 40 | This has the following implications:\\par \ 41 | \ 42 | \\pard{\\pntext\\f2\\'B7\\tab}{\\*\\pn\\pnlvlblt\\pnf2\\pnindent0{\\pntxtb\\'B7}}\\fi-360\\li720\\sa200\\sl276\\slmult1 The user will need to manually confirm pairing, until that happens no successful access to protected characteristics is possible.\\par \ 43 | {\\pntext\\f2\\'B7\\tab}Removing a device can only be done from the OS Bluetooth settings page.\\par \ 44 | {\\pntext\\f2\\'B7\\tab}There is no programmatic way of controlling this process.\\par \ 45 | \ 46 | \\pard\\sa200\\sl276\\slmult1 This applies at least for Windows OS and might be different on other OS platforms (not yet available).\\par \ 47 | \\b\\fs28 Limitations\\b0\\fs22\\par \ 48 | \ 49 | \\pard{\\pntext\\f2\\'B7\\tab}{\\*\\pn\\pnlvlblt\\pnf2\\pnindent0{\\pntxtb\\'B7}}\\fi-360\\li720\\sa200\\sl276\\slmult1 Since BLE-Scout leverages the PC\\rquote s integrated Bluetooth Controller, this ultimately limits what\\rquote s achievable from a BT hardware perspective (e.g., number of simultaneous connections).\\par \ 50 | {\\pntext\\f2\\'B7\\tab}It is not possible to tweak BLE settings like scan interval and/or scan window or similar low-level BLE parameters.\\par \ 51 | {\\pntext\\f2\\'B7\\tab}Pairing of BLE devices is not yet implmented.\\par \ 52 | {\\pntext\\f2\\'B7\\tab}Due to a limitation of the underlying BLE library, it\\rquote s not possible to properly receive notifications or indications from devices exposing an identcal GATT table (i.e. identical service/characteristic UUIDs).\\par \ 53 | {\\pntext\\f2\\'B7\\tab}The underlying BLE library does not yet expose properties of the descriptors and hence it\\rquote s not yet possible to directly read/write them.\\par \ 54 | \ 55 | \\pard\\sa200\\sl276\\slmult1\\b\\fs28 Libraries\\par \ 56 | \\fs24 NAppGUI\\par \ 57 | \\b0\\fs22 BLE-Scout uses NAppGUI ({{\\field{\\*\\fldinst{HYPERLINK https://nappgui.com/en/home/web/home.html }}{\\fldrslt{https://nappgui.com/en/home/web/home.html\\ul0\\cf0}}}}\\f0\\fs22 ) as GUI toolkit and cross-platform SDK.\\par \ 58 | \\b\\fs24 SimpleBLE\\par \ 59 | \\b0\\fs22 BLE-Scout BLE Tool uses the SimpleBLE library from {{\\field{\\*\\fldinst{HYPERLINK https://github.com/OpenBluetoothToolbox/SimpleBLE }}{\\fldrslt{https://github.com/OpenBluetoothToolbox/SimpleBLE\\ul0\\cf0}}}}\\f0\\fs22 .\\par \ 60 | \\b\\fs28 License\\par \ 61 | \\b0\\fs22 BLE-Scout BLE Tool is released under the MIT License.\\par \ 62 | Copyright (C) 2024 Erik Lins\\par \ 63 | \\i Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\par \ 64 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\par \ 65 | THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\par \ 66 | } \ 67 | \ 68 | "}; 69 | 70 | -------------------------------------------------------------------------------- /src/logwin.c: -------------------------------------------------------------------------------- 1 | /* Log window */ 2 | 3 | #include 4 | #include "ble.h" 5 | #include "logwin.h" 6 | 7 | #define LOG_STREAM_SIZE 1024 8 | 9 | const color_t colors[] = { 10 | 0xFF503E2C, // 0x2C3E50FF, dark grey 11 | 0xFF2B39C0, // 0xC0392BFF, dark red 12 | 0xFFA67428, // 0x2874A6FF, blue 13 | 0xFF1E6FCA, // 0xCA6F1EFF, orange 14 | 0xFF49841E, // 0x1E8449FF, green 15 | 0xFF0A7D9A, // 0x9A7D0AFF, brown 16 | 0xFFA04E88, // 0x884EA0FF, light violet 17 | 0xFF83346C // 0x6C3483FF, dark violet 18 | }; 19 | 20 | 21 | struct _logwin_t 22 | { 23 | Window* window; 24 | TextView* text_log; 25 | 26 | V2Df origin; 27 | bool_t add_timestamp; 28 | 29 | bool_t is_open; 30 | 31 | Stream* stream; 32 | Mutex* mutex; 33 | }; 34 | 35 | static LogWin* i_logwin = NULL; 36 | 37 | Stream* log_stream = NULL; 38 | Mutex* log_mutex = NULL; 39 | 40 | /*---------------------------------------------------------------------------*/ 41 | 42 | void logwin_update(void) 43 | { 44 | char_t* line = NULL; 45 | 46 | do 47 | { 48 | bmutex_lock(i_logwin->mutex); 49 | line = stm_read_line(i_logwin->stream); 50 | bmutex_unlock(i_logwin->mutex); 51 | 52 | if (line != NULL) 53 | { 54 | if (i_logwin->add_timestamp) 55 | { 56 | Date date; 57 | 58 | btime_to_date(btime_now(), &date); 59 | textview_color(i_logwin->text_log, color_gray(128)); 60 | textview_printf(i_logwin->text_log, "[%04d-%02d-%02d_%02d:%02d:%02d] ", 61 | date.year, date.month, date.mday, date.hour, date.minute, date.second); 62 | } 63 | 64 | uint32_t i, j = 0; 65 | uint32_t len = str_len_c(line); 66 | for (i = 0; i < len; ++i) 67 | { 68 | if (line[j] == '\a') 69 | { 70 | line[j] = '\0'; 71 | textview_writef(i_logwin->text_log, line); 72 | j++; 73 | textview_color(i_logwin->text_log, colors[line[j] - '0']); 74 | j++; 75 | line += j; 76 | j = 0; 77 | } 78 | else if (i == len - 1) 79 | { 80 | textview_writef(i_logwin->text_log, line); 81 | } 82 | else 83 | j++; 84 | } 85 | 86 | textview_color(i_logwin->text_log, colors[0]); 87 | textview_writef(i_logwin->text_log, "\n"); 88 | 89 | //textview_select(i_logwin->text_log, -1, -1); 90 | textview_scroll_caret(i_logwin->text_log); 91 | } 92 | 93 | } while (line != NULL); 94 | } 95 | 96 | /*---------------------------------------------------------------------------*/ 97 | 98 | static void i_OnSetTimestamp(LogWin* log_win, Event* e) 99 | { 100 | Button* button = event_sender(e, Button); 101 | 102 | if (button_get_state(button) == ekGUI_ON) 103 | log_win->add_timestamp = TRUE; 104 | else 105 | log_win->add_timestamp = FALSE; 106 | 107 | unref(log_win); 108 | unref(e); 109 | } 110 | 111 | static void i_OnClearLog(LogWin* log_win, Event* e) 112 | { 113 | textview_clear(log_win->text_log); 114 | 115 | unref(log_win); 116 | unref(e); 117 | } 118 | 119 | static void i_OnCopyLog(LogWin* log_win, Event* e) 120 | { 121 | textview_select(log_win->text_log, 0, INT32_MAX); 122 | textview_copy(log_win->text_log); 123 | textview_select(log_win->text_log, 0, 0); 124 | 125 | unref(log_win); 126 | unref(e); 127 | } 128 | 129 | static void i_OnSaveLog(LogWin* log_win, Event* e) 130 | { 131 | Date date; 132 | 133 | btime_to_date(btime_now(), &date); 134 | 135 | String* msg = str_printf("%04d%02d%02d-%02d%02d%02d_nappble.log", 136 | date.year, date.month, date.mday, date.hour, date.minute, date.second); 137 | 138 | File* file = bfile_create(tc(msg), NULL); 139 | if (file != NULL) 140 | { 141 | const char_t* text = textview_get_text(log_win->text_log); 142 | bfile_write(file, (byte_t*)text, str_len_c(text), NULL, NULL); 143 | bfile_close(&file); 144 | } 145 | else 146 | stm_printf(log_stream, "> Cannot open log file.\n"); 147 | 148 | str_destroy(&msg); 149 | 150 | unref(log_win); 151 | unref(e); 152 | } 153 | 154 | static void i_OnClose(LogWin* log, Event* e) 155 | { 156 | window_hide(log->window); 157 | 158 | log->is_open = FALSE; 159 | 160 | unref(e); 161 | } 162 | 163 | /*---------------------------------------------------------------------------*/ 164 | 165 | static Panel* i_panel(LogWin* log_win) 166 | { 167 | Panel* panel = panel_create(); 168 | 169 | Layout* layout = layout_create(1, 2); 170 | Layout* layout_buttons = layout_create(7, 1); 171 | 172 | Button* button_timestamp = button_check(); 173 | button_text(button_timestamp, "Timestamps"); 174 | button_OnClick(button_timestamp, listener(log_win, i_OnSetTimestamp, LogWin)); 175 | 176 | Button* button_clear_log = button_push(); 177 | button_text(button_clear_log, "Clear Log"); 178 | button_OnClick(button_clear_log, listener(log_win, i_OnClearLog, LogWin)); 179 | 180 | Button* button_copy_log = button_push(); 181 | button_text(button_copy_log, "Copy Log"); 182 | button_OnClick(button_copy_log, listener(log_win, i_OnCopyLog, LogWin)); 183 | 184 | Button* button_save_log = button_push(); 185 | button_text(button_save_log, "Save Log"); 186 | button_OnClick(button_save_log, listener(log_win, i_OnSaveLog, LogWin)); 187 | 188 | layout_button(layout_buttons, button_timestamp, 0, 0); 189 | layout_button(layout_buttons, button_clear_log, 2, 0); 190 | layout_button(layout_buttons, button_copy_log, 4, 0); 191 | layout_button(layout_buttons, button_save_log, 6, 0); 192 | layout_margin4(layout_buttons, 0, 0, 10, 0); 193 | layout_layout(layout, layout_buttons, 0, 0); 194 | 195 | log_win->text_log = textview_create(); 196 | textview_family(log_win->text_log, "Source Code Pro"); 197 | textview_fsize(log_win->text_log, 11); 198 | layout_textview(layout, log_win->text_log, 0, 1); 199 | textview_size(log_win->text_log, s2df(1200, 400)); 200 | 201 | layout_hsize(layout, 0, 680); 202 | layout_hsize(layout, 1, 680); 203 | layout_vsize(layout, 1, 400); 204 | layout_margin(layout, 5); 205 | 206 | layout_vexpand(layout, 1); 207 | 208 | panel_layout(panel, layout); 209 | 210 | return panel; 211 | } 212 | 213 | /*---------------------------------------------------------------------------*/ 214 | 215 | LogWin* logwin_create(void) 216 | { 217 | LogWin* log = heap_new0(LogWin); 218 | 219 | i_logwin = log; 220 | 221 | log->stream = stm_memory(LOG_STREAM_SIZE); 222 | log_stream = log->stream; 223 | 224 | log->mutex = bmutex_create(); 225 | log_mutex = log->mutex; 226 | 227 | Panel* panel = i_panel(log); 228 | 229 | log->window = window_create(ekWINDOW_STD | ekWINDOW_RESIZE); 230 | window_panel(log->window, panel); 231 | window_title(log->window, "BLE Scout - Log Output"); 232 | window_OnClose(log->window, listener(log, i_OnClose, LogWin)); 233 | 234 | log->add_timestamp = FALSE; 235 | log->is_open = FALSE; 236 | 237 | textview_color(i_logwin->text_log, colors[0]); 238 | 239 | return log; 240 | } 241 | 242 | void logwin_destroy(LogWin** log_win) 243 | { 244 | bmutex_close(&(*log_win)->mutex); 245 | stm_close(&(*log_win)->stream); 246 | window_destroy(&(*log_win)->window); 247 | heap_delete(log_win, LogWin); 248 | } 249 | 250 | /*---------------------------------------------------------------------------*/ 251 | 252 | void logwin_show(LogWin* log_win, const V2Df pos) 253 | { 254 | window_origin(log_win->window, pos); 255 | window_show(log_win->window); 256 | 257 | log_win->is_open = TRUE; 258 | } 259 | 260 | void logwin_hide(LogWin* log_win) 261 | { 262 | window_hide(log_win->window); 263 | 264 | log_win->is_open = FALSE; 265 | } 266 | 267 | bool_t logwin_is_open(void) 268 | { 269 | return i_logwin->is_open; 270 | } 271 | -------------------------------------------------------------------------------- /src/logwin.h: -------------------------------------------------------------------------------- 1 | /* Log into separate window */ 2 | 3 | #include "logwin.hxx" 4 | 5 | #define log_printf(...) bmutex_lock(log_mutex); stm_printf(log_stream, __VA_ARGS__); bmutex_unlock(log_mutex) 6 | 7 | LogWin* logwin_create(void); 8 | 9 | void logwin_show(LogWin* log, const V2Df pos); 10 | 11 | void logwin_hide(LogWin*); 12 | 13 | bool_t logwin_is_open(void); 14 | 15 | void logwin_destroy(LogWin**); 16 | 17 | void logwin_update(void); 18 | -------------------------------------------------------------------------------- /src/logwin.hxx: -------------------------------------------------------------------------------- 1 | /* Log into separate window */ 2 | 3 | typedef struct _logwin_t LogWin; 4 | 5 | extern Stream* log_stream; 6 | extern Mutex* log_mutex; 7 | -------------------------------------------------------------------------------- /src/scan.h: -------------------------------------------------------------------------------- 1 | /* BLE scanning */ 2 | 3 | uint32_t scan_create(void); 4 | 5 | void scan_destroy(void); 6 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* Utilities */ 2 | 3 | #include 4 | #include "util.h" 5 | #include "ble.h" 6 | #include "bt_assigned_numbers.h" 7 | #include "vsp_services.h" 8 | #include "logwin.h" 9 | 10 | struct _util_t 11 | { 12 | Window* win_modal; 13 | }; 14 | 15 | static Util* i_util = NULL; 16 | 17 | /*---------------------------------------------------------------------------*/ 18 | 19 | String* util_vsp_service_uuid_to_name(const char_t* uuid) 20 | { 21 | uint32_t i; 22 | 23 | for (i = 0; i < vsp_service_uuids_len; ++i) 24 | { 25 | if (str_cmp_c(uuid, vsp_service_uuids[i].uuid) == 0) 26 | return str_c(vsp_service_uuids[i].name); 27 | } 28 | return str_c(""); 29 | } 30 | 31 | String* util_vsp_characteristic_uuid_to_name(const char_t* uuid) 32 | { 33 | uint32_t i; 34 | 35 | for (i = 0; i < vsp_service_uuids_len; ++i) 36 | { 37 | if (str_cmp_c(uuid, vsp_service_uuids[i].Rx.uuid) == 0) 38 | return str_c(vsp_service_uuids[i].Rx.name); 39 | if (str_cmp_c(uuid, vsp_service_uuids[i].Tx.uuid) == 0) 40 | return str_c(vsp_service_uuids[i].Tx.name); 41 | if (str_cmp_c(uuid, vsp_service_uuids[i].ModemIn.uuid) == 0) 42 | return str_c(vsp_service_uuids[i].ModemIn.name); 43 | if (str_cmp_c(uuid, vsp_service_uuids[i].ModemOut.uuid) == 0) 44 | return str_c(vsp_service_uuids[i].ModemOut.name); 45 | } 46 | return str_c(""); 47 | } 48 | 49 | String* util_service_uuid_to_name(const char_t* uuid) 50 | { 51 | int i; 52 | 53 | for (i = 0; i < service_uuids_len; ++i) 54 | { 55 | if (str_cmp_c(uuid, service_uuids[i].uuid) == 0) 56 | return str_c(service_uuids[i].name); 57 | } 58 | return str_c(""); 59 | } 60 | 61 | String* util_characteristic_uuid_to_name(const char_t* uuid) 62 | { 63 | int i; 64 | 65 | for (i = 0; i < characteristic_uuids_len; ++i) 66 | { 67 | if (str_cmp_c(uuid, characteristic_uuids[i].uuid) == 0) 68 | return str_c(characteristic_uuids[i].name); 69 | } 70 | return str_c(""); 71 | } 72 | 73 | String* util_descriptor_uuid_to_name(const char_t* uuid) 74 | { 75 | int i; 76 | 77 | for (i = 0; i < descriptor_uuids_len; ++i) 78 | { 79 | if (str_cmp_c(uuid, descriptor_uuids[i].uuid) == 0) 80 | return str_c(descriptor_uuids[i].name); 81 | } 82 | return str_c(""); 83 | } 84 | 85 | String* util_company_id_to_name(const uint16_t id) 86 | { 87 | int i; 88 | 89 | for (i = 0; i < company_identifier_len; ++i) 90 | { 91 | if (id == company_identifier[i].code) 92 | return str_c(company_identifier[i].name); 93 | } 94 | 95 | return str_c(""); 96 | } 97 | 98 | String* util_gap_appearance_to_name(const uint16_t catval) 99 | { 100 | return str_c(""); 101 | 102 | unref(&catval); 103 | } 104 | 105 | /*---------------------------------------------------------------------------*/ 106 | 107 | void util_data_to_hex(const byte_t* in, char* out, const size_t len) 108 | { 109 | size_t i; 110 | 111 | for (i = 0; i < len; i++) 112 | { 113 | if ((in[i] >> 4) <= 9) 114 | out[2 * i] = '0' + (in[i] >> 4); 115 | else 116 | out[2 * i] = 'A' + ((in[i] >> 4) - 10); 117 | 118 | if ((in[i] & 0x0f) <= 9) 119 | out[2 * i + 1] = '0' + (in[i] & 0x0f); 120 | else 121 | out[2 * i + 1] = 'A' + ((in[i] & 0x0f) - 10); 122 | } 123 | 124 | out[2*i] = '\0'; 125 | } 126 | 127 | void util_hex_to_data(const char* in, byte_t* out, const size_t len) 128 | { 129 | size_t i; 130 | 131 | for (i = 0; i < (len/2); i++) 132 | { 133 | out[i] = 0; 134 | 135 | if ((in[2*i] >= '0') && (in[2*i] <= '9')) 136 | out[i] = (in[2*i] - '0') << 4; 137 | else if ((in[2 * i] >= 'A') && (in[2 * i] <= 'F')) 138 | out[i] = (in[2 * i] - 'A' + 10) << 4; 139 | else if ((in[2 * i] >= 'a') && (in[2 * i] <= 'f')) 140 | out[i] = (in[2 * i] - 'a' + 10) << 4; 141 | 142 | if ((in[2*i+1] >= '0') && (in[2*i+1] <= '9')) 143 | out[i] |= (in[2*i+1] - '0'); 144 | else if ((in[2 * i + 1] >= 'A') && (in[2 * i + 1] <= 'F')) 145 | out[i] |= (in[2 * i + 1] - 'A' + 10); 146 | else if ((in[2 * i + 1] >= 'a') && (in[2 * i + 1] <= 'f')) 147 | out[i] |= (in[2 * i + 1] - 'a' + 10); 148 | } 149 | } 150 | 151 | void util_data_to_ascii(const byte_t* in, char* out, const size_t len) 152 | { 153 | size_t i; 154 | 155 | for (i = 0; i < len; ++i) 156 | { 157 | if ((in[i] >= 32) && (in[i] <= 127)) 158 | { 159 | out[i] = in[i]; 160 | } 161 | else 162 | out[i] = '#'; 163 | } 164 | out[i] = '\0'; 165 | } 166 | 167 | /*---------------------------------------------------------------------------*/ 168 | 169 | bool_t util_buffer_is_hex(const char* in, const size_t len) 170 | { 171 | size_t i; 172 | 173 | if ((len % 2) != 0) 174 | return FALSE; 175 | 176 | for (i = 0; i < len; ++i) 177 | { 178 | if ((in[i] < '0') || (in[i] > 'f') || ((in[i] > '9') && (in[i] < 'A')) || ((in[i] > 'F') && (in[i] < 'a'))) 179 | return FALSE; 180 | } 181 | return TRUE; 182 | } 183 | 184 | bool_t util_buffer_is_mac_address(const char* in, const size_t len) 185 | { 186 | size_t i; 187 | 188 | for (i = 0; i < len; ++i) 189 | { 190 | if ((in[i] < '0') || (in[i] > 'f') || ((in[i] > ':') && (in[i] < 'A')) || ((in[i] > 'F') && (in[i] < 'a'))) 191 | return FALSE; 192 | } 193 | return TRUE; 194 | } 195 | 196 | /*---------------------------------------------------------------------------*/ 197 | 198 | static void i_OnCloseModal(Window* window, Event* e) 199 | { 200 | Button* button = event_sender(e, Button); 201 | window_stop_modal(window, button_get_tag(button)); 202 | } 203 | 204 | static void i_OnClickModal(Util* util, Event* e) 205 | { 206 | window_stop_modal(util->win_modal, 0); 207 | 208 | unref(e); 209 | } 210 | 211 | void util_message_window(Window* window, const char* message) 212 | { 213 | i_util->win_modal = window_create(ekWINDOW_TITLE | ekWINDOW_CLOSE); 214 | window_title(window, "Alert"); 215 | 216 | Panel* panel = panel_create(); 217 | Layout* layout = layout_create(1, 2); 218 | 219 | Label* label = label_create(); 220 | label_text(label, message); 221 | layout_label(layout, label, 0, 0); 222 | 223 | Button* button = button_push(); 224 | button_text(button, "OK"); 225 | layout_button(layout, button, 0, 1); 226 | 227 | panel_layout(panel, layout); 228 | 229 | window_panel(i_util->win_modal, panel); 230 | 231 | V2Df pos = window_get_origin(window); 232 | window_origin(i_util->win_modal, v2df(pos.x + 20, pos.y + 20)); 233 | 234 | window_modal(i_util->win_modal, window); 235 | } 236 | 237 | /*---------------------------------------------------------------------------*/ 238 | 239 | Util* util_create(void) 240 | { 241 | Util* util = heap_new0(Util); 242 | 243 | i_util = util; 244 | 245 | return util; 246 | } 247 | 248 | void util_destroy(Util** util) 249 | { 250 | heap_delete(util, Util); 251 | } 252 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* Utilities */ 2 | 3 | #include "util.hxx" 4 | 5 | 6 | String* util_vsp_service_uuid_to_name(const char_t* uuid); 7 | 8 | String* util_vsp_characteristic_uuid_to_name(const char_t* uuid); 9 | 10 | String* util_service_uuid_to_name(const char_t* uuid); 11 | 12 | String* util_characteristic_uuid_to_name(const char_t* uuid); 13 | 14 | String* util_descriptor_uuid_to_name(const char_t* uuid); 15 | 16 | String* util_company_id_to_name(const uint16_t id); 17 | 18 | String* util_gap_appearance_to_name(const uint16_t catval); 19 | 20 | void util_data_to_hex(const byte_t* in, char* out, const size_t len); 21 | 22 | void util_hex_to_data(const char* in, byte_t* out, const size_t len); 23 | 24 | void util_data_to_ascii(const byte_t* in, char* out, const size_t len); 25 | 26 | bool_t util_buffer_is_hex(const char* in, const size_t len); 27 | 28 | bool_t util_buffer_is_mac_address(const char* in, const size_t len); 29 | 30 | void util_message_window(Window* window, const char* message); 31 | 32 | Util* util_create(void); 33 | 34 | void util_destroy(Util** u); 35 | -------------------------------------------------------------------------------- /src/util.hxx: -------------------------------------------------------------------------------- 1 | /* Utilities */ 2 | 3 | typedef struct _util_t Util; 4 | -------------------------------------------------------------------------------- /src/vsp_services.h: -------------------------------------------------------------------------------- 1 | typedef const struct _vsp_characteristic_t VspCharacteristic; 2 | const struct _vsp_characteristic_t 3 | { 4 | const char* uuid; 5 | const char* name; 6 | }; 7 | 8 | typedef const struct _vsp_service_t VspService; 9 | const struct _vsp_service_t 10 | { 11 | const char* uuid; 12 | const char* name; 13 | VspCharacteristic Rx; 14 | VspCharacteristic Tx; 15 | VspCharacteristic ModemIn; 16 | VspCharacteristic ModemOut; 17 | }; 18 | 19 | const uint32_t vsp_service_uuids_len = 3; 20 | VspService vsp_service_uuids[3] = { 21 | {"569a1101-b87f-490c-92cb-11ba5ea5167c", "Laird Connectivity VSP Service", 22 | {"569a2001-b87f-490c-92cb-11ba5ea5167c", "Laird Vsp Rx"}, 23 | {"569a2000-b87f-490c-92cb-11ba5ea5167c", "Laird Vsp Tx"}, 24 | {"569a2003-b87f-490c-92cb-11ba5ea5167c", "Laird Vsp ModemIn"}, 25 | {"569a2002-b87f-490c-92cb-11ba5ea5167c", "Laird Vsp ModemOut"} 26 | }, 27 | {"6e400001-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART Service (NUS)", 28 | {"6e400002-b5a3-f393-e0a9-e50e24dcca9e", "NUS Rx"}, 29 | {"6e400003-b5a3-f393-e0a9-e50e24dcca9e", "NUS Tx"}, 30 | {"", ""}, 31 | {"", ""} 32 | }, 33 | {"2456e1b9-26e2-8f83-e744-f34f01e9d701", "u-connectXpress BLE Serial Port Service", 34 | {"2456e1b9-26e2-8f83-e744-f34f01e9d703", "Serial Port FIFO / Rx"}, 35 | {"2456e1b9-26e2-8f83-e744-f34f01e9d704", "Serial Port Credit / Tx"}, 36 | {"", ""}, 37 | {"", ""} 38 | } 39 | }; 40 | --------------------------------------------------------------------------------