Hublink is an Arduino library designed to facilitate Bluetooth Low Energy (BLE) communication and SD card file transfer for ESP32-based nodes. It provides a simple interface for periodic BLE advertising and file transfer capabilities.
Learn more at https://hublink.cloud.
Manages BLE advertising and connection cycles. Returns true if a connection was established.
temporaryConnectFor: Optional duration in seconds to override the default advertising period- Returns: boolean indicating if connection was successful
Example:
// Use default advertising period from meta.json
bool success = hublink.sync();
// Override with 60-second advertising period
bool success = hublink.sync(60);Puts the ESP32 into light sleep mode for the specified duration.
seconds: Duration to sleep in seconds- Returns: void
Example:
// Sleep for 5 seconds
hublink.sleep(5);Sets the battery level for the node characteristic. Range 0-255 (0 indicates not set).
level: Battery level (0-255)
Example:
hublink.setBatteryLevel(85); // Set to 85%Sets an alert message that will be included in the node characteristic during the next sync cycle. The alert is automatically cleared after the sync completes.
alert: Alert message string
Example:
hublink.setAlert("Low battery warning!");
hublink.sync(); // Alert gets sent and then automatically clearedReturns the current battery level setting.
- Returns: uint8_t battery level (0-255)
Returns the current alert message.
- Returns: String alert message
The begin() function initializes the Hublink node with the following sequence:
-
Debug Setup (if enabled)
- Initializes Serial1 at 115200 baud for debug output
- Logs wake-up reason (timer, reset, or other)
-
CPU Configuration
- Sets CPU frequency to 80MHz (minimum required for radio operation)
-
SD Card Initialization
- Attempts to initialize SD card with specified chip select and clock frequency
- Returns false if SD card initialization fails
-
BLE Configuration
- Sets advertising name from parameter
- Initializes default values:
advertise_every: 300 seconds (5 minutes)advertise_for: 30 secondsdisable: falseupload_path: "/FED"append_path: "subject:id/experimenter:name"try_reconnect: truereconnect_attempts: 3reconnect_every: 30 seconds
-
Meta.json Processing
- Reads and parses meta.json from SD card
- Updates configuration values if valid JSON is found
- Maintains default values if file is missing or invalid
-
State Initialization
- Sets lastHublinkMillis for timing control
- Returns true if all initialization steps complete successfully
Example:
Hublink hublink(SD_CS_PIN, SD_CLK_FREQ);
if (hublink.begin("HUBLINK")) {
Serial.println("Hublink initialized successfully");
} else {
Serial.println("Hublink initialization failed");
}- Download the Hublink-Node library in Arduino IDE. Alternatively (but not recommended), clone the GitHub repository.
- Ensure the dependencies are installed from Arduino IDE:
- NimBLE-Arduino
- ArduinoJson
- If the device has been previously flashed, it may be in a deep sleep state and will not connect to the serial port. To enter boot mode, hold the
Bootbutton and toggle theResetbutton (then release theBootbutton). - If the Arduino IDE does not indicate that it is connected to "Adafruit Feather ESP32-S3 2MB PSRAM", click Tools -> Board -> esp32 -> Adafruit Feather ESP32-S3 2MB PSRAM (you will need to download the esp32 board package (by espressif) from the Arduino IDE).
- Install the ESP32 board package in Arduino IDE following the official guide
- An ESP32-S3 board with an SD card module (View Pricing)
For a complete working example, refer to the examples/ directory. Files on the SD card are treated as accumulators and Hublink uses file size as a "diff" proxy (i.e., if the number of bytes in a file changes at all, Hublink will attempt to transfer the file).
The library accepts configuration via a file on the SD card in JSON format.
{
"hublink": {
"advertise": "HUBLINK",
"advertise_every": 300,
"advertise_for": 30,
"try_reconnect": true,
"reconnect_attempts": 3,
"reconnect_every": 30,
"upload_path": "/FED",
"append_path": "subject:id/experimenter:name",
"disable": false
},
"subject": {
"id": "mouse001",
"strain": "C57BL/6",
"strain_options": [
"C57BL/6",
"BALB/c",
"129S1/SvImJ",
"F344",
"Long Evans",
"Sprague Dawley"
],
"sex": "male",
"sex_options": [
"male",
"female"
]
},
"experimenter": {
"name": "john_doe"
},
"device": {
"id": "046"
},
"fed": {
"program": "Classic",
"program_options": [
"Classic",
"Intense",
"Minimal",
"Custom"
]
}
}Where,
advertise: Sets custom BLE advertising nameadvertise_every: Seconds between advertising periodsadvertise_for: Duration of each advertising period in secondstry_reconnect: Enables/disables automatic reconnection attempts (default: true)reconnect_attempts: Number of reconnection attempts if initial connection fails (default: 3)reconnect_every: Seconds between reconnection attempts (default: 30)upload_path: Base path for file uploads (e.g., "/FED")append_path: Dynamic path segments using nested JSON values (e.g., "subject:id/experimenter:name")disable: Enables/disables BLE functionalitydevice.id: Device identifier that appears in the node characteristic
The append_path field supports multiple nested JSON values separated by forward slashes. For example:
-
Single value:
"append_path": "subject:id"- Result:
/FED/mouse001
- Result:
-
Multiple values:
"append_path": "subject:id/experimenter:name"- Result:
/FED/mouse001/john_doe
- Result:
The path construction:
- Starts with
upload_path - Appends each value specified in
append_pathif it exists and is not empty - Skips any missing or empty values
- Sanitizes the path to ensure it's valid for S3 storage:
- Allows alphanumeric characters (a-z, A-Z, 0-9)
- Allows hyphen (-), underscore (_), plus (+), period (.)
- Removes duplicate slashes
- Removes trailing slashes
For example, if experimenter:name is missing or empty, the path would be /FED/mouse001 instead of /FED/mouse001/.
Hublink uses bblanchon/ArduinoJson to parse the JSON file. There are a number of free JSON editors/visualizers (e.g., JSON to Graph Converter).
The Hublink library implements a custom BLE service with four characteristics for file transfer and device management. All characteristics use the service UUID: 57617368-5501-0001-8000-00805f9b34fb
UUID: 57617368-5505-0001-8000-00805f9b34fb
Provides device status and configuration information in JSON format:
{
"upload_path": "/FED",
"firmware_version": "1.0.6",
"battery_level": 85,
"device_id": "046",
"alert": "Low battery warning!"
}Fields:
upload_path(string): Base path for file uploads, configured via meta.jsonfirmware_version(string): Current library version (always present)battery_level(number): Battery level 0-255 (0 = not set, only present if > 0)device_id(string): Device identifier from meta.json (only present if configured)alert(string): Alert message (only present if set by user, auto-clears after sync)
Usage: Read this characteristic after connection to get device information and status.
UUID: 57617368-5504-0001-8000-00805f9b34fb
Accepts JSON commands to control device behavior. Multiple commands can be sent in a single JSON object:
{
"timestamp": 1234567890,
"sendFilenames": true,
"watchdogTimeoutMs": 10000,
"metaJsonId": 1,
"metaJsonData": "{\"hublink\":{\"advertise\":\"HUBLINK\"}}"
}Commands:
timestamp(number): Unix timestamp for device synchronizationsendFilenames(boolean): Triggers file listing process when truewatchdogTimeoutMs(number): Sets connection timeout in milliseconds (default: 10000)metaJsonId+metaJsonData(pair): For meta.json updates (see Meta.json Transfer section)
Usage: Write JSON commands to control device behavior. Device responds via callbacks.
UUID: 57617368-5502-0001-8000-00805f9b34fb
WRITE: Send filename to request file transfer
"data.txt"
INDICATE: Receives file listing or transfer status
- File listing format:
"filename1.txt|1234;filename2.csv|5678;EOF"- Each file:
"filename|filesize" - Separator:
; - End marker:
"EOF"
- Each file:
- Transfer status:
"NFF"(No File Found) if requested file doesn't exist
Usage:
- Write filename to request transfer
- Subscribe to indications for file listing or status updates
UUID: 57617368-5503-0001-8000-00805f9b34fb
INDICATE: Receives file content in chunks
- Data chunks: Raw file bytes (MTU-sized, typically 512 bytes)
- End marker:
"EOF"when transfer complete - Error marker:
"NFF"if file not found
Usage: Subscribe to indications to receive file content. Monitor for "EOF" or "NFF" markers.
- Service UUID:
57617368-5501-0001-8000-00805f9b34fb - Advertising Name: Configurable via meta.json (default: "HUBLINK")
- MTU Size: Device negotiates to 515 bytes (512 + 3 byte header)
- Connect to device
- Read Node Characteristic to get device info
- Subscribe to indications on Filename and File Transfer characteristics
- Write to Gateway Characteristic to send commands
- Write
{"sendFilenames": true}to Gateway Characteristic - Receive file list via Filename Characteristic indications
- Parse
"filename|size;filename2|size2;EOF"format
- Write filename to Filename Characteristic
- Receive file content via File Transfer Characteristic indications
- Monitor for "EOF" or "NFF" markers
- Read Node Characteristic (contains current configuration)
- Send chunks via Gateway Characteristic:
{"metaJsonId": 1, "metaJsonData": "{\"hublink\":{\"advertise\":\"HUBLINK\"}}"} {"metaJsonId": 2, "metaJsonData": "{\"device\":{\"id\":\"046\"}}"} - Send completion marker:
{"metaJsonId": 0, "metaJsonData": "EOF"}
Chunking Rules:
- Sequential IDs starting from 1
- Device validates JSON structure on completion
- Timeout: 5 seconds between chunks
- Automatic cleanup on timeout or error
- Connection timeout: 10 seconds (configurable via
watchdogTimeoutMs) - File not found: Device sends "NFF" via Filename Characteristic
- Transfer errors: Device disconnects and resets state
- Meta.json errors: Device reverts to previous configuration
- Alert messages: Auto-clear after each sync cycle
- Battery level: Persists until next update
- File handles: Automatically closed on disconnect
- BLE state: Reset between advertising cycles
The Hublink Node library supports secure transfer of meta.json configuration between client (mobile app) and ESP32:
Reading meta.json (ESP32 → Client)
- Client requests meta.json content
- ESP32 serves current configuration via NODE characteristic
- Client receives complete JSON in single read
Writing meta.json (Client → ESP32)
- Client divides meta.json into chunks and sends sequentially
- ESP32 validates chunk sequence and writes to temporary file
- On completion, ESP32:
- Validates JSON structure
- Backs up existing meta.json
- Replaces with new content
- Updates BLE configuration
This library is open-source and available under the MIT license.
Contributions are welcome! Please feel free to submit a Pull Request.
- Memory/Resource Cleanup [x] Verify cleanupCallbacks() is called in all exit paths [ ] Add memory tracking at key points in sync() cycle [x] Ensure file handles are properly closed
- BLE State Management [ ] Add state validation before BLE operations [ ] Verify complete deinit between advertising cycles (see below) [x] Review static callback instance lifecycle
- Timing & Deep Sleep [x] Validate timer arithmetic for overflows [x] Add proper delays between BLE operations [ ] Review initialization after deep sleep
- Consider testing at begin(), detecting wake from sleep, or sync():
// Ensure clean BLE state before starting
if (NimBLEDevice::getInitialized()) {
NimBLEDevice::deinit(true);
delay(100);
}- SD Card Operations [ ] Add SD card state validation [ ] Verify SD reinitialization after sleep [x] Review file handle management
- Null Pointer Protection [x] Add null checks in critical paths [x] Validate object state before operations [x] Review pointer lifecycle in callbacks