I am trying to implement a BLE UART service where the central can send down a request, the peripheral takes that request and forwards it to a peripheral application to do processing, and then returns a response back to the central. The design is similar to Figure 2 found in https://punchthrough.com/serial-over-ble/
I am having trouble with the last part to return data back to the central.
Below are the tx/rx characteristics I came across to implement the UART
class TxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_TX_CHARACTERISTIC_UUID,
['notify'], service)
self.notifying = False
GLib.io_add_watch(sys.stdin, GLib.IO_IN, self.on_console_input)
def on_console_input(self, fd, condition):
s = fd.readline()
if s.isspace():
pass
else:
self.send_tx(s)
return True
def send_tx(self, s):
if not self.notifying:
return
value = []
for c in s:
value.append(dbus.Byte(c.encode()))
self.PropertiesChanged(GATT_CHRC_IFACE, {'Value': value}, [])
def StartNotify(self):
if self.notifying:
return
self.notifying = True
def StopNotify(self):
if not self.notifying:
return
self.notifying = False
class RxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
['write'], service)
def WriteValue(self, value, options):
data = bytearray(value).decode('utf-8')
print(f'Incoming UART data: {data}')
handle_request(data)
With a BLE scanner phone app, I can write data to the RxCharacteristic OK and when RxCharacteristic receives it, I call handle_request(data) for processing.
This is where I'm stuck. How do I get a handle or reference the TxCharacteristic so that I can call send_tx()? Is the 'notify' characteristic flag what I want or do I want a 'write' flag?
Typically with UART over BLE people use the Notify flag. The central device (phone app) will need to enable notifications so that it gets notified when values change.
Using the Nordic UART Service (NUS) is normally a good example to follow. If you do implement NUS then there are several apps that work with it.
Looking at your code, it looks like the plumbing is there to connect your console to the send_tx method. I suspect that your app isn't doing a StartNotify
It is the icon I've circled in Lime Green in the image below. This is from nRF Connect which is a generic Bluetooth Low Energy scanning and exploration tool.
Apps like Serial Bluetooth Terminal will do StartNotify automatically for NUS
Related
The documentation in the "Tasks in Toit" section indicates that the language has facilities for asynchronous data exchange between tasks. If I understand correctly, then two classes from the monitor package: Channel and Mailbox provide this opportunity. Unfortunately, I didn't find examples of using these classes, so I ask you to give at least the simplest example of the implementation of two tasks:
One of the tasks is a message generator, for example, it sends
integers or strings to the second task. The second task gets these
numbers or strings. Perhaps in this case the Channel class should
be used.
Each of the two tasks is both a generator and a receiver of messages.
Those. the first task sends a message to the second task and in turn
asynchronously receives the messages generated by the second task.
Judging by the description, the Mailbox class should be used in
this case.
Thanks in advance,
MK
Here's an example of the first part, using Channel. This class is useful if you have a stream of messages for another task.
import monitor
main:
// A channel with a backlog of 5 items. Once the reader is 5 items behind, the
// writer will block when trying to send. This helps avoid unbounded memory
// use by the in-flight messages if messages are being generated faster than
// they are being consumed. Decreasing this will tend to reduce throughput,
// increasing it will increase memory use.
channel := monitor.Channel 5
task:: message_generating_task channel
task:: message_receiving_task channel
/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection. It could block for some unknown time while doing this. In this
/// case we just sleep a little to illustrate that the data arrives at random
/// times.
generate_message:
milliseconds := random 1000
sleep --ms=milliseconds
// The message is just a string, but could be any object.
return "Message creation took $(milliseconds)ms"
message_generating_task channel/monitor.Channel:
10.repeat:
message := generate_message
channel.send message
channel.send null // We are done.
/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection. It could block for some unknown time while doing this. In this
/// case we just sleep a little to illustrate that the data takes a random
/// amount of time to process.
process_message message:
milliseconds := random 1000
sleep --ms=milliseconds
print message
message_receiving_task channel/monitor.Channel:
while message := channel.receive:
process_message message
Here is an example of using Mailbox. This class is useful if you have a task processing requests and giving responses to other tasks.
import monitor
main:
mailbox := monitor.Mailbox
task:: client_task 1 mailbox
task:: client_task 2 mailbox
task --background:: factorization_task mailbox
/// Normally this could be looking at IO ports, GPIO pins, or perhaps a socket
/// connection. It could block for some unknown time while doing this. For
/// this example we just sleep a little to illustrate that the data arrives at
/// random times.
generate_huge_number:
milliseconds := random 1000
sleep --ms=milliseconds
return (random 100) + 1 // Not actually so huge.
client_task task_number mailbox/monitor.Mailbox:
10.repeat:
huge := generate_huge_number
factor := mailbox.send huge // Send number, wait for result.
other_factor := huge / factor
print "task $task_number: $factor * $other_factor == $huge"
// Factorize a number using the quantum computing port.
factorize_number number:
// TODO: Use actual quantum computing instead of brute-force search.
for i := number.sqrt.round; i > 1; i--:
factor := number / i
if factor * i == number:
return factor
// This will yield so the other tasks can run. In a real application it
// would be waiting on an IO pin connected to the quantum computing unit.
sleep --ms=1
return 1 // 1 is sort-of a factor of all integers.
factorization_task mailbox/monitor.Mailbox:
// Because this task was started as a background task (see 'main' function),
// the program does not wait for it to exit so this loop does not need a real
// exit condition.
while number := mailbox.receive:
result := factorize_number number
mailbox.reply result
I'm pretty sure the Mailbox example worked great at the end of March. I decided to check it now and got the error:
In case of Console Toit:
./web.toit:8:3: error: Argument mismatch: 'task'
task --background:: factorization_task mailbox
^~~~
Compilation failed.
In case of terminal:
micrcx#micrcx-desktop:~/toit_apps/Hsm/communication$ toit execute mailbox_sample.toit
mailbox_sample.toit:8:3: error: Argument mismatch: 'task'
task --background:: factorization_task mailbox
^~~~
Compilation failed.
Perhaps this is due to the latest SDK update. Just in case:
Toit CLI:
| v1.0.0 | 2021-03-29 |
I want to call new actions as I complete the conversation with the user on the secondary receiver protocol and I also want to pass the thread again to the primary receiver.
After the conversation ended, I want my chatbot to be started again.
To restart the conversation, include Restarted() in the events returned from your last custom action:
class ActionName(Action):
def name(self):
...
return "action_name"
def run(self, dispatcher, tracker, domain):
...
return [Restarted()]
You can customise action_session_start to check some condition and pass it to the appropriate receiver as well
Can C++ Actor Framework be used in such a way that it guarantees message ordering between two actors? I couldn't find anything about this in the manual.
If you have only two actors communicating directly, CAF guarantees that messages arrive in the order they have been sent. Only multi-hop scenarios can cause non-determinism and message reordering.
auto a = spawn(A);
self->send(a, "foo");
self->send(a, 42); // arrives always after "foo"
At the receiving end, it is possible to change the message processing order by changing the actor behavior with become:
[=](int) {
self->become(
keep_behavior,
[=](const std::string&) {
self->unbecome();
}
);
}
In the above example, this will process the int before the string message, even though they have arrived in opposite order at the actor's mailbox.
When an app running on iOS8 is backgrounded, all uniquely identifiable information appears to be scrubbed from the Bluetooth advertising package. peripheral.name, peripheral.identifier, etc. It all goes away as soon as the app is backgrounded.
The only workaround I have discovered - to identify and range multiple bluetooth-emitting apps - is to scan and connect with a set of known devices (iPhones).
My app transmits as a peripheral, with a service that has a characteristic whose value is a unique identifier. This works.
Where I fall down is once I have read the characteristic (and identified the device) I need to range it. In the delegate call for did get RSSI, I get a peripheral object, but due to the asynchronous nature of the delegate pattern I don't know which of the discovered peripherals I am getting the RSSI signal for. Peripheral appears to remain anonymous, even after connected!
-(void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error == nil) {
NSString *valueString=[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"The new value=%#",valueString);
peripheral.delegate = self;
[peripheral readRSSI];
}
}
-(void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
NSLog(#"Got RSSI update in didReadRSSI : %4.1f", [RSSI doubleValue]);
// but which peripheral (and associated id) did we get back??
}
This is either a limitation of Apple's spec, or something wrong with my expectations. One central to many peripherals, unlike the original Bluetooth architecture of one-to-one.
Any ideas how I can identify and range an app broadcasting as a peripheral while backgrounded? Huge thanks if so!
The CBPeripheral object itself should be unique. You can keep a dictionary of your discovered / connected peripherals along with the custom identifier from your characteristic. When the didReadRSSI delegate is called, you can check against your 'known' collection of devices to identify a specific device.
This question is not about Android notificatinos, but BLE notifications (as the title may hint)
I have got basic BLE peripheral mode working on Android-L
Is there any way to implement BLE notifications in Android-L preview. I could do some thing like the following to make a charecteritic be able to notify, but trying to listen for
BluetoothGattCharacteristic firstServiceChar = new BluetoothGattCharacteristic(
UUID.fromString(serviceOneCharUuid),
BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ );
But in LightBlue app on iOS I cannot subscribe to this characteristic. Apprently there is no API that could be use to respond to the calls when a char is subscribed (like there are in iOS)
Kindly share your code if you have successfully enabled BLE notifications on Android-L
On top of what OP has done:
BluetoothGattCharacteristic firstServiceChar = new BluetoothGattCharacteristic(UUID.fromString(serviceOneCharUuid), BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ )
The next thing is to add a Client Characteristic Configuration descriptor (UUID is the 128 bit version of the 16 bit 0x2902 using the Bluetooth base UUID), so that the connected device can tell yours that it wants notifications (or indications), and then add that descriptor to your characteristic:
BluetoothGattDescriptor gD = new BluetoothGattDescriptor(UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"), BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattDescriptor.PERMISSION_READ);
firstServiceChar.addDescriptor(gD);
That UUID is from the Bluetooth spec. Apparently a device subscribes to notifications by updating this descriptor, so you've got to handle this in your BluetoothGattServerCallback by overriding onDescriptorWriteRequest:
#Override
public void onDescriptorWriteRequest (BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
btClient = device; // perhaps add to some kind of collection of devices to update?
// now tell the connected device that this was all successfull
btGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
Now update your value and notify the connectee:
firstServiceChar.setValue("HI");
btGattServer.notifyCharacteristicChanged(btClient, firstServiceChar, false);
Hopefully this quick and dirty code will help, because it was OP's code that I used in the first place to even get basic peripheral mode working :)