BLE writing to a characteristic (Android Studio) - android-ble
I'm developing an Android app using Android Studio. The app relies heavily on BLE (Bluetooth Low Energy), and the basic idea is that when a button is pressed, it will turn on a fan connected to an Arduino board. I'm pretty sure that I have the Arduino side of things all good, since I can connect to the Arduino through the Android app, and the Arduino is correctly advertising the services and characteristics. The problem I am having is that I am trying to write to a characteristic when a button is pressed, but I can't seem to be able to figure out how to obtain the current connected device to pass to the method to be able to write to the characteristic.
I am using the code from PunchThrough for all BLE-related tasks. (I have also read their article on BLE basics). Here is a link to their Github repository:
https://github.com/PunchThrough/ble-starter-android/tree/master/app/src/main/java/com/punchthrough/blestarterappandroid
I have confirmed that all their code is working properly in my app: I can scan for BLE devices, connect to one, and view services and characteristics for an associated device.
I have a separate page (activity) for the fan, which basically just has a button that turns the fan on and off. I am trying to do something like this:
public class Fan extends AppCompatActivity{
.
.
.
private BluetoothGatt gatt1;
byte[] charValueAray = new byte[1];
ConnectionManager connectionObject;
public BluetoothDevice deviceCurnt;
protected void onCreate(Bundle savedInstanceState){
.
. //Basic code that creates button and gets the XML activity file
.
public void onClick(View v)
{
if (fanButton.isChecked())
{
charValueArray[0] = 1; //When value is equal to 1, fan will turn on
fanCharacteristic.setValue(charValueArray);
connectionObject.writeCharacteristic(deviceCurnt, fanCharacteristic, charValueArray); //1
//Also tried this: gatt1.writeCharacteristic(fanCharacteristic); 2
}
else
{
//other code
}
}
Whenever I use 1 or 2, the app just crashes and returns to the main menu page of my app. The reason being probably because I am passing in a BluetoothDevice that is basically non existent, and same reason with the gatt1 reference. I have also already come up with a way to disable the button if there is no device connected, and I have confirmed that the app does not crash because of any "Not connected to any device to pass information to" type of error.
I'm guessing that I am also doing something wrong with the character values when using a byte array. I have looked at lots of sample code online about Android BLE, but I can't seem to find a way on how to write to a characteristic from within my own activity page. If someone can help me out to correctly write to a characteristic the right way I would greatly appreciate it. Thanks
Edit:
Here is the class ConnectionManager (by PunchThrough) that I am using to connect to BLE devices:
/*
* Copyright 2019 Punch Through Design LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.punchthrough.blestarterappandroid.ble
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
import timber.log.Timber
import java.lang.ref.WeakReference
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
private const val GATT_MIN_MTU_SIZE = 23
/** Maximum BLE MTU size as defined in gatt_api.h. */
private const val GATT_MAX_MTU_SIZE = 517
object ConnectionManager {
private var listeners: MutableSet<WeakReference<ConnectionEventListener>> = mutableSetOf()
private val deviceGattMap = ConcurrentHashMap<BluetoothDevice, BluetoothGatt>()
private val operationQueue = ConcurrentLinkedQueue<BleOperationType>()
private var pendingOperation: BleOperationType? = null
fun servicesOnDevice(device: BluetoothDevice): List<BluetoothGattService>? =
deviceGattMap[device]?.services
fun listenToBondStateChanges(context: Context) {
context.applicationContext.registerReceiver(
broadcastReceiver,
IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
)
}
fun registerListener(listener: ConnectionEventListener) {
if (listeners.map { it.get() }.contains(listener)) { return }
listeners.add(WeakReference(listener))
listeners = listeners.filter { it.get() != null }.toMutableSet()
Timber.d("Added listener $listener, ${listeners.size} listeners total")
}
fun unregisterListener(listener: ConnectionEventListener) {
// Removing elements while in a loop results in a java.util.ConcurrentModificationException
var toRemove: WeakReference<ConnectionEventListener>? = null
listeners.forEach {
if (it.get() == listener) {
toRemove = it
}
}
toRemove?.let {
listeners.remove(it)
Timber.d("Removed listener ${it.get()}, ${listeners.size} listeners total")
}
}
fun connect(device: BluetoothDevice, context: Context) {
if (device.isConnected()) {
Timber.e("Already connected to ${device.address}!")
} else {
enqueueOperation(Connect(device, context.applicationContext))
}
}
fun teardownConnection(device: BluetoothDevice) {
if (device.isConnected()) {
enqueueOperation(Disconnect(device))
} else {
Timber.e("Not connected to ${device.address}, cannot teardown connection!")
}
}
fun readCharacteristic(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic) {
if (device.isConnected() && characteristic.isReadable()) {
enqueueOperation(CharacteristicRead(device, characteristic.uuid))
} else if (!characteristic.isReadable()) {
Timber.e("Attempting to read ${characteristic.uuid} that isn't readable!")
} else if (!device.isConnected()) {
Timber.e("Not connected to ${device.address}, cannot perform characteristic read")
}
}
fun writeCharacteristic(
device: BluetoothDevice,
characteristic: BluetoothGattCharacteristic,
payload: ByteArray
) {
val writeType = when {
characteristic.isWritable() -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
characteristic.isWritableWithoutResponse() -> {
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
else -> {
Timber.e("Characteristic ${characteristic.uuid} cannot be written to")
return
}
}
if (device.isConnected()) {
enqueueOperation(CharacteristicWrite(device, characteristic.uuid, writeType, payload))
} else {
Timber.e("Not connected to ${device.address}, cannot perform characteristic write")
}
}
fun readDescriptor(device: BluetoothDevice, descriptor: BluetoothGattDescriptor) {
if (device.isConnected() && descriptor.isReadable()) {
enqueueOperation(DescriptorRead(device, descriptor.uuid))
} else if (!descriptor.isReadable()) {
Timber.e("Attempting to read ${descriptor.uuid} that isn't readable!")
} else if (!device.isConnected()) {
Timber.e("Not connected to ${device.address}, cannot perform descriptor read")
}
}
fun writeDescriptor(
device: BluetoothDevice,
descriptor: BluetoothGattDescriptor,
payload: ByteArray
) {
if (device.isConnected() && (descriptor.isWritable() || descriptor.isCccd())) {
enqueueOperation(DescriptorWrite(device, descriptor.uuid, payload))
} else if (!device.isConnected()) {
Timber.e("Not connected to ${device.address}, cannot perform descriptor write")
} else if (!descriptor.isWritable() && !descriptor.isCccd()) {
Timber.e("Descriptor ${descriptor.uuid} cannot be written to")
}
}
fun enableNotifications(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic) {
if (device.isConnected() &&
(characteristic.isIndicatable() || characteristic.isNotifiable())
) {
enqueueOperation(EnableNotifications(device, characteristic.uuid))
} else if (!device.isConnected()) {
Timber.e("Not connected to ${device.address}, cannot enable notifications")
} else if (!characteristic.isIndicatable() && !characteristic.isNotifiable()) {
Timber.e("Characteristic ${characteristic.uuid} doesn't support notifications/indications")
}
}
fun disableNotifications(device: BluetoothDevice, characteristic: BluetoothGattCharacteristic) {
if (device.isConnected() &&
(characteristic.isIndicatable() || characteristic.isNotifiable())
) {
enqueueOperation(DisableNotifications(device, characteristic.uuid))
} else if (!device.isConnected()) {
Timber.e("Not connected to ${device.address}, cannot disable notifications")
} else if (!characteristic.isIndicatable() && !characteristic.isNotifiable()) {
Timber.e("Characteristic ${characteristic.uuid} doesn't support notifications/indications")
}
}
fun requestMtu(device: BluetoothDevice, mtu: Int) {
if (device.isConnected()) {
enqueueOperation(MtuRequest(device, mtu.coerceIn(GATT_MIN_MTU_SIZE, GATT_MAX_MTU_SIZE)))
} else {
Timber.e("Not connected to ${device.address}, cannot request MTU update!")
}
}
// - Beginning of PRIVATE functions
#Synchronized
private fun enqueueOperation(operation: BleOperationType) {
operationQueue.add(operation)
if (pendingOperation == null) {
doNextOperation()
}
}
#Synchronized
private fun signalEndOfOperation() {
Timber.d("End of $pendingOperation")
pendingOperation = null
if (operationQueue.isNotEmpty()) {
doNextOperation()
}
}
/**
* Perform a given [BleOperationType]. All permission checks are performed before an operation
* can be enqueued by [enqueueOperation].
*/
#Synchronized
private fun doNextOperation() {
if (pendingOperation != null) {
Timber.e("doNextOperation() called when an operation is pending! Aborting.")
return
}
val operation = operationQueue.poll() ?: run {
Timber.v("Operation queue empty, returning")
return
}
pendingOperation = operation
// Handle Connect separately from other operations that require device to be connected
if (operation is Connect) {
with(operation) {
Timber.w("Connecting to ${device.address}")
device.connectGatt(context, false, callback)
}
return
}
// Check BluetoothGatt availability for other operations
val gatt = deviceGattMap[operation.device]
?: this#ConnectionManager.run {
Timber.e("Not connected to ${operation.device.address}! Aborting $operation operation.")
signalEndOfOperation()
return
}
// TODO: Make sure each operation ultimately leads to signalEndOfOperation()
// TODO: Refactor this into an BleOperationType abstract or extension function
when (operation) {
is Disconnect -> with(operation) {
Timber.w("Disconnecting from ${device.address}")
gatt.close()
deviceGattMap.remove(device)
listeners.forEach { it.get()?.onDisconnect?.invoke(device) }
signalEndOfOperation()
}
is CharacteristicWrite -> with(operation) {
gatt.findCharacteristic(characteristicUuid)?.let { characteristic ->
characteristic.writeType = writeType
characteristic.value = payload
gatt.writeCharacteristic(characteristic)
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $characteristicUuid to write to")
signalEndOfOperation()
}
}
is CharacteristicRead -> with(operation) {
gatt.findCharacteristic(characteristicUuid)?.let { characteristic ->
gatt.readCharacteristic(characteristic)
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $characteristicUuid to read from")
signalEndOfOperation()
}
}
is DescriptorWrite -> with(operation) {
gatt.findDescriptor(descriptorUuid)?.let { descriptor ->
descriptor.value = payload
gatt.writeDescriptor(descriptor)
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $descriptorUuid to write to")
signalEndOfOperation()
}
}
is DescriptorRead -> with(operation) {
gatt.findDescriptor(descriptorUuid)?.let { descriptor ->
gatt.readDescriptor(descriptor)
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $descriptorUuid to read from")
signalEndOfOperation()
}
}
is EnableNotifications -> with(operation) {
gatt.findCharacteristic(characteristicUuid)?.let { characteristic ->
val cccdUuid = UUID.fromString(CCC_DESCRIPTOR_UUID)
val payload = when {
characteristic.isIndicatable() ->
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
characteristic.isNotifiable() ->
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
else ->
error("${characteristic.uuid} doesn't support notifications/indications")
}
characteristic.getDescriptor(cccdUuid)?.let { cccDescriptor ->
if (!gatt.setCharacteristicNotification(characteristic, true)) {
Timber.e("setCharacteristicNotification failed for ${characteristic.uuid}")
signalEndOfOperation()
return
}
cccDescriptor.value = payload
gatt.writeDescriptor(cccDescriptor)
} ?: this#ConnectionManager.run {
Timber.e("${characteristic.uuid} doesn't contain the CCC descriptor!")
signalEndOfOperation()
}
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $characteristicUuid! Failed to enable notifications.")
signalEndOfOperation()
}
}
is DisableNotifications -> with(operation) {
gatt.findCharacteristic(characteristicUuid)?.let { characteristic ->
val cccdUuid = UUID.fromString(CCC_DESCRIPTOR_UUID)
characteristic.getDescriptor(cccdUuid)?.let { cccDescriptor ->
if (!gatt.setCharacteristicNotification(characteristic, false)) {
Timber.e("setCharacteristicNotification failed for ${characteristic.uuid}")
signalEndOfOperation()
return
}
cccDescriptor.value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(cccDescriptor)
} ?: this#ConnectionManager.run {
Timber.e("${characteristic.uuid} doesn't contain the CCC descriptor!")
signalEndOfOperation()
}
} ?: this#ConnectionManager.run {
Timber.e("Cannot find $characteristicUuid! Failed to disable notifications.")
signalEndOfOperation()
}
}
is MtuRequest -> with(operation) {
gatt.requestMtu(mtu)
}
}
}
private val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Timber.w("onConnectionStateChange: connected to $deviceAddress")
deviceGattMap[gatt.device] = gatt
Handler(Looper.getMainLooper()).post {
gatt.discoverServices()
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Timber.e("onConnectionStateChange: disconnected from $deviceAddress")
teardownConnection(gatt.device)
}
} else {
Timber.e("onConnectionStateChange: status $status encountered for $deviceAddress!")
if (pendingOperation is Connect) {
signalEndOfOperation()
}
teardownConnection(gatt.device)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
with(gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Timber.w("Discovered ${services.size} services for ${device.address}.")
printGattTable()
requestMtu(device, GATT_MAX_MTU_SIZE)
listeners.forEach { it.get()?.onConnectionSetupComplete?.invoke(this) }
} else {
Timber.e("Service discovery failed due to status $status")
teardownConnection(gatt.device)
}
}
if (pendingOperation is Connect) {
signalEndOfOperation()
}
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
Timber.w("ATT MTU changed to $mtu, success: ${status == BluetoothGatt.GATT_SUCCESS}")
listeners.forEach { it.get()?.onMtuChanged?.invoke(gatt.device, mtu) }
if (pendingOperation is MtuRequest) {
signalEndOfOperation()
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Timber.i("Read characteristic $uuid | value: ${value.toHexString()}")
listeners.forEach { it.get()?.onCharacteristicRead?.invoke(gatt.device, this) }
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Timber.e("Read not permitted for $uuid!")
}
else -> {
Timber.e("Characteristic read failed for $uuid, error: $status")
}
}
}
if (pendingOperation is CharacteristicRead) {
signalEndOfOperation()
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Timber.i("Wrote to characteristic $uuid | value: ${value.toHexString()}")
listeners.forEach { it.get()?.onCharacteristicWrite?.invoke(gatt.device, this) }
}
BluetoothGatt.GATT_WRITE_NOT_PERMITTED -> {
Timber.e("Write not permitted for $uuid!")
}
else -> {
Timber.e("Characteristic write failed for $uuid, error: $status")
}
}
}
if (pendingOperation is CharacteristicWrite) {
signalEndOfOperation()
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
with(characteristic) {
Timber.i("Characteristic $uuid changed | value: ${value.toHexString()}")
listeners.forEach { it.get()?.onCharacteristicChanged?.invoke(gatt.device, this) }
}
}
override fun onDescriptorRead(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
with(descriptor) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Timber.i("Read descriptor $uuid | value: ${value.toHexString()}")
listeners.forEach { it.get()?.onDescriptorRead?.invoke(gatt.device, this) }
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Timber.e("Read not permitted for $uuid!")
}
else -> {
Timber.e("Descriptor read failed for $uuid, error: $status")
}
}
}
if (pendingOperation is DescriptorRead) {
signalEndOfOperation()
}
}
override fun onDescriptorWrite(
gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor,
status: Int
) {
with(descriptor) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Timber.i("Wrote to descriptor $uuid | value: ${value.toHexString()}")
if (isCccd()) {
onCccdWrite(gatt, value, characteristic)
} else {
listeners.forEach { it.get()?.onDescriptorWrite?.invoke(gatt.device, this) }
}
}
BluetoothGatt.GATT_WRITE_NOT_PERMITTED -> {
Timber.e("Write not permitted for $uuid!")
}
else -> {
Timber.e("Descriptor write failed for $uuid, error: $status")
}
}
}
if (descriptor.isCccd() &&
(pendingOperation is EnableNotifications || pendingOperation is DisableNotifications)
) {
signalEndOfOperation()
} else if (!descriptor.isCccd() && pendingOperation is DescriptorWrite) {
signalEndOfOperation()
}
}
private fun onCccdWrite(
gatt: BluetoothGatt,
value: ByteArray,
characteristic: BluetoothGattCharacteristic
) {
val charUuid = characteristic.uuid
val notificationsEnabled =
value.contentEquals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) ||
value.contentEquals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
val notificationsDisabled =
value.contentEquals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)
when {
notificationsEnabled -> {
Timber.w("Notifications or indications ENABLED on $charUuid")
listeners.forEach {
it.get()?.onNotificationsEnabled?.invoke(
gatt.device,
characteristic
)
}
}
notificationsDisabled -> {
Timber.w("Notifications or indications DISABLED on $charUuid")
listeners.forEach {
it.get()?.onNotificationsDisabled?.invoke(
gatt.device,
characteristic
)
}
}
else -> {
Timber.e("Unexpected value ${value.toHexString()} on CCCD of $charUuid")
}
}
}
}
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
with(intent) {
if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val device = getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
val previousBondState = getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
val bondState = getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
val bondTransition = "${previousBondState.toBondStateDescription()} to " +
bondState.toBondStateDescription()
Timber.w("${device?.address} bond state changed | $bondTransition")
}
}
}
private fun Int.toBondStateDescription() = when (this) {
BluetoothDevice.BOND_BONDED -> "BONDED"
BluetoothDevice.BOND_BONDING -> "BONDING"
BluetoothDevice.BOND_NONE -> "NOT BONDED"
else -> "ERROR: $this"
}
}
private fun BluetoothDevice.isConnected() = deviceGattMap.containsKey(this)
}
Related
How to properly update Android BillingFlowParams sku() to setSkuDetails()
here is the code can anyone tell how can I setSkuDetails() as I was using vision one now I update it to 4 However, setSku and setType seem to be deprecated in the BillingFlowParams.Builder class. Instead, we should be using setSkuDetails(SkuDetails). private void BillingFunction() { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Establish connection to billing client mBillingClient = BillingClient.newBuilder(MainActivity.this).setListener(MainActivity.this).build(); mBillingClient.startConnection(new BillingClientStateListener() { #Override public void onBillingSetupFinished(#NonNull BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { // The billing client is ready. You can query purchases here. getPricesMonthlyTime(); getPricesYearlyTime(); getPricesONeTime(); } } #Override public void onBillingServiceDisconnected() { //TODO implement your own retry policy Toast.makeText(MainActivity.this, getResources().getString(R.string.billing_connection_failure), Toast.LENGTH_SHORT); // Try to restart the connection on the next request to // Google Play by calling the startConnection() method. } }); continue_button.setOnClickListener(view -> { if (select_radio_one.getVisibility() == View.VISIBLE) { BillingFlowParams flowParams = BillingFlowParams.newBuilder() .setSkuDetails() .build(); BillingResult responseCode = mBillingClient.launchBillingFlow(MainActivity.this, flowParams); brandDialogInAppPurchase.dismiss(); } else if (select_radio_two.getVisibility() == View.VISIBLE) { BillingFlowParams flowParams = BillingFlowParams.newBuilder() .setSkuDetails() .build(); BillingResult responseCode = mBillingClient.launchBillingFlow(MainActivity.this, flowParams); brandDialogInAppPurchase.dismiss(); } else if (select_radio_three.getVisibility() == View.VISIBLE) { BillingFlowParams flowParams = BillingFlowParams.newBuilder() .setSkuDetails() .build(); BillingResult responseCode = mBillingClient.launchBillingFlow(MainActivity.this, flowParams); brandDialogInAppPurchase.dismiss(); } else { Toast.makeText(MainActivity.this, "Nothing selected", Toast.LENGTH_SHORT).show(); } }); // queryPrefPurchases(); queryPurchases(); }
You should send the object skuDetail. To do so you need to retrieve it by calling querySkuDetailsAsync(). fun querySkuDetails() { val skuList = ArrayList<String>() skuList.add("premium_upgrade") skuList.add("gas") val params = SkuDetailsParams.newBuilder() params.setSkusList(skuList).setType(SkuType.INAPP) // leverage querySkuDetails Kotlin extension function val skuDetailsResult = withContext(Dispatchers.IO) { billingClient.querySkuDetails(params.build()) } // Process the result. }
Kotlin coroutines delay do not work on IOS queue dispatcher
I have a KMM app, and there is code: fun getWeather(callback: (WeatherInfo) -> Unit) { println("Start loading") GlobalScope.launch(ApplicationDispatcher) { while (true) { val response = httpClient.get<String>(API_URL) { url.parameters.apply { set("q", "Moscow") set("units", "metric") set("appid", weatherApiKey()) } println(url.build()) } val result = Json { ignoreUnknownKeys = true }.decodeFromString<WeatherApiResponse>(response).main callback(result) // because ApplicationDispatcher on IOS do not support delay withContext(Dispatchers.Default) { delay(DELAY_TIME) } } } } And if I replace withContext(Dispatchers.Default) { delay(DELAY_TIME) } with delay(DELAY_TIME) execution is never returned to while cycle and it will have only one iteration. And ApplicationDispatcher for IOS looks like: internal actual val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue()) internal class NsQueueDispatcher( private val dispatchQueue: dispatch_queue_t ) : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { dispatch_async(dispatchQueue) { block.run() } } } And from delay source code I can guess, that DefaultDelay should be returned and there is should be similar behaviour with/without withContext(Dispatchers.Default) /** Returns [Delay] implementation of the given context */ internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay Thanks! P.S. I got ApplicationDispatcher from ktor-samples.
Probably ApplicationDispatcher is some old stuff, you don't need to use it anymore: CoroutineScope(Dispatchers.Default).launch { } or MainScope().launch { } And don't forget to use -native-mt version of coroutines, more info in this issue
how to use the Microsoft Speech API: iOS Speech-to-Text Client Library in swift?
As I am new to swift language I know how to convert speech to text in objective c using the microsoft API but as part of client request I need that in swift language .can anyone help me how to do that in swift language. I also added the sample code which I used in objective c -(void)onFinalResponseReceived:(RecognitionResult*)response { bool isFinalDicationMessage = self.mode == SpeechRecognitionMode_LongDictation && (response.RecognitionStatus == RecognitionStatus_EndOfDictation || response.RecognitionStatus == RecognitionStatus_DictationEndSilenceTimeout); if (nil != micClient && self.useMicrophone && ((self.mode == SpeechRecognitionMode_ShortPhrase) || isFinalDicationMessage)) { // we got the final result, so it we can end the mic reco. No need to do this // for dataReco, since we already called endAudio on it as soon as we were done // sending all the data. [micClient endMicAndRecognition]; } if ((self.mode == SpeechRecognitionMode_ShortPhrase) || isFinalDicationMessage) { dispatch_async(dispatch_get_main_queue(), ^{ [[self startButton] setEnabled:YES]; }); } if (!isFinalDicationMessage) { dispatch_async(dispatch_get_main_queue(), ^{ [self WriteLine:(#"********* Final n-BEST Results *********")]; for (int i = 0; i < [response.RecognizedPhrase count]; i++) { RecognizedPhrase* phrase = response.RecognizedPhrase[i]; [self WriteLine:[[NSString alloc] initWithFormat:(#"[%d] Confidence=%# Text=\"%#\""), i, ConvertSpeechRecoConfidenceEnumToString(phrase.Confidence), phrase.DisplayText]]; } [self WriteLine:(#"")]; }); } } //convert speech OSStatus status = [micClient startMicAndRecognition]; if (status) { [self WriteLine:[[NSString alloc] initWithFormat:(#"Error starting audio. %#"), ConvertSpeechErrorToString(status)]]; } NSString* ConvertSpeechErrorToString(int errorCode) { switch ((SpeechClientStatus)errorCode) { case SpeechClientStatus_SecurityFailed: return #"SpeechClientStatus_SecurityFailed"; }
Try this: func onFinalResponseReceived(_ response: RecognitionResult?) { let isFinalDicationMessage: Bool = mode == SpeechRecognitionMode_LongDictation && (response?.recognitionStatus == RecognitionStatus_EndOfDictation || response?.recognitionStatus == RecognitionStatus_DictationEndSilenceTimeout) if nil != micClient && useMicrophone && ((mode == SpeechRecognitionMode_ShortPhrase) || isFinalDicationMessage) { // we got the final result, so it we can end the mic reco. No need to do this // for dataReco, since we already called endAudio on it as soon as we were done // sending all the data. micClient.endMicAndRecognition() } if (mode == SpeechRecognitionMode_ShortPhrase) || isFinalDicationMessage { DispatchQueue.main.async(execute: {() -> Void in self.startButton().enabled = true }) } if !isFinalDicationMessage { DispatchQueue.main.async(execute: {() -> Void in self.writeLine(("********* Final n-BEST Results *********")) var i = 0 while i < response.recognizedPhrase.count() { var phrase: RecognizedPhrase? = response.recognizedPhrase[i] if let aText = phrase?.displayText { self.writeLine(("[\(i)] Confidence=\(ConvertSpeechRecoConfidenceEnumToString(phrase?.confidence)) Text=\"\(aText)\"")) } i } i += 1 self.writeLine(("")) }) } } // edit: var status: OSStatus = micClient.startMicAndRecognition() func (int errorCode) -> String? { switch errorCode as? SpeechClientStatus { case SpeechClientStatus_SecurityFailed: return "SpeechClientStatus_SecurityFailed" } }
Alljoyn initialising bus attachment throws EXC_BAD_ACCESS
I am trying to create an iOS app using the alljoyn library. However when I try to create a bus attachment in swift by calling busAttachment = AJNBusAttachment.init(applicationName: "BusName", allowRemoteMessages: true); I get an EXC_BAD_ACCESS error during runtime. This is the class I am using to create and use the bus attachment class HostController: UIViewController { var hostApi: HostApi! var busAttachment: AJNBusAttachment! var sessionOpts: AJNSessionOptions! var CONTACT_PORT: AJNSessionPort = 12345 var serviceName: String = "org.company.appname.HelloWorld" override func viewDidLoad() { super.viewDidLoad(); NSLog("HostController viewDidLoad"); var status: QStatus! busAttachment = AJNBusAttachment(applicationName: "BusName", allowRemoteMessages: true); busAttachment.registerBusListener(BusListener()); hostApi = HostApi.init(busAttachment: busAttachment, onPath: "/HostService"); status = busAttachment.start(); if (status != ER_OK) { NSLog("busAttachment.start, Status: %s", QCC_StatusText(status)); } status = busAttachment.registerBusObject(hostApi); if (status != ER_OK) { NSLog("busAttachment.registerBusObject, Status: %s", QCC_StatusText(status)); } status = busAttachment.connectWithArguments(nil); if (status != ER_OK) { NSLog("busAttachment.connectWithArguments, Status: %s", QCC_StatusText(status)); } status = busAttachment.requestWellKnownName(serviceName, withFlags: kAJNBusNameFlagReplaceExisting | kAJNBusNameFlagDoNotQueue) if (status != ER_OK) { NSLog("busAttachment.requestWellKnownName, Status: %s", QCC_StatusText(status)); } sessionOpts = AJNSessionOptions.init(trafficType: kAJNTrafficMessages, supportsMultipoint: true, proximity: kAJNProximityAny, transportMask: kAJNTransportMaskTCP); status = busAttachment.bindSessionOnPort(CONTACT_PORT, withOptions: sessionOpts, withDelegate: nil); if (status != ER_OK) { NSLog("busAttachment.bindSessionOnPort, Status: %s", QCC_StatusText(status)); } status = busAttachment.advertiseName(serviceName, withTransportMask: sessionOpts.transports); if (status != ER_OK) { NSLog("busAttachment.advertiseName, Status: %s", QCC_StatusText(status)); } NSLog("Done"); } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning(); // Dispose of any resources that can be recreated. } class BusListener: NSObject, AJNBusListener {} class SessionPortListener: NSObject, AJNSessionPortListener { func shouldAcceptSessionJoinerNamed(joiner: String!, onSessionPort sessionPort: AJNSessionPort, withSessionOptions options: AJNSessionOptions!) -> Bool { if (sessionPort == 12345) { NSLog("New peer joining session: %s", joiner); return true; } else { return false; } } } } One thing I could be missing, on java we had to call org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon(context); before we could use bus attachments, however in the samples provided for iOS there does not seem to be an equivalent call. Maybe I just missed it and it is put somewhere else, or maybe on iOS there is no equivalent.
Like Java you need to init AllJoyn before you can use it. To fix this I added #import "AJNInit.h" to my bridging header and added this to my swift to initialise AllJoyn. AJNInit.alljoynInit(); AJNInit.alljoynRouterInit(); You should also call these methods to release resources used by AllJoyn. AJNInit.alljoynShutdown(); AJNInit.alljoynRouterShutdown();
Xamarin iOS Bluetooth Low Energy - CBPeripheral.UpdatedCharacteristicValue reading TX characteristic shows unexpected data
I recently received a BLE device for Bluetooth to Serial. It uses TruConnect and I'm trying to get it to communicate with my serial device. The serial device receives communication over a serial cable and echoes back anything that is sent to it as well as any results from a command that is sent. Right now I'm simply trying to send TruConnect commands to the BLE device to check the current baud rate that the BLE device is set for. I wrote some code based on this TruConnect guide that I found: https://truconnect.ack.me/1.5/apps/communicating_via_ble#reading_from_a_truconnect_device_serial_interface. The problem seems to be that whenever I try to read anything from the tx characteristic when there should be data, the data is not right. Setting up CBPeripheral events: private void setupPerif(CBPeripheral perf) { selectedPeripheral = perf; selectedPeripheral.UpdatedCharacterteristicValue += (sender, e) => { var c = e.Characteristic; if (c != null) { var uuid = c.UUID.ToString(true).ToLower(); if (uuid == UUID_RX) { // } else if (uuid == UUID_TX) { // expecting bytes to contain valid response data // it almost always contains twenty 0s. byte[] bytes = c.Value.Where(i => i != 13).ToArray(); var invalidBytes = c.Value.Where(i => i > 127).ToArray(); var nonZeros = c.Value.Where(i => i != 0).ToArray(); if (nonZeros.Length < 1) { return; } else { foreach (byte b in bytes) handler.handleByteReceived((char)b); } } else if (uuid == UUID_MODE) { // } } }; selectedPeripheral.DiscoveredService += (sender, e) => { var services = selectedPeripheral.Services; if (services != null) { foreach (CBService service in services) { if (service.UUID.ToString(true).ToLower() == UUID_TRUCONNECT) { truConnect = service; selectedPeripheral.DiscoverCharacteristics(truConnect); } } } }; selectedPeripheral.DiscoveredCharacteristic += (sender, e) => { if (truConnect != null && truConnect.Characteristics != null) { foreach (CBCharacteristic c in truConnect.Characteristics) { var uuidString = c.UUID.ToString(true).ToLower(); if (uuidString == UUID_RX) { rx = c; } else if (uuidString == UUID_TX) { tx = c; } else if (uuidString == UUID_MODE) { mode = c; // set to stream mode selectedPeripheral.WriteValue(NSData.FromArray(new byte[] { MODE_COMMAND }), mode, CBCharacteristicWriteType.WithResponse); } } } }; selectedPeripheral.WroteCharacteristicValue += (sender, e) => { // if UUID is for RX, we just wrote to RX. Drill down to // TX characteristic and read it. This will trigger // the UpdatedCharacteristicValue event. string uuid = e.Characteristic.UUID.ToString(true).ToLower(); if (uuid == UUID_RX) { var services = selectedPeripheral.Services; if (services != null) { foreach (CBService s in services) { if (s.UUID.ToString(true).ToLower() == UUID_TRUCONNECT) { var charachteristics = s.Characteristics; if (charachteristics != null && charachteristics.Length > 0) { foreach (CBCharacteristic c in charachteristics) { if (c.UUID.ToString(true).ToLower() == UUID_TX) { Timer t = new Timer(new TimerCallback(delegate(object o) { selectedPeripheral.ReadValue(c); }), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(-1)); } } } } } } } }; manager.ConnectPeripheral(selectedPeripheral); } Writes to rx. This is what should be used to actually send commands. public void sendCommand(string command) { command += endString + "\n"; if (rx != null) { NSData d = NSData.FromString(command); foreach (CBService s in selectedPeripheral.Services) { if (s.UUID.ToString(true).ToLower() == UUID_TRUCONNECT) foreach (CBCharacteristic c in s.Characteristics) { if (c.UUID.ToString(true).ToLower() == UUID_RX) selectedPeripheral.WriteValue(NSData.FromString(command), c, CBCharacteristicWriteType.WithResponse); } } } } So my question is, why am I not getting the expected data when the CBPeripheral.UpdatedCharacteristicValue event is called? Occasionally I will get the expected data, but it is quite rare, and I can't seem to find any logical reason or pattern that would explain why this is happening.
AHA! I figured it out! The problem was that I need to set the notify value for the appropriate characteristics. After doing that, I didn't need to call CBPeripheral.ReadValue(CBCharacteristic). selectedPeripheral.DiscoveredCharacteristic += (sender, e) => { if (truConnect != null && truConnect.Characteristics != null) { foreach (CBCharacteristic c in truConnect.Characteristics) { var uuidString = c.UUID.ToString(true).ToLower(); if (uuidString == UUID_RX) { rx = c; } else if (uuidString == UUID_TX) { tx = c; // set the notify value to true and poof! // now CBPeripheral.UpdatedCharacteristicValue // event will be triggered at the appropriate time. selectedPeripheral.SetNotifyValue(true, tx); } else if (uuidString == UUID_MODE) { mode = c; // set to remote command mode selectedPeripheral.WriteValue(NSData.FromArray(new byte[] { MODE_COMMAND }), mode, CBCharacteristicWriteType.WithResponse); } } } };