
High-level API for discovering, connecting to and exchanging messages with Bluetooth mesh companion devices, featuring scanning with filters, reliable acknowledgements, auto-sync time, channel polling and message streams.
A Kotlin Multiplatform MeshCore Library.
Provides high-level, ergonomic access to to send and receive data via Bluetooth MeshCore Companions.
open project in Android Studio and run the sample app
open 'sample/iosApp/iosApp.xcodeproj' in Xcode and run the sample app
Add the dependency to your build.gradle file:
implementation("com.darkrockstudios:meshcore:0.9.3")Create a BlueFalconBleAdapter with a platform-specific BlueFalcon instance, then pass it to DeviceScanner:
// Android
val blueFalcon = BlueFalcon(context = application)
val scanner = DeviceScanner(BlueFalconBleAdapter(blueFalcon))
// iOS
val blueFalcon = BlueFalcon(context = UIApplication.sharedApplication)
val scanner = DeviceScanner(BlueFalconBleAdapter(blueFalcon))// Start scanning for MeshCore companion devices
scanner.startScan(scope = coroutineScope)
// Observe discovered devices via StateFlow
scanner.discoveredDevices.collect { devices ->
devices.forEach { device ->
println("Found: ${device.name} (${device.identifier}) rssi=${device.rssi}")
}
}
// Optionally filter by name prefix
scanner.startScan(
filter = ScanFilter(namePrefix = "MeshCore"),
scope = coroutineScope,
)
// Stop scanning when done
scanner.stopScan() val connection = scanner.connect(
device = selectedDevice,
scope = coroutineScope,
config = ConnectionConfig(
appName = "MyApp",
autoSyncTime = true,
autoFetchChannels = true,
autoPollMessages = true,
),
)
// Observe connection state
connection.connectionState.collect { state ->
when (state) {
is ConnectionState.Connected -> println("Connected!")
is ConnectionState.Connecting -> println("Connecting...")
is ConnectionState.Disconnected -> println("Disconnected")
is ConnectionState.Error -> println("Error: ${state.message}")
}
} // Send a message on a channel
val confirmation = connection.sendChannelMessage(
channelIndex = 0,
text = "Hello mesh network!",
)
println("Sent! Ack expected: ${confirmation.expectedAck}")
// Wait for the acknowledgment
connection.acks.first { it == confirmation.expectedAck }
println("Message acknowledged!") // Incoming messages are automatically polled and emitted
connection.incomingMessages.collect { message ->
when (message) {
is ReceivedMessage.ChannelMessage -> {
println("[Channel ${message.channelIndex}] ${message.text}")
}
is ReceivedMessage.ContactMessage -> {
println("[DM from ${message.publicKeyPrefix}] ${message.text}")
}
}
} Send and receive arbitrary binary payloads over the mesh, useful for custom application protocols.
// Send raw data (broadcast/flood) with an optional path
connection.sendRawData(payload = byteArrayOf(0x01, 0x02, 0x03))
// Listen for incoming raw data from the mesh
connection.incomingRawData.collect { rawData ->
println("Raw data received (SNR: ${rawData.snr}, RSSI: ${rawData.rssi})")
println("Payload: ${rawData.payload.size} bytes")
}Send binary data to a specific contact by public key, with correlated responses.
// Send a binary request to a specific contact
val confirmation = connection.sendBinaryRequest(
publicKey = contactPublicKey, // 32-byte public key
requestData = myRequestPayload,
)
println("Binary request sent, tag: ${confirmation.expectedAck}")
// Listen for binary responses (correlated by tag)
connection.incomingBinaryResponses.collect { response ->
println("Binary response for tag=${response.tag}: ${response.responseData.size} bytes")
}// Device info is fetched automatically on connect
val info = connection.deviceInfo.value
println("Firmware: ${info?.firmwareBuild} | Model: ${info?.model}")
// Battery
val battery = connection.getBattery()
println("Battery: ${battery.levelPercent}%")
// Radio stats
val radio = connection.getRadioStats()
println("RSSI: ${radio.lastRssiDbm} dBm, SNR: ${radio.lastSnrDb} dB") // Channels are auto-fetched on connect, observe via StateFlow
val channels = connection.channels.value
// Or fetch manually
val allChannels = connection.getAllChannels()
allChannels.forEach { ch ->
println("Channel ${ch.index}: ${ch.name} (empty=${ch.isEmpty})")
} connection.disconnect() A Kotlin Multiplatform MeshCore Library.
Provides high-level, ergonomic access to to send and receive data via Bluetooth MeshCore Companions.
open project in Android Studio and run the sample app
open 'sample/iosApp/iosApp.xcodeproj' in Xcode and run the sample app
Add the dependency to your build.gradle file:
implementation("com.darkrockstudios:meshcore:0.9.3")Create a BlueFalconBleAdapter with a platform-specific BlueFalcon instance, then pass it to DeviceScanner:
// Android
val blueFalcon = BlueFalcon(context = application)
val scanner = DeviceScanner(BlueFalconBleAdapter(blueFalcon))
// iOS
val blueFalcon = BlueFalcon(context = UIApplication.sharedApplication)
val scanner = DeviceScanner(BlueFalconBleAdapter(blueFalcon))// Start scanning for MeshCore companion devices
scanner.startScan(scope = coroutineScope)
// Observe discovered devices via StateFlow
scanner.discoveredDevices.collect { devices ->
devices.forEach { device ->
println("Found: ${device.name} (${device.identifier}) rssi=${device.rssi}")
}
}
// Optionally filter by name prefix
scanner.startScan(
filter = ScanFilter(namePrefix = "MeshCore"),
scope = coroutineScope,
)
// Stop scanning when done
scanner.stopScan() val connection = scanner.connect(
device = selectedDevice,
scope = coroutineScope,
config = ConnectionConfig(
appName = "MyApp",
autoSyncTime = true,
autoFetchChannels = true,
autoPollMessages = true,
),
)
// Observe connection state
connection.connectionState.collect { state ->
when (state) {
is ConnectionState.Connected -> println("Connected!")
is ConnectionState.Connecting -> println("Connecting...")
is ConnectionState.Disconnected -> println("Disconnected")
is ConnectionState.Error -> println("Error: ${state.message}")
}
} // Send a message on a channel
val confirmation = connection.sendChannelMessage(
channelIndex = 0,
text = "Hello mesh network!",
)
println("Sent! Ack expected: ${confirmation.expectedAck}")
// Wait for the acknowledgment
connection.acks.first { it == confirmation.expectedAck }
println("Message acknowledged!") // Incoming messages are automatically polled and emitted
connection.incomingMessages.collect { message ->
when (message) {
is ReceivedMessage.ChannelMessage -> {
println("[Channel ${message.channelIndex}] ${message.text}")
}
is ReceivedMessage.ContactMessage -> {
println("[DM from ${message.publicKeyPrefix}] ${message.text}")
}
}
} Send and receive arbitrary binary payloads over the mesh, useful for custom application protocols.
// Send raw data (broadcast/flood) with an optional path
connection.sendRawData(payload = byteArrayOf(0x01, 0x02, 0x03))
// Listen for incoming raw data from the mesh
connection.incomingRawData.collect { rawData ->
println("Raw data received (SNR: ${rawData.snr}, RSSI: ${rawData.rssi})")
println("Payload: ${rawData.payload.size} bytes")
}Send binary data to a specific contact by public key, with correlated responses.
// Send a binary request to a specific contact
val confirmation = connection.sendBinaryRequest(
publicKey = contactPublicKey, // 32-byte public key
requestData = myRequestPayload,
)
println("Binary request sent, tag: ${confirmation.expectedAck}")
// Listen for binary responses (correlated by tag)
connection.incomingBinaryResponses.collect { response ->
println("Binary response for tag=${response.tag}: ${response.responseData.size} bytes")
}// Device info is fetched automatically on connect
val info = connection.deviceInfo.value
println("Firmware: ${info?.firmwareBuild} | Model: ${info?.model}")
// Battery
val battery = connection.getBattery()
println("Battery: ${battery.levelPercent}%")
// Radio stats
val radio = connection.getRadioStats()
println("RSSI: ${radio.lastRssiDbm} dBm, SNR: ${radio.lastSnrDb} dB") // Channels are auto-fetched on connect, observe via StateFlow
val channels = connection.channels.value
// Or fetch manually
val allChannels = connection.getAllChannels()
allChannels.forEach { ch ->
println("Channel ${ch.index}: ${ch.name} (empty=${ch.isEmpty})")
} connection.disconnect()