diff --git a/.gitignore b/.gitignore index b37ecce25..89963ebe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ docs/doxydocs .development +_codeql_detected_source_root diff --git a/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino b/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino new file mode 100644 index 000000000..cd9e7ca60 --- /dev/null +++ b/examples/NimBLE_Stream_Client/NimBLE_Stream_Client.ino @@ -0,0 +1,215 @@ +/** + * NimBLE_Stream_Client Example: + * + * Demonstrates using NimBLEStreamClient to connect to a BLE GATT server + * and communicate using the Arduino Stream interface. + * + * This allows you to use familiar methods like print(), println(), + * read(), and available() over BLE, similar to how you would use Serial. + * + * This example connects to the NimBLE_Stream_Server example. + * + * Created: November 2025 + * Author: NimBLE-Arduino Contributors + */ + +#include +#include +#include + +// Service and Characteristic UUIDs (must match the server) +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" + +// Create the stream client instance +NimBLEStreamClient bleStream; + +// Connection state variables +static bool doConnect = false; +static bool connected = false; +static const NimBLEAdvertisedDevice* pServerDevice = nullptr; +static NimBLEClient* pClient = nullptr; + +/** Scan callbacks to find the server */ +class ScanCallbacks : public NimBLEScanCallbacks { + void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override { + Serial.printf("Advertised Device: %s\n", advertisedDevice->toString().c_str()); + + // Check if this device advertises our service + if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) { + Serial.println("Found our stream server!"); + pServerDevice = advertisedDevice; + NimBLEDevice::getScan()->stop(); + doConnect = true; + } + } + + void onScanEnd(const NimBLEScanResults& results, int reason) override { + Serial.println("Scan ended"); + if (!doConnect && !connected) { + Serial.println("Server not found, restarting scan..."); + NimBLEDevice::getScan()->start(5, false, true); + } + } +} scanCallbacks; + +/** Client callbacks for connection/disconnection events */ +class ClientCallbacks : public NimBLEClientCallbacks { + void onConnect(NimBLEClient* pClient) override { + Serial.println("Connected to server"); + // Update connection parameters for better throughput + pClient->updateConnParams(12, 24, 0, 200); + } + + void onDisconnect(NimBLEClient* pClient, int reason) override { + Serial.printf("Disconnected from server, reason: %d\n", reason); + connected = false; + bleStream.deinit(); + + // Restart scanning + Serial.println("Restarting scan..."); + NimBLEDevice::getScan()->start(5, false, true); + } +} clientCallbacks; + +/** Connect to the BLE Server and set up the stream */ +bool connectToServer() { + Serial.printf("Connecting to: %s\n", pServerDevice->getAddress().toString().c_str()); + + // Create or reuse a client + pClient = NimBLEDevice::getClientByPeerAddress(pServerDevice->getAddress()); + if (!pClient) { + pClient = NimBLEDevice::createClient(); + if (!pClient) { + Serial.println("Failed to create client"); + return false; + } + pClient->setClientCallbacks(&clientCallbacks, false); + pClient->setConnectionParams(12, 24, 0, 200); + pClient->setConnectTimeout(5000); + } + + // Connect to the remote BLE Server + if (!pClient->connect(pServerDevice)) { + Serial.println("Failed to connect to server"); + return false; + } + + Serial.println("Connected! Discovering services..."); + + // Get the service and characteristic + NimBLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID); + if (!pRemoteService) { + Serial.println("Failed to find our service UUID"); + pClient->disconnect(); + return false; + } + Serial.println("Found the stream service"); + + NimBLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID); + if (!pRemoteCharacteristic) { + Serial.println("Failed to find our characteristic UUID"); + pClient->disconnect(); + return false; + } + Serial.println("Found the stream characteristic"); + + /** + * Initialize the stream client with the remote characteristic + * subscribeNotify=true means we'll receive notifications in the RX buffer + */ + if (!bleStream.init(pRemoteCharacteristic, true)) { + Serial.println("Failed to initialize BLE stream!"); + pClient->disconnect(); + return false; + } + + /** Start the stream (begins internal buffers and tasks) */ + if (!bleStream.begin()) { + Serial.println("Failed to start BLE stream!"); + bleStream.deinit(); + pClient->disconnect(); + return false; + } + + Serial.println("BLE Stream initialized successfully!"); + connected = true; + return true; +} + +void setup() { + Serial.begin(115200); + Serial.println("Starting NimBLE Stream Client"); + + /** Initialize NimBLE */ + NimBLEDevice::init("NimBLE-StreamClient"); + + /** + * Create the BLE scan instance and set callbacks + * Configure scan parameters + */ + NimBLEScan* pScan = NimBLEDevice::getScan(); + pScan->setScanCallbacks(&scanCallbacks, false); + pScan->setInterval(100); + pScan->setWindow(99); + pScan->setActiveScan(true); + + /** Start scanning for the server */ + Serial.println("Scanning for BLE Stream Server..."); + pScan->start(5, false, true); +} + +void loop() { + // If we found a server, try to connect + if (doConnect) { + doConnect = false; + if (connectToServer()) { + Serial.println("Stream ready for communication!"); + } else { + Serial.println("Failed to connect to server"); + // Scan will restart automatically + } + } + + // If we're connected, demonstrate the stream interface + if (connected && bleStream) { + + // Check if we received any data from the server + if (bleStream.available()) { + Serial.print("Received from server: "); + + // Read all available data (just like Serial.read()) + while (bleStream.available()) { + char c = bleStream.read(); + Serial.write(c); + } + Serial.println(); + } + + // Send a message every 5 seconds using Stream methods + static unsigned long lastSend = 0; + if (millis() - lastSend > 5000) { + lastSend = millis(); + + // Using familiar Serial-like methods! + bleStream.print("Hello from client! Uptime: "); + bleStream.print(millis() / 1000); + bleStream.println(" seconds"); + + Serial.println("Sent data to server via BLE stream"); + } + + // You can also read from Serial and send over BLE + if (Serial.available()) { + Serial.println("Reading from Serial and sending via BLE:"); + while (Serial.available()) { + char c = Serial.read(); + Serial.write(c); // Echo locally + bleStream.write(c); // Send via BLE + } + Serial.println(); + } + } + + delay(10); +} diff --git a/examples/NimBLE_Stream_Client/README.md b/examples/NimBLE_Stream_Client/README.md new file mode 100644 index 000000000..5b91330fb --- /dev/null +++ b/examples/NimBLE_Stream_Client/README.md @@ -0,0 +1,53 @@ +# NimBLE Stream Client Example + +This example demonstrates how to use the `NimBLEStreamClient` class to connect to a BLE GATT server and communicate using the familiar Arduino Stream interface. + +## Features + +- Uses Arduino Stream interface (print, println, read, available, etc.) +- Automatic server discovery and connection +- Bidirectional communication +- Buffered TX/RX using FreeRTOS ring buffers +- Automatic reconnection on disconnect +- Similar usage to Serial communication + +## How it Works + +1. Scans for BLE devices advertising the target service UUID +2. Connects to the server and discovers the stream characteristic +3. Initializes `NimBLEStreamClient` with the remote characteristic +4. Subscribes to notifications to receive data in the RX buffer +5. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()` + +## Usage + +1. First, upload the NimBLE_Stream_Server example to one ESP32 +2. Upload this client sketch to another ESP32 +3. The client will automatically: + - Scan for the server + - Connect when found + - Set up the stream interface + - Begin bidirectional communication +4. You can also type in the Serial monitor to send data to the server + +## Service UUIDs + +Must match the server: +- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` +- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` + +## Serial Monitor Output + +The example displays: +- Server discovery progress +- Connection status +- All data received from the server +- Confirmation of data sent to the server + +## Testing + +Run with NimBLE_Stream_Server to see bidirectional communication: +- Server sends periodic status messages +- Client sends periodic uptime messages +- Both echo data received from each other +- You can send data from either Serial monitor diff --git a/examples/NimBLE_Stream_Echo/NimBLE_Stream_Echo.ino b/examples/NimBLE_Stream_Echo/NimBLE_Stream_Echo.ino new file mode 100644 index 000000000..66d818e43 --- /dev/null +++ b/examples/NimBLE_Stream_Echo/NimBLE_Stream_Echo.ino @@ -0,0 +1,52 @@ +/** + * NimBLE_Stream_Echo Example: + * + * A minimal example demonstrating NimBLEStreamServer. + * Echoes back any data received from BLE clients. + * + * This is the simplest way to use the NimBLE Stream interface, + * showing just the essential setup and read/write operations. + * + * Created: November 2025 + * Author: NimBLE-Arduino Contributors + */ + +#include +#include +#include + +NimBLEStreamServer bleStream; + +void setup() { + Serial.begin(115200); + Serial.println("NimBLE Stream Echo Server"); + + // Initialize BLE + NimBLEDevice::init("BLE-Echo"); + NimBLEDevice::createServer(); + + // Initialize stream with default UUIDs, allow writes + bleStream.init(NimBLEUUID(uint16_t(0xc0de)), // Service UUID + NimBLEUUID(uint16_t(0xfeed)), // Characteristic UUID + true, // canWrite + false); // secure + bleStream.begin(); + + // Start advertising + NimBLEDevice::getAdvertising()->start(); + Serial.println("Ready! Connect with a BLE client and send data."); +} + +void loop() { + // Echo any received data back to the client + if (bleStream.hasSubscriber() && bleStream.available()) { + Serial.print("Echo: "); + while (bleStream.available()) { + char c = bleStream.read(); + Serial.write(c); + bleStream.write(c); // Echo back + } + Serial.println(); + } + delay(10); +} diff --git a/examples/NimBLE_Stream_Echo/README.md b/examples/NimBLE_Stream_Echo/README.md new file mode 100644 index 000000000..4a80224d0 --- /dev/null +++ b/examples/NimBLE_Stream_Echo/README.md @@ -0,0 +1,39 @@ +# NimBLE Stream Echo Example + +This is the simplest example demonstrating `NimBLEStreamServer`. It echoes back any data received from BLE clients. + +## Features + +- Minimal code showing essential NimBLE Stream usage +- Echoes all received data back to the client +- Uses default service and characteristic UUIDs +- Perfect starting point for learning the Stream interface + +## How it Works + +1. Initializes BLE with minimal configuration +2. Creates a stream server with default UUIDs +3. Waits for client connection and data +4. Echoes received data back to the client +5. Displays received data on Serial monitor + +## Default UUIDs + +- Service: `0xc0de` +- Characteristic: `0xfeed` + +## Usage + +1. Upload this sketch to your ESP32 +2. Connect with a BLE client app (nRF Connect, Serial Bluetooth Terminal, etc.) +3. Find the service `0xc0de` and characteristic `0xfeed` +4. Subscribe to notifications +5. Write data to the characteristic +6. The data will be echoed back and displayed in Serial monitor + +## Good For + +- Learning the basic NimBLE Stream API +- Testing BLE connectivity +- Starting point for custom applications +- Understanding Stream read/write operations diff --git a/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino b/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino new file mode 100644 index 000000000..6257c54ea --- /dev/null +++ b/examples/NimBLE_Stream_Server/NimBLE_Stream_Server.ino @@ -0,0 +1,130 @@ +/** + * NimBLE_Stream_Server Example: + * + * Demonstrates using NimBLEStreamServer to create a BLE GATT server + * that behaves like a serial port using the Arduino Stream interface. + * + * This allows you to use familiar methods like print(), println(), + * read(), and available() over BLE, similar to how you would use Serial. + * + * Created: November 2025 + * Author: NimBLE-Arduino Contributors + */ + +#include +#include +#include + +// Create the stream server instance +NimBLEStreamServer bleStream; + +// Service and Characteristic UUIDs for the stream +// Using custom UUIDs - you can change these as needed +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" + +/** Server callbacks to handle connection/disconnection events */ +class ServerCallbacks : public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override { + Serial.printf("Client connected: %s\n", connInfo.getAddress().toString().c_str()); + // Optionally update connection parameters for better throughput + pServer->updateConnParams(connInfo.getConnHandle(), 12, 24, 0, 200); + } + + void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override { + Serial.printf("Client disconnected - reason: %d, restarting advertising\n", reason); + NimBLEDevice::startAdvertising(); + } + + void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) override { + Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, connInfo.getConnHandle()); + } +} serverCallbacks; + +void setup() { + Serial.begin(115200); + Serial.println("Starting NimBLE Stream Server"); + + /** Initialize NimBLE and set the device name */ + NimBLEDevice::init("NimBLE-Stream"); + + /** + * Create the BLE server and set callbacks + * Note: The stream will create its own service and characteristic + */ + NimBLEServer* pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(&serverCallbacks); + + /** + * Initialize the stream server with: + * - Service UUID + * - Characteristic UUID + * - canWrite: true (allows clients to write data to us) + * - secure: false (no encryption required - set to true for secure connections) + */ + if (!bleStream.init(NimBLEUUID(SERVICE_UUID), + NimBLEUUID(CHARACTERISTIC_UUID), + true, // canWrite - allow receiving data + false)) // secure + { + Serial.println("Failed to initialize BLE stream!"); + return; + } + + /** Start the stream (begins internal buffers and tasks) */ + if (!bleStream.begin()) { + Serial.println("Failed to start BLE stream!"); + return; + } + + /** + * Create advertising instance and add service UUID + * This makes the stream service discoverable + */ + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setName("NimBLE-Stream"); + pAdvertising->enableScanResponse(true); + pAdvertising->start(); + + Serial.println("BLE Stream Server ready!"); + Serial.println("Waiting for client connection..."); + Serial.println("Once connected, you can send data from the client."); +} + +void loop() { + // Check if a client is subscribed (connected and listening) + if (bleStream.hasSubscriber()) { + + // Send a message every 2 seconds using Stream methods + static unsigned long lastSend = 0; + if (millis() - lastSend > 2000) { + lastSend = millis(); + + // Using familiar Serial-like methods! + bleStream.print("Hello from BLE Server! Time: "); + bleStream.println(millis()); + + // You can also use printf + bleStream.printf("Free heap: %d bytes\n", ESP.getFreeHeap()); + + Serial.println("Sent data to client via BLE stream"); + } + + // Check if we received any data from the client + if (bleStream.available()) { + Serial.print("Received from client: "); + + // Read all available data (just like Serial.read()) + while (bleStream.available()) { + char c = bleStream.read(); + Serial.write(c); // Echo to Serial + bleStream.write(c); // Echo back to BLE client + } + Serial.println(); + } + } else { + // No subscriber, just wait + delay(100); + } +} diff --git a/examples/NimBLE_Stream_Server/README.md b/examples/NimBLE_Stream_Server/README.md new file mode 100644 index 000000000..5e0a582c7 --- /dev/null +++ b/examples/NimBLE_Stream_Server/README.md @@ -0,0 +1,42 @@ +# NimBLE Stream Server Example + +This example demonstrates how to use the `NimBLEStreamServer` class to create a BLE GATT server that behaves like a serial port using the familiar Arduino Stream interface. + +## Features + +- Uses Arduino Stream interface (print, println, read, available, etc.) +- Automatic connection management +- Bidirectional communication +- Buffered TX/RX using FreeRTOS ring buffers +- Similar usage to Serial communication + +## How it Works + +1. Creates a BLE GATT server with a custom service and characteristic +2. Initializes `NimBLEStreamServer` with the characteristic configured for notifications and writes +3. Uses familiar Stream methods like `print()`, `println()`, `read()`, and `available()` +4. Automatically handles connection state and MTU negotiation + +## Usage + +1. Upload this sketch to your ESP32 +2. The device will advertise as "NimBLE-Stream" +3. Connect with a BLE client (such as the NimBLE_Stream_Client example or a mobile app) +4. Once connected, the server will: + - Send periodic messages to the client + - Echo back any data received from the client + - Display all communication on the Serial monitor + +## Service UUIDs + +- Service: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` +- Characteristic: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` + +These are based on the Nordic UART Service (NUS) UUIDs for compatibility with many BLE terminal apps. + +## Compatible With + +- NimBLE_Stream_Client example +- nRF Connect mobile app +- Serial Bluetooth Terminal apps +- Any BLE client that supports characteristic notifications and writes diff --git a/examples/STREAM_EXAMPLES.md b/examples/STREAM_EXAMPLES.md new file mode 100644 index 000000000..34f6a9dcc --- /dev/null +++ b/examples/STREAM_EXAMPLES.md @@ -0,0 +1,145 @@ +# NimBLE Stream Examples + +This document describes the new Stream-based examples that demonstrate using BLE characteristics with the familiar Arduino Stream interface. + +## Overview + +The NimBLE Stream classes (`NimBLEStreamServer` and `NimBLEStreamClient`) provide a simple way to use BLE characteristics like serial ports. You can use familiar methods like `print()`, `println()`, `read()`, and `available()` just like you would with `Serial`. + +## Available Examples + +### 1. NimBLE_Stream_Echo +**Difficulty:** Beginner +**Purpose:** Introduction to the Stream API + +This is the simplest example, perfect for getting started. It creates a BLE server that echoes back any data it receives. Uses default UUIDs for quick setup. + +**Key Features:** +- Minimal setup code +- Simple echo functionality +- Good starting point for learning + +**Use Case:** Testing BLE connectivity, learning the basics + +--- + +### 2. NimBLE_Stream_Server +**Difficulty:** Intermediate +**Purpose:** Full-featured server implementation + +A complete BLE server example that demonstrates all the major features of `NimBLEStreamServer`. Shows connection management, bidirectional communication, and proper server setup. + +**Key Features:** +- Custom service and characteristic UUIDs +- Connection callbacks +- Periodic message sending +- Echo functionality +- MTU negotiation +- Connection parameter updates + +**Use Case:** Building custom BLE services, wireless data logging, remote monitoring + +--- + +### 3. NimBLE_Stream_Client +**Difficulty:** Intermediate +**Purpose:** Full-featured client implementation + +Demonstrates how to scan for, connect to, and communicate with a BLE server using `NimBLEStreamClient`. Pairs with the NimBLE_Stream_Server example. + +**Key Features:** +- Automatic server discovery +- Connection management +- Reconnection on disconnect +- Bidirectional communication +- Serial passthrough (type in Serial monitor to send via BLE) + +**Use Case:** Building BLE client applications, data collection from BLE devices + +## Quick Start + +### Running the Echo Example + +1. Upload `NimBLE_Stream_Echo` to your ESP32 +2. Open Serial monitor (115200 baud) +3. Use a BLE app (like nRF Connect) to connect +4. Find service UUID `0xc0de`, characteristic `0xfeed` +5. Subscribe to notifications +6. Write data to see it echoed back + +### Running Server + Client Examples + +1. Upload `NimBLE_Stream_Server` to one ESP32 +2. Upload `NimBLE_Stream_Client` to another ESP32 +3. Open Serial monitors for both (115200 baud) +4. Client will automatically find and connect to server +5. Observe bidirectional communication +6. Type in either Serial monitor to send data + +## Common Use Cases + +- **Wireless Serial Communication:** Replace physical serial connections with BLE +- **Data Logging:** Stream sensor data to a mobile device or another microcontroller +- **Remote Control:** Send commands between devices using familiar print/println +- **Debugging:** Use BLE as a wireless alternative to USB serial debugging +- **IoT Applications:** Simple data exchange between ESP32 devices + +## Stream Interface Methods + +The examples demonstrate these familiar methods: + +**Output (Sending Data):** +- `print(value)` - Print data without newline +- `println(value)` - Print data with newline +- `printf(format, ...)` - Formatted output +- `write(data)` - Write raw bytes + +**Input (Receiving Data):** +- `available()` - Check if data is available +- `read()` - Read a single byte +- `peek()` - Preview next byte without removing it + +**Status:** +- `hasSubscriber()` - Check if a client is connected (server only) +- `ready()` or `operator bool()` - Check if stream is ready + +## Configuration + +Both server and client support configuration before calling `begin()`: + +```cpp +bleStream.setTxBufSize(2048); // TX buffer size +bleStream.setRxBufSize(2048); // RX buffer size +bleStream.setTxTaskStackSize(4096); // Task stack size +bleStream.setTxTaskPriority(1); // Task priority +``` + +## Compatibility + +These examples work with: +- ESP32 (all variants: ESP32, ESP32-C3, ESP32-S3, etc.) +- Nordic chips (with n-able Arduino core) +- Any BLE client supporting GATT characteristics with notifications + +## Additional Resources + +- [Arduino Stream Reference](https://www.arduino.cc/reference/en/language/functions/communication/stream/) +- [NimBLE-Arduino Documentation](https://h2zero.github.io/NimBLE-Arduino/) +- Each example includes a detailed README.md file + +## Troubleshooting + +**Client can't find server:** +- Check that both devices are powered on +- Verify UUIDs match between server and client +- Ensure server advertising is started + +**Data not received:** +- Verify client has subscribed to notifications +- Check that buffers aren't full (increase buffer sizes) +- Ensure both `init()` and `begin()` were called + +**Connection drops:** +- Check for interference +- Reduce connection interval if needed +- Ensure devices are within BLE range diff --git a/src/NimBLEStream.cpp b/src/NimBLEStream.cpp index b3439c73a..6dbb4ba0f 100644 --- a/src/NimBLEStream.cpp +++ b/src/NimBLEStream.cpp @@ -26,6 +26,8 @@ static const char* LOG_TAG = "NimBLEStream"; // Stub Print/Stream implementations when Arduino not available # if !NIMBLE_CPP_ARDUINO_STRING_AVAILABLE +# include + size_t Print::print(const char* s) { if (!s) return 0; return write(reinterpret_cast(s), strlen(s)); @@ -245,7 +247,7 @@ size_t NimBLEStream::pushRx(const uint8_t* data, size_t len) { m_hasPeek = false; if (xRingbufferSend(m_rxBuf, data, len, 0) != pdTRUE) { - NIMBLE_UART_LOGE(LOG_TAG, "RX buffer full, dropping %u bytes", len); + NIMBLE_UART_LOGE(LOG_TAG, "RX buffer full, dropping %zu bytes", len); return 0; } return len; @@ -481,7 +483,7 @@ int uart_log_printfv(const char* format, va_list arg) { free(temp); } - return len; + return wlen; } int uart_log_printf(const char* format, ...) { diff --git a/src/NimBLEStream.h b/src/NimBLEStream.h index e69168203..598765ffb 100644 --- a/src/NimBLEStream.h +++ b/src/NimBLEStream.h @@ -187,7 +187,7 @@ class NimBLEStreamClient : public NimBLEStream { # endif // CONFIG_BT_NIMBLE_ENABLED && (MYNEWT_VAL(BLE_ROLE_PERIPHERAL) || MYNEWT_VAL(BLE_ROLE_CENTRAL)) -// These logging macros exist to provide log output over UART so that it stream classes can +// These logging macros exist to provide log output over UART so that the stream classes can // be used to redirect logs without causing recursion issues. static int uart_log_printfv(const char* format, va_list arg); static int uart_log_printf(const char* format, ...);