Initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# build output
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# runtime database
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# macos
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(Bluehound)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# SimpleBLE local build
|
||||||
|
set(SIMPLEBLE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/simpleble/simpleble")
|
||||||
|
set(SIMPLEBLE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/simpleble")
|
||||||
|
add_library(simpleble STATIC IMPORTED)
|
||||||
|
set_target_properties(simpleble PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${SIMPLEBLE_DIR}/build/lib/libsimpleble.a"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SIMPLEBLE_DIR}/include;${SIMPLEBLE_DIR}/build/export;${SIMPLEBLE_ROOT}/dependencies/external"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find other packages
|
||||||
|
find_package(SQLite3 REQUIRED)
|
||||||
|
|
||||||
|
# cpp-httplib v0.14.3 vendored under third_party for offline builds
|
||||||
|
set(HTTPLIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party")
|
||||||
|
|
||||||
|
# Executable
|
||||||
|
add_executable(bluehound
|
||||||
|
bluehound.cpp
|
||||||
|
classic_bluetooth.mm
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable Objective-C++ for .mm files
|
||||||
|
set_source_files_properties(classic_bluetooth.mm PROPERTIES
|
||||||
|
COMPILE_FLAGS "-x objective-c++ -fobjc-arc"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(bluehound
|
||||||
|
simpleble
|
||||||
|
SQLite::SQLite3
|
||||||
|
pthread
|
||||||
|
"-framework Foundation"
|
||||||
|
"-framework CoreBluetooth"
|
||||||
|
"-framework IOKit"
|
||||||
|
"-framework IOBluetooth"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(bluehound PRIVATE
|
||||||
|
${HTTPLIB_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
install(TARGETS bluehound DESTINATION bin)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Bluehound
|
||||||
|
|
||||||
|
Continuous BLE and Classic Bluetooth scanner with time-searchable logging, a REST API, and a web dashboard. Built for macOS and embedded/sensor-node deployments with WiFi streaming support.
|
||||||
|
|
||||||
|
Bluehound scans nearby Bluetooth devices in a loop, enriches each observation (manufacturer, service UUIDs, device class, estimated distance, Apple Continuity data), and persists everything to SQLite. A built-in HTTP server exposes the data for the bundled dashboard or any client.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Dual-stack scanning** — BLE via [SimpleBLE](https://github.com/OpenBluetoothToolbox/SimpleBLE), plus Classic Bluetooth inquiry and SDP service discovery on macOS via IOBluetooth.
|
||||||
|
- **Time-searchable history** — every observation is stamped with date, time, hour, and minute and indexed for fast range queries.
|
||||||
|
- **Device enrichment** — company/manufacturer lookup, standard service and characteristic UUID decoding, device-class decoding, and GATT characteristic enumeration on connect.
|
||||||
|
- **Distance estimation** — RSSI + Tx power path-loss model, with per-device distance history.
|
||||||
|
- **Apple Continuity decoding** — parses proximity/pairing advertisements.
|
||||||
|
- **Passive security assessment** — flags risky services (OBEX, SPP, HID, audio) and known-vulnerable device signatures, assigning a HIGH/MEDIUM/LOW/NONE risk level.
|
||||||
|
- **Device nicknames** — assign and manage human-readable labels per address.
|
||||||
|
- **ESP32 ingestion** — remote sensor nodes can POST scan batches over WiFi to `/esp32/scan`.
|
||||||
|
- **REST API + web dashboard** — a single-file HTML dashboard served over HTTP.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- macOS (Classic Bluetooth support uses IOBluetooth/CoreBluetooth frameworks)
|
||||||
|
- CMake >= 3.15
|
||||||
|
- SQLite3
|
||||||
|
- Vendored dependencies (included in the repo):
|
||||||
|
- SimpleBLE static library at `simpleble/simpleble/build/lib/libsimpleble.a`
|
||||||
|
- cpp-httplib header at `third_party/httplib.h`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This verifies the vendored dependencies, configures with CMake into `build/`, and compiles. The resulting binary is `build/bluehound`.
|
||||||
|
|
||||||
|
To build manually:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake ..
|
||||||
|
make -j
|
||||||
|
```
|
||||||
|
|
||||||
|
Install system-wide with `sudo make install` from the `build/` directory.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build/bluehound
|
||||||
|
```
|
||||||
|
|
||||||
|
On start, Bluehound initializes `ble_scans.db`, launches the HTTP server on port `8080` (bound to `0.0.0.0`), and begins scanning. Shut down cleanly with `Ctrl-C` (SIGINT/SIGTERM are handled gracefully).
|
||||||
|
|
||||||
|
Default configuration (compiled into `Config` in [bluehound.cpp](bluehound.cpp)):
|
||||||
|
|
||||||
|
| Setting | Default |
|
||||||
|
| --- | --- |
|
||||||
|
| Scan duration | 5000 ms per cycle |
|
||||||
|
| Scan interval | 1000 ms between cycles |
|
||||||
|
| Database path | `ble_scans.db` |
|
||||||
|
| HTTP port | 8080 |
|
||||||
|
| Max DB entries | 1,000,000 (rotates) |
|
||||||
|
|
||||||
|
## Web dashboard
|
||||||
|
|
||||||
|
Serve the dashboard with the helper script (static server on port 8090):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 serve_dashboard.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://127.0.0.1:8090. The dashboard queries the scanner's API on port 8080, so run the `bluehound` binary alongside it.
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
The scanner serves the following endpoints on port 8080:
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| GET | `/health` | Health check |
|
||||||
|
| GET | `/scans/recent?limit=N` | Most recent observations |
|
||||||
|
| GET | `/scans/date/:date` | Observations for `YYYY-MM-DD` |
|
||||||
|
| GET | `/scans/hour/:hour` | Observations for hour `HH` |
|
||||||
|
| GET | `/stats` | Aggregate statistics |
|
||||||
|
| GET | `/device/:address/count` | Observation count for a device |
|
||||||
|
| GET | `/distance/:address` | Distance history for a device |
|
||||||
|
| GET | `/characteristics/:address` | Discovered GATT characteristics |
|
||||||
|
| GET | `/read/:address/:service/:characteristic` | Read a characteristic value |
|
||||||
|
| GET | `/nickname/:address` | Get a device nickname |
|
||||||
|
| POST | `/nickname/:address` | Set a device nickname |
|
||||||
|
| DELETE | `/nickname/:address` | Remove a device nickname |
|
||||||
|
| GET | `/nicknames` | List all nicknames |
|
||||||
|
| GET | `/security/:address` | Security assessment for a device |
|
||||||
|
| GET | `/security` | All security assessments |
|
||||||
|
| GET | `/tracking/:address` | Tracking data (device info, Apple Continuity) |
|
||||||
|
| POST | `/esp32/scan` | Ingest a scan batch from a remote ESP32 node |
|
||||||
|
|
||||||
|
CORS is enabled for all origins.
|
||||||
|
|
||||||
|
## Data model
|
||||||
|
|
||||||
|
Observations are stored in `ble_scans.db` (SQLite). Key tables:
|
||||||
|
|
||||||
|
- `ble_scans` — every observation, with timestamps, RSSI, manufacturer/company, service UUIDs, estimated distance, protocol type, and Classic device-class/SDP fields.
|
||||||
|
- `device_distance_history` — per-device RSSI/distance samples over time.
|
||||||
|
- `device_characteristics` — discovered GATT services and characteristics.
|
||||||
|
- `device_nicknames` — user-assigned labels.
|
||||||
|
- `security_assessments` — risk level, vulnerabilities, and recommendations per device.
|
||||||
|
- `device_tracking_data` — device model/firmware/serial, Apple Continuity, pairing status, and tracking identifiers.
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [bluehound.cpp](bluehound.cpp) | Scanner core: scan loop, SQLite logging, HTTP API, BLE decoding, security assessment |
|
||||||
|
| [classic_bluetooth.h](classic_bluetooth.h) / [classic_bluetooth.mm](classic_bluetooth.mm) | Classic Bluetooth inquiry and SDP via IOBluetooth (Objective-C++) |
|
||||||
|
| [web_dashboard.html](web_dashboard.html) | Single-file web dashboard |
|
||||||
|
| [serve_dashboard.py](serve_dashboard.py) | Static server for the dashboard |
|
||||||
|
| [CMakeLists.txt](CMakeLists.txt) / [build.sh](build.sh) | Build configuration |
|
||||||
|
| `simpleble/` | Vendored SimpleBLE |
|
||||||
|
| `third_party/httplib.h` | Vendored cpp-httplib |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Classic Bluetooth scanning and the IOBluetooth-backed features are macOS-only. BLE scanning via SimpleBLE is portable, but the build as configured links macOS frameworks.
|
||||||
|
- The ESP32 endpoint uses lightweight string-based JSON parsing; use a proper JSON library for production ingestion.
|
||||||
|
- Security assessments are heuristic and intended for authorized inventory and awareness, not as a substitute for a full audit.
|
||||||
+2497
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==================================="
|
||||||
|
echo "Bluehound Build Script"
|
||||||
|
echo "==================================="
|
||||||
|
echo
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# vendored simpleble static lib
|
||||||
|
if [ ! -f "${SCRIPT_DIR}/simpleble/simpleble/build/lib/libsimpleble.a" ]; then
|
||||||
|
echo "❌ vendored SimpleBLE not found at simpleble/simpleble/build/lib/libsimpleble.a"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# vendored cpp-httplib header
|
||||||
|
if [ ! -f "${SCRIPT_DIR}/third_party/httplib.h" ]; then
|
||||||
|
echo "❌ vendored httplib.h not found at third_party/httplib.h"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cmake toolchain
|
||||||
|
if ! command -v cmake >/dev/null 2>&1; then
|
||||||
|
echo "❌ cmake not found (need >= 3.15)"
|
||||||
|
echo " macOS: brew install cmake"
|
||||||
|
echo " Ubuntu/Debian: sudo apt-get install cmake"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Dependencies found"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Create build directory
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# Configure
|
||||||
|
echo "Configuring..."
|
||||||
|
cmake ..
|
||||||
|
|
||||||
|
# Build
|
||||||
|
echo "Building..."
|
||||||
|
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==================================="
|
||||||
|
echo "✓ Build complete!"
|
||||||
|
echo "==================================="
|
||||||
|
echo
|
||||||
|
echo "Run the scanner:"
|
||||||
|
echo " ./build/bluehound"
|
||||||
|
echo
|
||||||
|
echo "Install system-wide:"
|
||||||
|
echo " sudo make install"
|
||||||
|
echo
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#ifndef CLASSIC_BLUETOOTH_H
|
||||||
|
#define CLASSIC_BLUETOOTH_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// C++ interface for Classic Bluetooth scanning (wraps IOBluetooth Objective-C API)
|
||||||
|
|
||||||
|
namespace ClassicBluetooth {
|
||||||
|
|
||||||
|
// Device class information
|
||||||
|
struct DeviceClass {
|
||||||
|
std::string major_class; // Computer, Phone, Audio, Peripheral, etc.
|
||||||
|
std::string minor_class; // Smartphone, Laptop, Headset, etc.
|
||||||
|
uint32_t class_of_device; // Raw CoD value
|
||||||
|
};
|
||||||
|
|
||||||
|
// SDP service record
|
||||||
|
struct SDPService {
|
||||||
|
std::string uuid; // Service UUID
|
||||||
|
std::string name; // Human-readable name (OBEX, SPP, HID, etc.)
|
||||||
|
uint8_t rfcomm_channel; // RFCOMM channel ID (if applicable)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Classic Bluetooth device info
|
||||||
|
struct ClassicDevice {
|
||||||
|
std::string address; // MAC address (XX:XX:XX:XX:XX:XX)
|
||||||
|
std::string name; // Device name
|
||||||
|
int16_t rssi; // Signal strength
|
||||||
|
DeviceClass device_class; // Device class info
|
||||||
|
std::vector<SDPService> services; // SDP services
|
||||||
|
bool is_connected; // Connection status
|
||||||
|
bool is_paired; // Pairing status
|
||||||
|
std::string last_seen; // Timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback for device discovery
|
||||||
|
using DeviceFoundCallback = std::function<void(const ClassicDevice&)>;
|
||||||
|
using InquiryCompleteCallback = std::function<void(bool success, const std::string& error)>;
|
||||||
|
|
||||||
|
// Scanner interface
|
||||||
|
class Scanner {
|
||||||
|
public:
|
||||||
|
Scanner();
|
||||||
|
~Scanner();
|
||||||
|
|
||||||
|
// Start Classic Bluetooth inquiry
|
||||||
|
bool start_inquiry(int duration_seconds = 10);
|
||||||
|
|
||||||
|
// Stop inquiry
|
||||||
|
void stop_inquiry();
|
||||||
|
|
||||||
|
// Check if inquiry is running
|
||||||
|
bool is_scanning() const;
|
||||||
|
|
||||||
|
// Set callback for when devices are found
|
||||||
|
void set_device_found_callback(DeviceFoundCallback callback);
|
||||||
|
|
||||||
|
// Set callback for when inquiry completes
|
||||||
|
void set_inquiry_complete_callback(InquiryCompleteCallback callback);
|
||||||
|
|
||||||
|
// Perform SDP query on a specific device
|
||||||
|
std::vector<SDPService> query_sdp_services(const std::string& address);
|
||||||
|
|
||||||
|
// Get device class information
|
||||||
|
DeviceClass get_device_class(const std::string& address);
|
||||||
|
|
||||||
|
// Get all discovered devices (synchronous, for one-shot queries)
|
||||||
|
std::vector<ClassicDevice> get_discovered_devices();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl; // Forward declaration for PIMPL
|
||||||
|
Impl* pImpl; // Pointer to implementation (hides Objective-C from C++ header)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
std::string decode_device_class_major(uint32_t cod);
|
||||||
|
std::string decode_device_class_minor(uint32_t cod);
|
||||||
|
std::string decode_service_uuid(const std::string& uuid);
|
||||||
|
|
||||||
|
} // namespace ClassicBluetooth
|
||||||
|
|
||||||
|
#endif // CLASSIC_BLUETOOTH_H
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
#import "classic_bluetooth.h"
|
||||||
|
#import <IOBluetooth/IOBluetooth.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// Objective-C delegate for IOBluetoothDeviceInquiry
|
||||||
|
@interface BluetoothInquiryDelegate : NSObject <IOBluetoothDeviceInquiryDelegate>
|
||||||
|
{
|
||||||
|
ClassicBluetooth::DeviceFoundCallback deviceFoundCallback;
|
||||||
|
ClassicBluetooth::InquiryCompleteCallback inquiryCompleteCallback;
|
||||||
|
std::vector<ClassicBluetooth::ClassicDevice>* discoveredDevices;
|
||||||
|
std::mutex* devicesMutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithCallbacks:(ClassicBluetooth::DeviceFoundCallback)foundCb
|
||||||
|
completeCb:(ClassicBluetooth::InquiryCompleteCallback)completeCb
|
||||||
|
devices:(std::vector<ClassicBluetooth::ClassicDevice>*)devices
|
||||||
|
mutex:(std::mutex*)mutex;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation BluetoothInquiryDelegate
|
||||||
|
|
||||||
|
- (instancetype)initWithCallbacks:(ClassicBluetooth::DeviceFoundCallback)foundCb
|
||||||
|
completeCb:(ClassicBluetooth::InquiryCompleteCallback)completeCb
|
||||||
|
devices:(std::vector<ClassicBluetooth::ClassicDevice>*)devices
|
||||||
|
mutex:(std::mutex*)mutex {
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
deviceFoundCallback = foundCb;
|
||||||
|
inquiryCompleteCallback = completeCb;
|
||||||
|
discoveredDevices = devices;
|
||||||
|
devicesMutex = mutex;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
|
||||||
|
device:(IOBluetoothDevice*)device {
|
||||||
|
|
||||||
|
ClassicBluetooth::ClassicDevice classicDevice;
|
||||||
|
|
||||||
|
// Basic info
|
||||||
|
classicDevice.address = [[device addressString] UTF8String];
|
||||||
|
classicDevice.name = device.name ? [device.name UTF8String] : "<unnamed>";
|
||||||
|
classicDevice.rssi = [device rawRSSI];
|
||||||
|
classicDevice.is_connected = [device isConnected];
|
||||||
|
classicDevice.is_paired = [device isPaired];
|
||||||
|
|
||||||
|
// Device class
|
||||||
|
BluetoothClassOfDevice cod = [device classOfDevice];
|
||||||
|
classicDevice.device_class.class_of_device = cod;
|
||||||
|
classicDevice.device_class.major_class = ClassicBluetooth::decode_device_class_major(cod);
|
||||||
|
classicDevice.device_class.minor_class = ClassicBluetooth::decode_device_class_minor(cod);
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto now_time_t = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::tm tm_buf;
|
||||||
|
gmtime_r(&now_time_t, &tm_buf);
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
|
||||||
|
classicDevice.last_seen = oss.str();
|
||||||
|
|
||||||
|
// Get SDP services (if available)
|
||||||
|
NSArray *services = [device services];
|
||||||
|
if (services && [services count] > 0) {
|
||||||
|
for (IOBluetoothSDPServiceRecord *record in services) {
|
||||||
|
ClassicBluetooth::SDPService sdpService;
|
||||||
|
|
||||||
|
// Get RFCOMM channel (for SPP services)
|
||||||
|
BluetoothRFCOMMChannelID channelID;
|
||||||
|
if ([record getRFCOMMChannelID:&channelID] == kIOReturnSuccess) {
|
||||||
|
sdpService.rfcomm_channel = channelID;
|
||||||
|
} else {
|
||||||
|
sdpService.rfcomm_channel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get service UUID from service record
|
||||||
|
BluetoothSDPServiceRecordHandle handle;
|
||||||
|
if ([record getServiceRecordHandle:&handle] == kIOReturnSuccess) {
|
||||||
|
char uuidStr[10];
|
||||||
|
snprintf(uuidStr, sizeof(uuidStr), "%04X", (uint16_t)handle);
|
||||||
|
sdpService.uuid = uuidStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get service name if available
|
||||||
|
NSString *serviceName = nil;
|
||||||
|
IOBluetoothSDPDataElement *nameElement = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceName];
|
||||||
|
if (nameElement) {
|
||||||
|
serviceName = [nameElement getStringValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceName) {
|
||||||
|
sdpService.name = [serviceName UTF8String];
|
||||||
|
} else if (!sdpService.uuid.empty()) {
|
||||||
|
sdpService.name = ClassicBluetooth::decode_service_uuid(sdpService.uuid);
|
||||||
|
} else {
|
||||||
|
sdpService.name = "Unknown Service";
|
||||||
|
}
|
||||||
|
|
||||||
|
classicDevice.services.push_back(sdpService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store device
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(*devicesMutex);
|
||||||
|
discoveredDevices->push_back(classicDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback
|
||||||
|
if (deviceFoundCallback) {
|
||||||
|
deviceFoundCallback(classicDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
|
||||||
|
error:(IOReturn)error
|
||||||
|
aborted:(BOOL)aborted {
|
||||||
|
if (inquiryCompleteCallback) {
|
||||||
|
bool success = (error == kIOReturnSuccess && !aborted);
|
||||||
|
std::string errorMsg;
|
||||||
|
if (!success) {
|
||||||
|
if (aborted) {
|
||||||
|
errorMsg = "Inquiry aborted";
|
||||||
|
} else {
|
||||||
|
errorMsg = "Inquiry failed with error code: " + std::to_string(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inquiryCompleteCallback(success, errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
// PIMPL implementation
|
||||||
|
namespace ClassicBluetooth {
|
||||||
|
|
||||||
|
class Scanner::Impl {
|
||||||
|
public:
|
||||||
|
IOBluetoothDeviceInquiry *inquiry;
|
||||||
|
BluetoothInquiryDelegate *delegate;
|
||||||
|
std::vector<ClassicDevice> discovered_devices;
|
||||||
|
std::mutex devices_mutex;
|
||||||
|
DeviceFoundCallback device_found_callback;
|
||||||
|
InquiryCompleteCallback inquiry_complete_callback;
|
||||||
|
bool is_scanning;
|
||||||
|
|
||||||
|
Impl() : inquiry(nil), delegate(nil), is_scanning(false) {
|
||||||
|
@autoreleasepool {
|
||||||
|
delegate = [[BluetoothInquiryDelegate alloc]
|
||||||
|
initWithCallbacks:nullptr
|
||||||
|
completeCb:nullptr
|
||||||
|
devices:&discovered_devices
|
||||||
|
mutex:&devices_mutex];
|
||||||
|
|
||||||
|
inquiry = [[IOBluetoothDeviceInquiry alloc] initWithDelegate:delegate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Impl() {
|
||||||
|
@autoreleasepool {
|
||||||
|
if (inquiry) {
|
||||||
|
[inquiry stop];
|
||||||
|
inquiry = nil;
|
||||||
|
}
|
||||||
|
delegate = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scanner implementation
|
||||||
|
Scanner::Scanner() : pImpl(new Impl()) {}
|
||||||
|
|
||||||
|
Scanner::~Scanner() {
|
||||||
|
delete pImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Scanner::start_inquiry(int duration_seconds) {
|
||||||
|
@autoreleasepool {
|
||||||
|
if (pImpl->is_scanning) {
|
||||||
|
return false; // Already scanning
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear previous results
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(pImpl->devices_mutex);
|
||||||
|
pImpl->discovered_devices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
[pImpl->inquiry setInquiryLength:duration_seconds];
|
||||||
|
[pImpl->inquiry setUpdateNewDeviceNames:YES];
|
||||||
|
|
||||||
|
IOReturn result = [pImpl->inquiry start];
|
||||||
|
if (result == kIOReturnSuccess) {
|
||||||
|
pImpl->is_scanning = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scanner::stop_inquiry() {
|
||||||
|
@autoreleasepool {
|
||||||
|
if (pImpl->inquiry) {
|
||||||
|
[pImpl->inquiry stop];
|
||||||
|
pImpl->is_scanning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Scanner::is_scanning() const {
|
||||||
|
return pImpl->is_scanning;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scanner::set_device_found_callback(DeviceFoundCallback callback) {
|
||||||
|
pImpl->device_found_callback = callback;
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
pImpl->delegate = [[BluetoothInquiryDelegate alloc]
|
||||||
|
initWithCallbacks:callback
|
||||||
|
completeCb:pImpl->inquiry_complete_callback
|
||||||
|
devices:&pImpl->discovered_devices
|
||||||
|
mutex:&pImpl->devices_mutex];
|
||||||
|
[pImpl->inquiry setDelegate:pImpl->delegate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scanner::set_inquiry_complete_callback(InquiryCompleteCallback callback) {
|
||||||
|
pImpl->inquiry_complete_callback = callback;
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
pImpl->delegate = [[BluetoothInquiryDelegate alloc]
|
||||||
|
initWithCallbacks:pImpl->device_found_callback
|
||||||
|
completeCb:callback
|
||||||
|
devices:&pImpl->discovered_devices
|
||||||
|
mutex:&pImpl->devices_mutex];
|
||||||
|
[pImpl->inquiry setDelegate:pImpl->delegate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ClassicDevice> Scanner::get_discovered_devices() {
|
||||||
|
std::lock_guard<std::mutex> lock(pImpl->devices_mutex);
|
||||||
|
return pImpl->discovered_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SDPService> Scanner::query_sdp_services(const std::string& address) {
|
||||||
|
std::vector<SDPService> services;
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
NSString *addressStr = [NSString stringWithUTF8String:address.c_str()];
|
||||||
|
IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:addressStr];
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform SDP query
|
||||||
|
IOReturn result = [device performSDPQuery:nil];
|
||||||
|
if (result != kIOReturnSuccess) {
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray *sdpRecords = [device services];
|
||||||
|
if (!sdpRecords) {
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IOBluetoothSDPServiceRecord *record in sdpRecords) {
|
||||||
|
SDPService service;
|
||||||
|
|
||||||
|
// Service name
|
||||||
|
NSString *serviceName = [record getServiceName];
|
||||||
|
if (serviceName) {
|
||||||
|
service.name = [serviceName UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFCOMM channel
|
||||||
|
BluetoothRFCOMMChannelID channelID;
|
||||||
|
if ([record getRFCOMMChannelID:&channelID] == kIOReturnSuccess) {
|
||||||
|
service.rfcomm_channel = channelID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get service UUID
|
||||||
|
BluetoothSDPServiceRecordHandle handle;
|
||||||
|
if ([record getServiceRecordHandle:&handle] == kIOReturnSuccess) {
|
||||||
|
char uuidStr[10];
|
||||||
|
snprintf(uuidStr, sizeof(uuidStr), "%04X", (uint16_t)handle);
|
||||||
|
service.uuid = uuidStr;
|
||||||
|
service.name = decode_service_uuid(service.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
services.push_back(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceClass Scanner::get_device_class(const std::string& address) {
|
||||||
|
DeviceClass devClass;
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
NSString *addressStr = [NSString stringWithUTF8String:address.c_str()];
|
||||||
|
IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:addressStr];
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
BluetoothClassOfDevice cod = [device classOfDevice];
|
||||||
|
devClass.class_of_device = cod;
|
||||||
|
devClass.major_class = decode_device_class_major(cod);
|
||||||
|
devClass.minor_class = decode_device_class_minor(cod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return devClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
std::string decode_device_class_major(uint32_t cod) {
|
||||||
|
uint8_t major = (cod >> 8) & 0x1F;
|
||||||
|
|
||||||
|
switch (major) {
|
||||||
|
case 0x00: return "Miscellaneous";
|
||||||
|
case 0x01: return "Computer";
|
||||||
|
case 0x02: return "Phone";
|
||||||
|
case 0x03: return "LAN/Network Access Point";
|
||||||
|
case 0x04: return "Audio/Video";
|
||||||
|
case 0x05: return "Peripheral";
|
||||||
|
case 0x06: return "Imaging";
|
||||||
|
case 0x07: return "Wearable";
|
||||||
|
case 0x08: return "Toy";
|
||||||
|
case 0x09: return "Health";
|
||||||
|
case 0x1F: return "Uncategorized";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decode_device_class_minor(uint32_t cod) {
|
||||||
|
uint8_t major = (cod >> 8) & 0x1F;
|
||||||
|
uint8_t minor = (cod >> 2) & 0x3F;
|
||||||
|
|
||||||
|
// Computer minor classes
|
||||||
|
if (major == 0x01) {
|
||||||
|
switch (minor) {
|
||||||
|
case 0x00: return "Uncategorized";
|
||||||
|
case 0x01: return "Desktop Workstation";
|
||||||
|
case 0x02: return "Server-class Computer";
|
||||||
|
case 0x03: return "Laptop";
|
||||||
|
case 0x04: return "Handheld PC/PDA";
|
||||||
|
case 0x05: return "Palm-size PC/PDA";
|
||||||
|
case 0x06: return "Wearable Computer";
|
||||||
|
case 0x07: return "Tablet";
|
||||||
|
default: return "Unknown Computer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phone minor classes
|
||||||
|
if (major == 0x02) {
|
||||||
|
switch (minor) {
|
||||||
|
case 0x00: return "Uncategorized";
|
||||||
|
case 0x01: return "Cellular";
|
||||||
|
case 0x02: return "Cordless";
|
||||||
|
case 0x03: return "Smartphone";
|
||||||
|
case 0x04: return "Wired Modem/Voice Gateway";
|
||||||
|
case 0x05: return "Common ISDN Access";
|
||||||
|
default: return "Unknown Phone";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio/Video minor classes
|
||||||
|
if (major == 0x04) {
|
||||||
|
switch (minor) {
|
||||||
|
case 0x00: return "Uncategorized";
|
||||||
|
case 0x01: return "Wearable Headset";
|
||||||
|
case 0x02: return "Hands-free";
|
||||||
|
case 0x04: return "Microphone";
|
||||||
|
case 0x05: return "Loudspeaker";
|
||||||
|
case 0x06: return "Headphones";
|
||||||
|
case 0x07: return "Portable Audio";
|
||||||
|
case 0x08: return "Car Audio";
|
||||||
|
case 0x09: return "Set-top Box";
|
||||||
|
case 0x0A: return "HiFi Audio";
|
||||||
|
case 0x0B: return "VCR";
|
||||||
|
case 0x0C: return "Video Camera";
|
||||||
|
case 0x0D: return "Camcorder";
|
||||||
|
case 0x0E: return "Video Monitor";
|
||||||
|
case 0x0F: return "Video Display and Loudspeaker";
|
||||||
|
case 0x10: return "Video Conferencing";
|
||||||
|
case 0x12: return "Gaming/Toy";
|
||||||
|
default: return "Unknown Audio/Video";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decode_service_uuid(const std::string& uuid) {
|
||||||
|
static const std::map<std::string, std::string> SERVICE_MAP = {
|
||||||
|
{"1101", "Serial Port (SPP)"},
|
||||||
|
{"1102", "LAN Access Using PPP"},
|
||||||
|
{"1103", "Dialup Networking (DUN)"},
|
||||||
|
{"1104", "IrMC Sync"},
|
||||||
|
{"1105", "OBEX Object Push"},
|
||||||
|
{"1106", "OBEX File Transfer"},
|
||||||
|
{"1107", "IrMC Sync Command"},
|
||||||
|
{"1108", "Headset"},
|
||||||
|
{"1109", "Cordless Telephony"},
|
||||||
|
{"110A", "Audio Source"},
|
||||||
|
{"110B", "Audio Sink"},
|
||||||
|
{"110C", "A/V Remote Control Target"},
|
||||||
|
{"110D", "Advanced Audio Distribution"},
|
||||||
|
{"110E", "A/V Remote Control"},
|
||||||
|
{"110F", "A/V Remote Control Controller"},
|
||||||
|
{"1112", "Headset Audio Gateway"},
|
||||||
|
{"1115", "PANU"},
|
||||||
|
{"1116", "NAP"},
|
||||||
|
{"1117", "GN"},
|
||||||
|
{"1118", "Direct Printing"},
|
||||||
|
{"111E", "Handsfree"},
|
||||||
|
{"111F", "Handsfree Audio Gateway"},
|
||||||
|
{"1200", "PnP Information"},
|
||||||
|
{"1201", "Generic Networking"},
|
||||||
|
{"1202", "Generic File Transfer"},
|
||||||
|
{"1203", "Generic Audio"},
|
||||||
|
{"1204", "Generic Telephony"},
|
||||||
|
{"1812", "Human Interface Device (HID)"}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = SERVICE_MAP.find(uuid);
|
||||||
|
if (it != SERVICE_MAP.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return "Unknown Service (" + uuid + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ClassicBluetooth
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# serves web_dashboard.html at / on localhost:8090
|
||||||
|
import http.server
|
||||||
|
import os
|
||||||
|
|
||||||
|
PORT = 8090
|
||||||
|
DASHBOARD = "web_dashboard.html"
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path in ("/", "/index.html"):
|
||||||
|
self.path = "/" + DASHBOARD
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
def log_message(self, *args):
|
||||||
|
pass # quiet
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
http.server.HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
|
||||||
+323
@@ -0,0 +1,323 @@
|
|||||||
|
#ifndef KVN_BYTEARRAY_H
|
||||||
|
#define KVN_BYTEARRAY_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace kvn {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class bytearray
|
||||||
|
* @brief A class to handle byte arrays and their conversion from/to hex strings.
|
||||||
|
*/
|
||||||
|
class bytearray {
|
||||||
|
public:
|
||||||
|
using value_type = uint8_t;
|
||||||
|
using size_type = std::size_t;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using reference = uint8_t&;
|
||||||
|
using const_reference = const uint8_t&;
|
||||||
|
using pointer = uint8_t*;
|
||||||
|
using const_pointer = const uint8_t*;
|
||||||
|
using iterator = std::vector<uint8_t>::iterator;
|
||||||
|
using const_iterator = std::vector<uint8_t>::const_iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default constructor.
|
||||||
|
*/
|
||||||
|
bytearray() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from a vector of uint8_t.
|
||||||
|
* @param vec A vector of uint8_t.
|
||||||
|
*/
|
||||||
|
bytearray(const std::vector<uint8_t>& vec) : data_(vec) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from an initializer list of uint8_t.
|
||||||
|
* @param list An initializer list of uint8_t.
|
||||||
|
*/
|
||||||
|
bytearray(std::initializer_list<uint8_t> list) : data_(list) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from a raw pointer and size.
|
||||||
|
* @param ptr A pointer to uint8_t data.
|
||||||
|
* @param size The size of the data.
|
||||||
|
*/
|
||||||
|
bytearray(const uint8_t* ptr, size_t size) : data_(ptr, ptr + size) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from iterators.
|
||||||
|
* @tparam InputIt Iterator type.
|
||||||
|
* @param first Iterator to the first element.
|
||||||
|
* @param last Iterator to one past the last element.
|
||||||
|
*/
|
||||||
|
template <typename InputIt>
|
||||||
|
bytearray(InputIt first, InputIt last) : data_(first, last) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from a std::string.
|
||||||
|
* @param byteArr A string containing byte data.
|
||||||
|
*/
|
||||||
|
bytearray(const std::string& byteArr) : data_(byteArr.begin(), byteArr.end()) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from a C-style string and size.
|
||||||
|
* @param byteArr A C-style string.
|
||||||
|
* @param size The size of the string.
|
||||||
|
*/
|
||||||
|
bytearray(const char* byteArr, size_t size) : bytearray(std::string(byteArr, size)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs byte array from a C-style string.
|
||||||
|
* @param byteArr A C-style string.
|
||||||
|
*/
|
||||||
|
bytearray(const char* byteArr) : bytearray(std::string(byteArr)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a byte array of specified size, initialized with zeros.
|
||||||
|
* @param size The number of bytes to allocate.
|
||||||
|
*/
|
||||||
|
explicit bytearray(size_t size) : data_(size) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a ByteArray from a hex string.
|
||||||
|
*
|
||||||
|
* Case is ignored and the string may have a '0x' hex prefix or not.
|
||||||
|
*
|
||||||
|
* @param hexStr A string containing hex data.
|
||||||
|
* @return A ByteArray object.
|
||||||
|
* @throws std::invalid_argument If the hex string contains non-hexadecimal characters.
|
||||||
|
* @throws std::length_error If the hex string length is not even.
|
||||||
|
*/
|
||||||
|
static bytearray fromHex(const std::string& hexStr) {
|
||||||
|
std::string cleanString(hexStr);
|
||||||
|
|
||||||
|
// Check and skip the '0x' prefix if present
|
||||||
|
if (cleanString.size() >= 2 && cleanString.substr(0, 2) == "0x") {
|
||||||
|
cleanString = cleanString.substr(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = cleanString.size();
|
||||||
|
if (size % 2 != 0) {
|
||||||
|
throw std::length_error("Hex string length must be even.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bytearray byteArray;
|
||||||
|
byteArray.data_.reserve(size / 2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i += 2) {
|
||||||
|
uint8_t byte = static_cast<uint8_t>(std::stoi(cleanString.substr(i, 2), nullptr, 16));
|
||||||
|
byteArray.data_.push_back(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @overload
|
||||||
|
*/
|
||||||
|
static bytearray fromHex(const char* byteArr) { return fromHex(std::string(byteArr)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @overload
|
||||||
|
*/
|
||||||
|
static bytearray fromHex(const char* byteArr, size_t size) { return fromHex(std::string(byteArr, size)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts the byte array to a lowercase hex string without '0x' prefix.
|
||||||
|
* @param spacing Whether to include spaces between bytes.
|
||||||
|
*
|
||||||
|
* @return A hex string representation of the byte array.
|
||||||
|
*/
|
||||||
|
std::string toHex(bool spacing = false) const {
|
||||||
|
std::ostringstream oss;
|
||||||
|
for (auto byte : data_) {
|
||||||
|
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
|
||||||
|
if (spacing) {
|
||||||
|
oss << " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Slices the byte array from a specified start index to an end index.
|
||||||
|
*
|
||||||
|
* This method creates a new byte array containing bytes from the specified range.
|
||||||
|
* The start index is inclusive, while the end index is exclusive.
|
||||||
|
*
|
||||||
|
* @param start The starting index from which to begin slicing.
|
||||||
|
* @param end The ending index up to which to slice (exclusive).
|
||||||
|
* @return byte array A new byte array containing the sliced segment.
|
||||||
|
* @throws std::out_of_range If the start index is greater than the end index or if the end index is out of bounds.
|
||||||
|
*/
|
||||||
|
bytearray slice(size_t start, size_t end) const {
|
||||||
|
if (start > end || end > data_.size()) {
|
||||||
|
throw std::out_of_range("Invalid slice range");
|
||||||
|
}
|
||||||
|
return bytearray(data_.data() + start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Slices the byte array from a specified start index to the end of the array.
|
||||||
|
*
|
||||||
|
* This method creates a new byte array containing all bytes from the specified start index to the end of the
|
||||||
|
* byte array.
|
||||||
|
*
|
||||||
|
* @param start The starting index from which to begin slicing.
|
||||||
|
* @return byte array A new byte array containing the sliced segment from the start index to the end.
|
||||||
|
* @throws std::out_of_range If the start index is out of the bounds of the byte array.
|
||||||
|
*/
|
||||||
|
bytearray slice_from(size_t start) const { return slice(start, data_.size()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Slices the byte array from the beginning to a specified end index.
|
||||||
|
*
|
||||||
|
* This method creates a new byte array containing all bytes from the beginning of the byte array to the specified
|
||||||
|
* end index.
|
||||||
|
*
|
||||||
|
* @param end The ending index up to which to slice (exclusive).
|
||||||
|
* @return byte array A new byte array containing the sliced segment from the beginning to the end index.
|
||||||
|
* @throws std::out_of_range If the end index is out of the bounds of the byte array.
|
||||||
|
*/
|
||||||
|
bytearray slice_to(size_t end) const { return slice(0, end); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Overloaded stream insertion operator for byte array.
|
||||||
|
* @param os The output stream.
|
||||||
|
* @param byteArray The byte array object.
|
||||||
|
* @return The output stream.
|
||||||
|
*/
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const bytearray& byteArray) {
|
||||||
|
os << byteArray.toHex(true);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conversion operator to convert byte array to std::string.
|
||||||
|
*
|
||||||
|
* @note This is provided to support code that relies on byte array
|
||||||
|
* being representd as a string.
|
||||||
|
* @return String containing the raw bytes of the byte array
|
||||||
|
*/
|
||||||
|
operator std::string() const { return std::string(data_.begin(), data_.end()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conversion operator to convert byte array to std::vector<uint8_t>.
|
||||||
|
* @return Vector containing the raw bytes of the byte array
|
||||||
|
*/
|
||||||
|
operator std::vector<uint8_t>() const { return data_; }
|
||||||
|
|
||||||
|
//! @cond Doxygen_Suppress
|
||||||
|
// Expose vector-like functionality
|
||||||
|
size_type size() const { return data_.size(); }
|
||||||
|
const uint8_t* data() const { return data_.data(); }
|
||||||
|
uint8_t* data() { return data_.data(); }
|
||||||
|
bool empty() const { return data_.empty(); }
|
||||||
|
void clear() { data_.clear(); }
|
||||||
|
void reserve(size_type n) { data_.reserve(n); }
|
||||||
|
void resize(size_type n) { data_.resize(n); }
|
||||||
|
void resize(size_type n, uint8_t v) { data_.resize(n, v); }
|
||||||
|
uint8_t& operator[](size_type index) { return data_[index]; }
|
||||||
|
const uint8_t& operator[](size_type index) const { return data_[index]; }
|
||||||
|
uint8_t& at(size_type index) { return data_.at(index); }
|
||||||
|
const uint8_t& at(size_type index) const { return data_.at(index); }
|
||||||
|
void push_back(uint8_t byte) { data_.push_back(byte); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a uint16_t value as 2 little-endian bytes.
|
||||||
|
* @param value The uint16_t value to append.
|
||||||
|
*/
|
||||||
|
void push_back(uint16_t value) {
|
||||||
|
for (size_t i = 0; i < 2; ++i) {
|
||||||
|
data_.push_back(static_cast<uint8_t>(value >> (i * 8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a uint32_t value as 4 little-endian bytes.
|
||||||
|
* @param value The uint32_t value to append.
|
||||||
|
*/
|
||||||
|
void push_back(uint32_t value) {
|
||||||
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
|
data_.push_back(static_cast<uint8_t>(value >> (i * 8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a uint64_t value as 8 little-endian bytes.
|
||||||
|
* @param value The uint64_t value to append.
|
||||||
|
*/
|
||||||
|
void push_back(uint64_t value) {
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
data_.push_back(static_cast<uint8_t>(value >> (i * 8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto begin() { return data_.begin(); }
|
||||||
|
auto begin() const { return data_.begin(); }
|
||||||
|
auto end() { return data_.end(); }
|
||||||
|
auto end() const { return data_.end(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inserts a single byte at the specified position.
|
||||||
|
* @param pos Iterator to the position where the element is inserted.
|
||||||
|
* @param value The byte to insert.
|
||||||
|
* @return Iterator pointing to the inserted element.
|
||||||
|
*/
|
||||||
|
auto insert(iterator pos, uint8_t value) { return data_.insert(pos, value); }
|
||||||
|
auto insert(const_iterator pos, uint8_t value) { return data_.insert(pos, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inserts multiple copies of a byte at the specified position.
|
||||||
|
* @param pos Iterator to the position where the elements are inserted.
|
||||||
|
* @param count Number of copies to insert.
|
||||||
|
* @param value The byte to insert.
|
||||||
|
* @return Iterator pointing to the first inserted element.
|
||||||
|
*/
|
||||||
|
auto insert(iterator pos, size_type count, uint8_t value) { return data_.insert(pos, count, value); }
|
||||||
|
auto insert(const_iterator pos, size_type count, uint8_t value) { return data_.insert(pos, count, value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inserts elements from a range at the specified position.
|
||||||
|
* @tparam InputIt Iterator type.
|
||||||
|
* @param pos Iterator to the position where the elements are inserted.
|
||||||
|
* @param first Iterator to the first element of the range.
|
||||||
|
* @param last Iterator to one past the last element of the range.
|
||||||
|
* @return Iterator pointing to the first inserted element.
|
||||||
|
*/
|
||||||
|
template <typename InputIt>
|
||||||
|
auto insert(iterator pos, InputIt first, InputIt last) {
|
||||||
|
return data_.insert(pos, first, last);
|
||||||
|
}
|
||||||
|
template <typename InputIt>
|
||||||
|
auto insert(const_iterator pos, InputIt first, InputIt last) {
|
||||||
|
return data_.insert(pos, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inserts another bytearray at the specified position.
|
||||||
|
* @param pos Iterator to the position where the elements are inserted.
|
||||||
|
* @param other The bytearray to insert.
|
||||||
|
* @return Iterator pointing to the first inserted element.
|
||||||
|
*/
|
||||||
|
auto insert(iterator pos, const bytearray& other) { return data_.insert(pos, other.begin(), other.end()); }
|
||||||
|
auto insert(const_iterator pos, const bytearray& other) { return data_.insert(pos, other.begin(), other.end()); }
|
||||||
|
|
||||||
|
//! @endcond
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kvn
|
||||||
|
|
||||||
|
#endif // KVN_BYTEARRAY_H
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2022 Kevin Dewald <kevin@dewald.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KVN_SAFE_CALLBACK_HPP
|
||||||
|
#define KVN_SAFE_CALLBACK_HPP
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace kvn {
|
||||||
|
|
||||||
|
template <typename _Signature>
|
||||||
|
class safe_callback;
|
||||||
|
|
||||||
|
template <class _Res, class... _ArgTypes>
|
||||||
|
class safe_callback<_Res(_ArgTypes...)> {
|
||||||
|
public:
|
||||||
|
safe_callback() = default;
|
||||||
|
|
||||||
|
// Remove copy constructor and copy assignment
|
||||||
|
safe_callback(const safe_callback&) = delete;
|
||||||
|
safe_callback& operator=(const safe_callback&) = delete;
|
||||||
|
|
||||||
|
virtual ~safe_callback() { unload(); };
|
||||||
|
|
||||||
|
void load(std::function<_Res(_ArgTypes...)> callback) {
|
||||||
|
if (callback == nullptr) return;
|
||||||
|
|
||||||
|
std::scoped_lock lock(_mutex);
|
||||||
|
_callback = std::move(callback);
|
||||||
|
_is_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unload() {
|
||||||
|
std::scoped_lock lock(_mutex);
|
||||||
|
_callback = nullptr;
|
||||||
|
_is_loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_loaded() const { return _is_loaded; }
|
||||||
|
|
||||||
|
explicit operator bool() const { return is_loaded(); }
|
||||||
|
|
||||||
|
_Res operator()(_ArgTypes... arguments) {
|
||||||
|
std::scoped_lock lock(_mutex);
|
||||||
|
if (_is_loaded) {
|
||||||
|
return _callback(std::forward<_ArgTypes&&>(arguments)...);
|
||||||
|
} else {
|
||||||
|
return _Res();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::atomic_bool _is_loaded{false};
|
||||||
|
std::function<_Res(_ArgTypes...)> _callback;
|
||||||
|
std::recursive_mutex _mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kvn
|
||||||
|
|
||||||
|
#endif // KVN_SAFE_CALLBACK_HPP
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef LOGFWD_HPP
|
||||||
|
#define LOGFWD_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace logfwd {
|
||||||
|
|
||||||
|
enum level : int {
|
||||||
|
NONE = 0,
|
||||||
|
FATAL,
|
||||||
|
ERROR,
|
||||||
|
WARN,
|
||||||
|
INFO,
|
||||||
|
#pragma push_macro("DEBUG")
|
||||||
|
#undef DEBUG
|
||||||
|
DEBUG,
|
||||||
|
#pragma pop_macro("DEBUG")
|
||||||
|
VERBOSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
void receive(
|
||||||
|
level level,
|
||||||
|
const std::string& module,
|
||||||
|
const std::string& file,
|
||||||
|
uint32_t line,
|
||||||
|
const std::string& function,
|
||||||
|
const std::string& message);
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
} // namespace logfwd
|
||||||
|
|
||||||
|
#endif // LOGFWD_HPP
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
#ifndef SIMPLEBLE_EXPORT_H
|
||||||
|
#define SIMPLEBLE_EXPORT_H
|
||||||
|
|
||||||
|
#ifdef SIMPLEBLE_STATIC_DEFINE
|
||||||
|
# define SIMPLEBLE_EXPORT
|
||||||
|
# define SIMPLEBLE_NO_EXPORT
|
||||||
|
#else
|
||||||
|
# ifndef SIMPLEBLE_EXPORT
|
||||||
|
# ifdef simpleble_EXPORTS
|
||||||
|
/* We are building this library */
|
||||||
|
# define SIMPLEBLE_EXPORT
|
||||||
|
# else
|
||||||
|
/* We are using this library */
|
||||||
|
# define SIMPLEBLE_EXPORT
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# ifndef SIMPLEBLE_NO_EXPORT
|
||||||
|
# define SIMPLEBLE_NO_EXPORT
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIMPLEBLE_DEPRECATED
|
||||||
|
# define SIMPLEBLE_DEPRECATED __attribute__ ((__deprecated__))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIMPLEBLE_DEPRECATED_EXPORT
|
||||||
|
# define SIMPLEBLE_DEPRECATED_EXPORT SIMPLEBLE_EXPORT SIMPLEBLE_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIMPLEBLE_DEPRECATED_NO_EXPORT
|
||||||
|
# define SIMPLEBLE_DEPRECATED_NO_EXPORT SIMPLEBLE_NO_EXPORT SIMPLEBLE_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(readability-avoid-unconditional-preprocessor-if) */
|
||||||
|
#if 0 /* DEFINE_NO_DEPRECATED */
|
||||||
|
# ifndef SIMPLEBLE_NO_DEPRECATED
|
||||||
|
# define SIMPLEBLE_NO_DEPRECATED
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* SIMPLEBLE_EXPORT_H */
|
||||||
Binary file not shown.
@@ -0,0 +1,105 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Exceptions.h>
|
||||||
|
#include <simpleble/Peripheral.h>
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class AdapterBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bluetooth Adapter.
|
||||||
|
*
|
||||||
|
* A default-constructed Adapter object is not initialized. Calling any method
|
||||||
|
* other than `initialized()` on an uninitialized Adapter will throw an exception
|
||||||
|
* of type `SimpleBLE::NotInitialized`.
|
||||||
|
*
|
||||||
|
* NOTE: This class is intended to be used by the user only. Library developers
|
||||||
|
* should use shared pointers to `AdapterBase` instead.
|
||||||
|
*/
|
||||||
|
class SIMPLEBLE_EXPORT Adapter {
|
||||||
|
public:
|
||||||
|
Adapter() = default;
|
||||||
|
virtual ~Adapter() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying OS object/handle.
|
||||||
|
*
|
||||||
|
* For certain compatibility with external libraries, we sometimes need to
|
||||||
|
* expose the actual OS handle to the user. This is particularly important
|
||||||
|
* for MacOS right now.
|
||||||
|
*/
|
||||||
|
void* underlying() const;
|
||||||
|
|
||||||
|
std::string identifier();
|
||||||
|
BluetoothAddress address();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control the power state of the adapter.
|
||||||
|
*
|
||||||
|
* NOTE: Power control support depends on the backend and platform.
|
||||||
|
* Unsupported backends may do nothing.
|
||||||
|
* NOTE: Power callbacks are supported by backends that provide power state change events.
|
||||||
|
*/
|
||||||
|
void power_on();
|
||||||
|
void power_off();
|
||||||
|
bool is_powered();
|
||||||
|
void set_callback_on_power_on(std::function<void()> on_power_on);
|
||||||
|
void set_callback_on_power_off(std::function<void()> on_power_off);
|
||||||
|
|
||||||
|
void scan_start();
|
||||||
|
void scan_stop();
|
||||||
|
void scan_for(int timeout_ms);
|
||||||
|
bool scan_is_active();
|
||||||
|
std::vector<Peripheral> scan_get_results();
|
||||||
|
|
||||||
|
void set_callback_on_scan_start(std::function<void()> on_scan_start);
|
||||||
|
void set_callback_on_scan_stop(std::function<void()> on_scan_stop);
|
||||||
|
void set_callback_on_scan_updated(std::function<void(Peripheral)> on_scan_updated);
|
||||||
|
void set_callback_on_scan_found(std::function<void(Peripheral)> on_scan_found);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of all paired peripherals.
|
||||||
|
*
|
||||||
|
* NOTE:This method is currently only supported by the Linux, Windows and Android backends.
|
||||||
|
*/
|
||||||
|
std::vector<Peripheral> get_paired_peripherals();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of all connected peripherals.
|
||||||
|
*
|
||||||
|
* NOTE: This method is currently only supported by the Windows backend. (More backends coming soon.)
|
||||||
|
*/
|
||||||
|
std::vector<Peripheral> get_connected_peripherals();
|
||||||
|
|
||||||
|
static bool bluetooth_enabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a list of all available adapters from all available backends.
|
||||||
|
*
|
||||||
|
* This will cause backends to be instantiated/initialized and adapters
|
||||||
|
* too.
|
||||||
|
*
|
||||||
|
* @note All configuration values must be set prior to calling this function.
|
||||||
|
* Please refer to the SimpleBLE::Config documentation for more details.
|
||||||
|
*/
|
||||||
|
static std::vector<Adapter> get_adapters();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AdapterBase* operator->();
|
||||||
|
const AdapterBase* operator->() const;
|
||||||
|
|
||||||
|
std::shared_ptr<AdapterBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Adapter.h>
|
||||||
|
#include <simpleble/PeripheralSafe.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
namespace Safe {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around the Adapter class that provides a noexcept interface.
|
||||||
|
*
|
||||||
|
* We use instances of this class directly and not through shared_ptr because
|
||||||
|
* this is just a wrapper around the Adapter class, which is already managed by
|
||||||
|
* shared_ptr.
|
||||||
|
*/
|
||||||
|
class SIMPLEBLE_EXPORT Adapter {
|
||||||
|
public:
|
||||||
|
Adapter(SimpleBLE::Adapter& adapter);
|
||||||
|
Adapter(SimpleBLE::Adapter&& adapter);
|
||||||
|
virtual ~Adapter() = default;
|
||||||
|
|
||||||
|
std::optional<std::string> identifier() noexcept;
|
||||||
|
std::optional<BluetoothAddress> address() noexcept;
|
||||||
|
|
||||||
|
bool scan_start() noexcept;
|
||||||
|
bool scan_stop() noexcept;
|
||||||
|
bool scan_for(int timeout_ms) noexcept;
|
||||||
|
std::optional<bool> scan_is_active() noexcept;
|
||||||
|
std::optional<std::vector<SimpleBLE::Safe::Peripheral>> scan_get_results() noexcept;
|
||||||
|
|
||||||
|
bool set_callback_on_scan_start(std::function<void()> on_scan_start) noexcept;
|
||||||
|
bool set_callback_on_scan_stop(std::function<void()> on_scan_stop) noexcept;
|
||||||
|
bool set_callback_on_scan_updated(std::function<void(SimpleBLE::Safe::Peripheral)> on_scan_updated) noexcept;
|
||||||
|
bool set_callback_on_scan_found(std::function<void(SimpleBLE::Safe::Peripheral)> on_scan_found) noexcept;
|
||||||
|
|
||||||
|
std::optional<std::vector<SimpleBLE::Safe::Peripheral>> get_paired_peripherals() noexcept;
|
||||||
|
|
||||||
|
static std::optional<bool> bluetooth_enabled() noexcept;
|
||||||
|
static std::optional<std::vector<SimpleBLE::Safe::Adapter>> get_adapters() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast to the underlying adapter object.
|
||||||
|
*/
|
||||||
|
operator SimpleBLE::Adapter() const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SimpleBLE::Adapter internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Safe
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#if __APPLE__
|
||||||
|
#include "TargetConditionals.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced Features
|
||||||
|
*
|
||||||
|
* The functions presented in this namespace are OS-specific backdoors that are
|
||||||
|
* not part of the standard SimpleBLE API, which allow the user to access
|
||||||
|
* low-level details of the implementation for advanced use cases.
|
||||||
|
*
|
||||||
|
* These functions should be used with caution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
namespace SimpleBLE::Advanced::Windows {}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
namespace SimpleBLE::Advanced::MacOS {}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
|
namespace SimpleBLE::Advanced::iOS {}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE::Advanced::Android {
|
||||||
|
|
||||||
|
JavaVM* SIMPLEBLE_EXPORT get_jvm();
|
||||||
|
void SIMPLEBLE_EXPORT set_jvm(JavaVM* jvm);
|
||||||
|
|
||||||
|
} // namespace SimpleBLE::Advanced::Android
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
|
namespace SimpleBLE::Advanced::Linux {}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <simpleble/Adapter.h>
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class BackendBase;
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Backend {
|
||||||
|
friend class Adapter;
|
||||||
|
public:
|
||||||
|
Backend() = default;
|
||||||
|
virtual ~Backend() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of all available adapters from this backend.
|
||||||
|
*/
|
||||||
|
std::vector<Adapter> adapters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Bluetooth is enabled for this backend.
|
||||||
|
*/
|
||||||
|
bool bluetooth_enabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identifier of the backend.
|
||||||
|
*/
|
||||||
|
std::string identifier() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available backends.
|
||||||
|
*
|
||||||
|
* This will cause backends to be instantiated/initialized.
|
||||||
|
*/
|
||||||
|
static std::vector<Backend> get_backends();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
BackendBase* operator->();
|
||||||
|
const BackendBase* operator->() const;
|
||||||
|
|
||||||
|
std::shared_ptr<BackendBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Descriptor.h>
|
||||||
|
#include <simpleble/Exceptions.h>
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class CharacteristicBase;
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Characteristic {
|
||||||
|
public:
|
||||||
|
Characteristic() = default;
|
||||||
|
virtual ~Characteristic() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
|
||||||
|
BluetoothUUID uuid();
|
||||||
|
std::vector<Descriptor> descriptors();
|
||||||
|
std::vector<std::string> capabilities();
|
||||||
|
|
||||||
|
bool can_read();
|
||||||
|
bool can_write_request();
|
||||||
|
bool can_write_command();
|
||||||
|
bool can_notify();
|
||||||
|
bool can_indicate();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CharacteristicBase* operator->();
|
||||||
|
const CharacteristicBase* operator->() const;
|
||||||
|
|
||||||
|
std::shared_ptr<CharacteristicBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <chrono>
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
//clang-format off
|
||||||
|
namespace SimpleBLE {
|
||||||
|
/**
|
||||||
|
* @namespace SimpleBLE::Config
|
||||||
|
* @brief Configuration options for SimpleBLE.
|
||||||
|
*
|
||||||
|
* @note All configuration values must be set prior to any other interaction with a SimpleBLE component.
|
||||||
|
* Changes made after an adapter has been retrieved may not take effect or could lead to undefined behavior.
|
||||||
|
*/
|
||||||
|
namespace Config {
|
||||||
|
namespace SimpleBluez {
|
||||||
|
extern SIMPLEBLE_EXPORT bool use_legacy_bluez_backend;
|
||||||
|
extern SIMPLEBLE_EXPORT bool use_system_bus; // NOTE: This is only available in the new Bluez backend.
|
||||||
|
extern SIMPLEBLE_EXPORT std::chrono::steady_clock::duration connection_timeout;
|
||||||
|
extern SIMPLEBLE_EXPORT std::chrono::steady_clock::duration disconnection_timeout;
|
||||||
|
|
||||||
|
static void reset() {
|
||||||
|
use_legacy_bluez_backend = false;
|
||||||
|
use_system_bus = true;
|
||||||
|
connection_timeout = std::chrono::seconds(2);
|
||||||
|
disconnection_timeout = std::chrono::seconds(1);
|
||||||
|
}
|
||||||
|
} // namespace SimpleBluez
|
||||||
|
|
||||||
|
namespace WinRT {
|
||||||
|
/**
|
||||||
|
* @deprecated SimpleBLE uses its own MTA apartment by default. This compatibility flag will be removed in a future release.
|
||||||
|
*/
|
||||||
|
extern SIMPLEBLE_EXPORT bool experimental_use_own_mta_apartment;
|
||||||
|
extern SIMPLEBLE_EXPORT bool experimental_reinitialize_winrt_apartment_on_main_thread;
|
||||||
|
extern SIMPLEBLE_EXPORT bool use_deferred_disconnect;
|
||||||
|
|
||||||
|
static void reset() {
|
||||||
|
experimental_use_own_mta_apartment = true;
|
||||||
|
experimental_reinitialize_winrt_apartment_on_main_thread = false;
|
||||||
|
use_deferred_disconnect = true;
|
||||||
|
}
|
||||||
|
} // namespace WinRT
|
||||||
|
|
||||||
|
namespace CoreBluetooth {
|
||||||
|
static void reset() {}
|
||||||
|
} // namespace CoreBluetooth
|
||||||
|
|
||||||
|
namespace Android {
|
||||||
|
enum class ConnectionPriorityRequest { DISABLED = -1, BALANCED = 0, HIGH = 1, LOW_POWER = 2, DCK = 3 };
|
||||||
|
|
||||||
|
extern SIMPLEBLE_EXPORT ConnectionPriorityRequest connection_priority_request;
|
||||||
|
|
||||||
|
static void reset() { connection_priority_request = ConnectionPriorityRequest::DISABLED; }
|
||||||
|
} // namespace Android
|
||||||
|
|
||||||
|
namespace Dongl {
|
||||||
|
extern SIMPLEBLE_EXPORT bool use_dongl_backend;
|
||||||
|
extern SIMPLEBLE_EXPORT bool auto_update;
|
||||||
|
extern SIMPLEBLE_EXPORT bool force_update;
|
||||||
|
static void reset() {
|
||||||
|
use_dongl_backend = false;
|
||||||
|
auto_update = false;
|
||||||
|
force_update = false;
|
||||||
|
}
|
||||||
|
} // namespace Dongl
|
||||||
|
|
||||||
|
namespace Base {
|
||||||
|
static void reset_all() {
|
||||||
|
SimpleBluez::reset();
|
||||||
|
WinRT::reset();
|
||||||
|
CoreBluetooth::reset();
|
||||||
|
Android::reset();
|
||||||
|
Dongl::reset();
|
||||||
|
}
|
||||||
|
} // namespace Base
|
||||||
|
} // namespace Config
|
||||||
|
} // namespace SimpleBLE
|
||||||
|
//clang-format on
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Exceptions.h>
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class DescriptorBase;
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Descriptor {
|
||||||
|
public:
|
||||||
|
Descriptor() = default;
|
||||||
|
virtual ~Descriptor() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
|
||||||
|
BluetoothUUID uuid();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DescriptorBase* operator->();
|
||||||
|
const DescriptorBase* operator->() const;
|
||||||
|
|
||||||
|
std::shared_ptr<DescriptorBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
namespace Exception {
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT BaseException : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
BaseException(const std::string& __arg) : std::runtime_error(__arg) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT NotInitialized : public BaseException {
|
||||||
|
public:
|
||||||
|
NotInitialized();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT NotConnected : public BaseException {
|
||||||
|
public:
|
||||||
|
NotConnected();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT InvalidReference : public BaseException {
|
||||||
|
public:
|
||||||
|
InvalidReference();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT ServiceNotFound : public BaseException {
|
||||||
|
public:
|
||||||
|
ServiceNotFound(BluetoothUUID uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT CharacteristicNotFound : public BaseException {
|
||||||
|
public:
|
||||||
|
CharacteristicNotFound(BluetoothUUID uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT DescriptorNotFound : public BaseException {
|
||||||
|
public:
|
||||||
|
DescriptorNotFound(BluetoothUUID uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT OperationNotSupported : public BaseException {
|
||||||
|
public:
|
||||||
|
OperationNotSupported();
|
||||||
|
OperationNotSupported(const std::string& operation, const BluetoothUUID& characteristic_uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT OperationFailed : public BaseException {
|
||||||
|
public:
|
||||||
|
OperationFailed();
|
||||||
|
OperationFailed(const std::string& err_msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT WinRTException : public BaseException {
|
||||||
|
public:
|
||||||
|
WinRTException(int32_t err_code, const std::string& err_msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT CoreBluetoothException : public BaseException {
|
||||||
|
public:
|
||||||
|
CoreBluetoothException(const std::string& err_msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Exception
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
namespace Logging {
|
||||||
|
|
||||||
|
enum Level : int {
|
||||||
|
None = 0,
|
||||||
|
Fatal,
|
||||||
|
Error,
|
||||||
|
Warn,
|
||||||
|
Info,
|
||||||
|
Debug,
|
||||||
|
Verbose,
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
using Callback = std::function<void(
|
||||||
|
Level,
|
||||||
|
const std::string& module,
|
||||||
|
const std::string& file,
|
||||||
|
uint32_t line,
|
||||||
|
const std::string& function,
|
||||||
|
const std::string& message)>;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Logger {
|
||||||
|
public:
|
||||||
|
static Logger* get();
|
||||||
|
|
||||||
|
void set_level(Level level);
|
||||||
|
Level get_level();
|
||||||
|
|
||||||
|
void set_callback(Callback callback);
|
||||||
|
bool has_callback();
|
||||||
|
|
||||||
|
void log_default_stdout();
|
||||||
|
void log_default_file();
|
||||||
|
void log_default_file(const std::string path);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
void log(
|
||||||
|
Level level,
|
||||||
|
const std::string& module,
|
||||||
|
const std::string& file,
|
||||||
|
uint32_t line,
|
||||||
|
const std::string& function,
|
||||||
|
const std::string& message);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger();
|
||||||
|
~Logger();
|
||||||
|
Logger(Logger& other) = delete; // Remove copy constructor
|
||||||
|
void operator=(const Logger&) = delete; // Remove copy assignment
|
||||||
|
|
||||||
|
static std::string level_to_str(Level level);
|
||||||
|
|
||||||
|
Level level_{Level::Info};
|
||||||
|
Callback callback_{nullptr};
|
||||||
|
std::recursive_mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Logging
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Exceptions.h>
|
||||||
|
#include <simpleble/Service.h>
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class PeripheralBase;
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Peripheral {
|
||||||
|
public:
|
||||||
|
Peripheral() = default;
|
||||||
|
virtual ~Peripheral() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
void* underlying() const;
|
||||||
|
|
||||||
|
std::string identifier();
|
||||||
|
BluetoothAddress address();
|
||||||
|
BluetoothAddressType address_type();
|
||||||
|
int16_t rssi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the advertised transmit power in dBm.
|
||||||
|
*
|
||||||
|
* @note If the field has not been advertised by the peripheral,
|
||||||
|
* the returned value will be -32768.
|
||||||
|
*/
|
||||||
|
int16_t tx_power();
|
||||||
|
uint16_t mtu();
|
||||||
|
|
||||||
|
void connect();
|
||||||
|
void disconnect();
|
||||||
|
bool is_connected();
|
||||||
|
bool is_connectable();
|
||||||
|
bool is_paired();
|
||||||
|
void unpair();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides a list of all services that are available on the peripheral.
|
||||||
|
*
|
||||||
|
* @note If the peripheral is not connected, it will return a list of services
|
||||||
|
* that were advertised by the device.
|
||||||
|
*/
|
||||||
|
std::vector<Service> services();
|
||||||
|
std::map<uint16_t, ByteArray> manufacturer_data();
|
||||||
|
|
||||||
|
/* Calling any of the methods below when the device is not connected will throw
|
||||||
|
Exception::NotConnected */
|
||||||
|
// clang-format off
|
||||||
|
ByteArray read(BluetoothUUID const& service, BluetoothUUID const& characteristic);
|
||||||
|
void write_request(BluetoothUUID const& service, BluetoothUUID const& characteristic, ByteArray const& data);
|
||||||
|
void write_command(BluetoothUUID const& service, BluetoothUUID const& characteristic, ByteArray const& data);
|
||||||
|
void notify(BluetoothUUID const& service, BluetoothUUID const& characteristic, std::function<void(ByteArray payload)> callback);
|
||||||
|
void indicate(BluetoothUUID const& service, BluetoothUUID const& characteristic, std::function<void(ByteArray payload)> callback);
|
||||||
|
void unsubscribe(BluetoothUUID const& service, BluetoothUUID const& characteristic);
|
||||||
|
|
||||||
|
ByteArray read(BluetoothUUID const& service, BluetoothUUID const& characteristic, BluetoothUUID const& descriptor);
|
||||||
|
void write(BluetoothUUID const& service, BluetoothUUID const& characteristic, BluetoothUUID const& descriptor, ByteArray const& data);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
void set_callback_on_connected(std::function<void()> on_connected);
|
||||||
|
void set_callback_on_disconnected(std::function<void()> on_disconnected);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PeripheralBase* operator->();
|
||||||
|
const PeripheralBase* operator->() const;
|
||||||
|
|
||||||
|
std::shared_ptr<PeripheralBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Peripheral.h>
|
||||||
|
#include <simpleble/Service.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
namespace Safe {
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Peripheral {
|
||||||
|
public:
|
||||||
|
Peripheral(SimpleBLE::Peripheral& peripheral);
|
||||||
|
Peripheral(SimpleBLE::Peripheral&& peripheral);
|
||||||
|
virtual ~Peripheral() = default;
|
||||||
|
|
||||||
|
std::optional<std::string> identifier() noexcept;
|
||||||
|
std::optional<BluetoothAddress> address() noexcept;
|
||||||
|
std::optional<BluetoothAddressType> address_type() noexcept;
|
||||||
|
std::optional<int16_t> rssi() noexcept;
|
||||||
|
std::optional<int16_t> tx_power() noexcept;
|
||||||
|
std::optional<uint16_t> mtu() noexcept;
|
||||||
|
|
||||||
|
bool connect() noexcept;
|
||||||
|
bool disconnect() noexcept;
|
||||||
|
std::optional<bool> is_connected() noexcept;
|
||||||
|
std::optional<bool> is_connectable() noexcept;
|
||||||
|
std::optional<bool> is_paired() noexcept;
|
||||||
|
bool unpair() noexcept;
|
||||||
|
|
||||||
|
std::optional<std::vector<Service>> services() noexcept;
|
||||||
|
std::optional<std::map<uint16_t, ByteArray>> manufacturer_data() noexcept;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
std::optional<ByteArray> read(BluetoothUUID const& service, BluetoothUUID const& characteristic) noexcept;
|
||||||
|
bool write_request(BluetoothUUID const& service, BluetoothUUID const& characteristic, ByteArray const& data) noexcept;
|
||||||
|
bool write_command(BluetoothUUID const& service, BluetoothUUID const& characteristic, ByteArray const& data) noexcept;
|
||||||
|
bool notify(BluetoothUUID const& service, BluetoothUUID const& characteristic, std::function<void(ByteArray payload)> callback) noexcept;
|
||||||
|
bool indicate(BluetoothUUID const& service, BluetoothUUID const& characteristic, std::function<void(ByteArray payload)> callback) noexcept;
|
||||||
|
bool unsubscribe(BluetoothUUID const& service, BluetoothUUID const& characteristic) noexcept;
|
||||||
|
|
||||||
|
std::optional<ByteArray> read(BluetoothUUID const& service, BluetoothUUID const& characteristic, BluetoothUUID const& descriptor) noexcept;
|
||||||
|
bool write(BluetoothUUID const& service, BluetoothUUID const& characteristic, BluetoothUUID const& descriptor, ByteArray const& data) noexcept;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
bool set_callback_on_connected(std::function<void()> on_connected) noexcept;
|
||||||
|
bool set_callback_on_disconnected(std::function<void()> on_disconnected) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the underlying peripheral object.
|
||||||
|
*/
|
||||||
|
operator SimpleBLE::Peripheral() const noexcept;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SimpleBLE::Peripheral internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Safe
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Exceptions.h>
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
#include "simpleble/Characteristic.h"
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
class ServiceBase;
|
||||||
|
|
||||||
|
class SIMPLEBLE_EXPORT Service {
|
||||||
|
public:
|
||||||
|
Service() = default;
|
||||||
|
virtual ~Service() = default;
|
||||||
|
|
||||||
|
bool initialized() const;
|
||||||
|
|
||||||
|
BluetoothUUID uuid();
|
||||||
|
ByteArray data();
|
||||||
|
std::vector<Characteristic> characteristics();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const ServiceBase* operator->() const;
|
||||||
|
ServiceBase* operator->();
|
||||||
|
|
||||||
|
std::shared_ptr<ServiceBase> internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <simpleble/Config.h>
|
||||||
|
#include <simpleble/Adapter.h>
|
||||||
|
#include <simpleble/AdapterSafe.h>
|
||||||
|
#include <simpleble/Peripheral.h>
|
||||||
|
#include <simpleble/PeripheralSafe.h>
|
||||||
|
#include <simpleble/Utils.h>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "kvn/kvn_bytearray.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file Types.h
|
||||||
|
* @brief Defines types and enumerations used throughout the SimpleBLE library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
using BluetoothAddress = std::string;
|
||||||
|
|
||||||
|
// IDEA: Extend BluetoothUUID to include a `uuid` function that
|
||||||
|
// returns the same string, but provides a homogeneous interface.
|
||||||
|
using BluetoothUUID = std::string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef ByteArray
|
||||||
|
* @brief Represents a byte array using kvn::bytearray from the external library.
|
||||||
|
*/
|
||||||
|
using ByteArray = kvn::bytearray;
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
#pragma push_macro("ANDROID")
|
||||||
|
#undef ANDROID
|
||||||
|
#define ANDROID_WAS_DEFINED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum class OperatingSystem {
|
||||||
|
WINDOWS,
|
||||||
|
MACOS,
|
||||||
|
IOS,
|
||||||
|
LINUX,
|
||||||
|
ANDROID,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef ANDROID_WAS_DEFINED
|
||||||
|
#pragma pop_macro("ANDROID")
|
||||||
|
#undef ANDROID_WAS_DEFINED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TODO: Add to_string functions for all enums.
|
||||||
|
enum BluetoothAddressType : int32_t { PUBLIC = 0, RANDOM = 1, UNSPECIFIED = 2 };
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <simpleble/export.h>
|
||||||
|
|
||||||
|
#include <simpleble/Types.h>
|
||||||
|
|
||||||
|
namespace SimpleBLE {
|
||||||
|
|
||||||
|
OperatingSystem SIMPLEBLE_EXPORT get_operating_system();
|
||||||
|
|
||||||
|
std::string SIMPLEBLE_EXPORT get_simpleble_version();
|
||||||
|
|
||||||
|
} // namespace SimpleBLE
|
||||||
Vendored
+9370
File diff suppressed because it is too large
Load Diff
+1381
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user