I encountered a situation
When I connect 2 devices at once
When the connection is successful, the notification subcribe is listened to
When one is off and the other is in use
The program keeps trying to connect the two devices every 5 seconds
but because the connection is not disconnected
so it has to wait until the one that is switched off GATT ERROR
The one that is in use will not successfully connect to receive data
This is a bit of a long wait.
I want to disconnect manually after receiving the data
So that the next time I connect, I don't have to wait for GATT ERROR.
I don't know if this will improve the long waiting time of the one in use.
I have tried nRF connect
When I disconnected it manually
I can quickly connect with another
class BleDevice : BaseDevice() {
lateinit var context: Context
private var macAddress : String = "00:00:00:00:00:00"
//Search Device behavior
//If true, always search no interrupt
private var establishConnection = false
private var setupNotificationMap = arrayListOf<Disposable>()
private var connectionDisposable: Disposable? = null
private var sendDisposable: Disposable? = null
private var connection: RxBleConnection? = null
override fun work() {
if((strategy as BaseBleVariables).enableSetMTU()) {
connection?.requestMtu((strategy as BaseBleVariables).getMTUSize())
?.subscribe(
{ it ->
Log.d(logTag, "Set MTU = $it")
},
{ exception ->
exception.printStackTrace()
}
)?.let { disposable ->
setupNotificationMap.add(disposable)
}
}
(strategy as BaseBleVariables).getNotificationMap().forEach { uuid ->
Log.d(logTag, "getNotification")
connection?.setupNotification(UUID.fromString(uuid))
?.subscribe(
{
it.subscribe(
{ receive ->
strategy.onRespond(
receive,
Bundle().apply { putString("Characteristic", uuid) }
)
},
{ exception ->
Log.d(logTag, "Receive Response Fail")
exception.printStackTrace()
disconnect(exception.message)
}
).let { disposable ->
setupNotificationMap.add(disposable)
}
},
{ exception ->
Log.d(logTag, "Setup Notification Fail")
exception.printStackTrace()
disconnect(exception.message)
}
)?.let { disposable ->
setupNotificationMap.add(disposable)
}
}
strategy.onSend(null)
}
override fun stopWork() {
setupNotificationMap.forEach {
if(!it.isDisposed) {
it.dispose()
}
}
sendDisposable?.let {
if(!it.isDisposed) {
Log.d(logTag, "SendDisposable dispose")
it.dispose()
}
}
setupNotificationMap.clear()
}
#ExperimentalStdlibApi
override fun connect() {
if(strategy !is BaseBleVariables) {
Log.d(logTag, "Strategy not impl ble variables")
returnConnectionStatus(false, "Strategy incorrectly implement")
return
}
RxJavaPlugins.setErrorHandler { throwable: Throwable ->
throwable.printStackTrace()
}
connectionDisposable = RxBleClient.create(context)
.getBleDevice(macAddress)
.establishConnection(establishConnection)
.subscribe(
{
connection = it
returnConnectionStatus(true)
},
{
Log.d(logTag, "Client Disconnected")
it.printStackTrace()
disconnect(it.message)
}
)
}
override fun disconnect(message: String?) {
runCatching {
stopWork()
connectionDisposable?.let {
if(!it.isDisposed) {
Log.d(logTag, "ConnectionDisposable dispose")
it.dispose()
}
}
connection = null
returnConnectionStatus(false, message)
}.onFailure {
returnConnectionStatus(false, message)
}
}
override fun receive(): Result<ByteArray> {
return Result.success(byteArrayOf())
}
override fun send(command: Any, extras: Bundle?): Result<Any> {
if(connection == null) {
return Result.failure(Exception("Connection is null"))
}
else {
return runCatching {
when(command) {
is ByteArray -> {
Log.d(logTag, "Delay : ${extras?.getLong("SendDelay", 0L)}")
Log.d(logTag, "Send : ${command.contentToString()}")
extras?.getString("Characteristic")?.let { characteristic ->
sendDisposable?.let {
if(!it.isDisposed) {
it.dispose()
}
}
sendDisposable = connection?.writeCharacteristic(
(UUID.fromString(characteristic)),
command
)?.delaySubscription(extras.getLong("SendDelay", 0L), TimeUnit.MILLISECONDS)
?.subscribe(
{
Log.d(logTag, "Write Characteristic($characteristic) = ${String(it)}")
},
{
Log.d(logTag, "Send Fail")
it.printStackTrace()
}
)
}
"Nothing"
}
else -> {
Log.d(logTag, "This command type not support now")
}
}
}
}
}
class Builder<T: BaseStrategy>(strategyClass: Class<T>, _context: Context): BaseDevice.Builder<BleDevice, T>(BleDevice::class.java, strategyClass){
private var logTag = "BleBuilder"
init {
device.context = _context
}
fun setMacAddress(address: String) : Builder<T> {
device.macAddress = address
Log.d(logTag, "Set MacAddress = ".plus(address))
return this
}
/**
* Search Device behavior
* If true, always search no interrupt
*/
fun setEstablishConnection(establish: Boolean) : Builder<T> {
device.establishConnection = establish
Log.d(logTag, "Set EstablishConnection = ".plus(establish))
return this
}
}
}
To use it, remember the mac address of the two Bluetooth devices
Then use the for loop to connect one device in 5 seconds
When the device is connected, dispose of it and connect to the other one which is not connected.
I have also tried for looping two units at a time in five seconds
When connecting
When both devices are on, the data can be transferred back quickly
When one of them is switched off
When one is switched off, the switched on device must wait for the switched off device to automatically Gatt ERROR
The one that is switched on will only connect and send data
But when using the nRF connect app
When I manually disconnect the one that is off
the other one can connect and send back data very quickly
RxAndroidBle Author's Response
RxAndroidBle was designed to not have any state management (i.e. disconnection function) — when the user is no longer interested in keeping the connection they just dispose the flow.
You should be able to dispose your connection when you receive the data
Related
I am using rxbleandroid library for scanning Bluetooth devices
fun startScan() {
if(rxBleClient.isScanRuntimePermissionGranted) {
Log.d("TAG", "All permission granted")
}
else {
Log.d("TAG", "Not all permission granted\"")
}
val scanSettings = ScanSettings.Builder()
.setScanMode(SCAN_MODE_BALANCED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
val scanFilter = ScanFilter.Builder()
.build()
rxBleClient.scanBleDevices(scanSettings, scanFilter)
.observeOn(AndroidSchedulers.mainThread())
.doFinally { dispose() }
.subscribe({scanResult ->
Log.d("TAG", scanResult?.bleDevice?.name!!)
},
{
onScanFailure(it) })
.let {
scanDisposable = it}//
}
private fun dispose() {
//scanDisposable = null
}
private fun onScanFailure(throwable: Throwable) {
Log.d("TAG", "onScanFailure ${throwable. Cause}")
viewModelScope.launch{
delay(6000)
scanDisposable?.dispose()
startScan()
}
}
After a short while a get a onScanFailure event. The output on the screen is
onScanFailure null
The only way to fix it is to wait little bit and restart the scanning.
I am using the latest version of android on a pixel6. The version number of rxAndroidLibraryble is 1.17.0
Am i using the library wrong?
In the callback function you are using this line:
Log.d("TAG", scanResult?.bleDevice?.name!!)
It will throw an NPE once a device with no advertised name will get scanned and that NPE will have no cause.
You could use this:
Log.d("TAG", scanResult?.bleDevice?.name ?: "no name")
I want to implement a "simple" SSDP discovering client. Means the client should send out a
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 1
ST: ssdp:all
and afterwards listen to "the network"(?) to get the list of IP addresses.
To test the implementation I've written a unit test which creates a "fake" MulticastServer which simply hear to the SSDP IP&Port and, when receive something, send the same message back.
The problem is that this code works on my machine (macOS) most of the time but never on our CI Server (Linux). I (macOS) receive sometimes the same assertion failed error as on the CI. But as I said - only sometimes! Not always. And I don't know why.
This is the implementation on the client side:
interface GatewayDiscoverer {
companion object {
val instance: GatewayDiscoverer = DefaultGatewayDiscoverer()
}
suspend fun discoverGateways(timeoutMillis: Int = 1000): List<String>
}
internal class DefaultGatewayDiscoverer : GatewayDiscoverer {
override suspend fun discoverGateways(timeoutMillis: Int): List<String> {
require(timeoutMillis in 1000..5000) {
"timeoutMillis should be between 1000 (inclusive) and 5000 (inclusive)!"
}
val socket = DatagramSocket()
sendSsdpPacket(socket)
val gateways = receiveSsdpPacket(socket, timeoutMillis)
return gateways
}
private fun sendSsdpPacket(socket: DatagramSocket) {
val packetToSend =
"M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 1\r\nST: ssdp:all\r\n\r\n"
val packetToSendAsBytes = packetToSend.toByteArray()
val packet = DatagramPacket(
packetToSendAsBytes,
packetToSendAsBytes.size,
InetAddress.getByName("239.255.255.250"),
1900
)
socket.send(packet)
}
private fun receiveSsdpPacket(socket: DatagramSocket, timeoutInMillis: Int): List<String> {
val gatewayList = mutableListOf<String>()
while (true) {
val receivedData = ByteArray(12)
val packetToReceive = DatagramPacket(receivedData, receivedData.size)
socket.soTimeout = timeoutInMillis
try {
socket.receive(packetToReceive)
packetToReceive.address?.hostName?.let { gatewayList.add(it) }
} catch (socketTimeout: SocketTimeoutException) {
return gatewayList
}
}
}
}
And this the test (includes the MulticastServer):
class DefaultGatewayDiscovererTest {
#Test
fun `discover gateways should return a list of gateway IPs`() = with(MulticastServer()) {
start()
val list = runBlocking { GatewayDiscoverer.instance.discoverGateways(1000) }
close()
assertThat(list.size).isEqualTo(1)
assertThat(list).contains(InetAddress.getLocalHost().hostAddress)
Unit
}
}
/**
* A "MulticastServer" which will join the
* 239.255.255.250:1900 group to listen on SSDP events.
* They will report back with the same package
* it received.
*/
class MulticastServer : Thread(), Closeable {
private val group = InetAddress.getByName("239.255.255.250")
private val socket: MulticastSocket = MulticastSocket(1900)
init {
// This force to use IPv4...
var netinterface: NetworkInterface? = null
// Otherwise it will (at least on macOS) use IPv6 which leads to issues
// while joining the group...
val networkInterfaces = NetworkInterface.getNetworkInterfaces()
while (networkInterfaces.hasMoreElements()) {
val networkInterface = networkInterfaces.nextElement()
val addressesFromNetworkInterface = networkInterface.inetAddresses
while (addressesFromNetworkInterface.hasMoreElements()) {
val inetAddress = addressesFromNetworkInterface.nextElement()
if (inetAddress.isSiteLocalAddress
&& !inetAddress.isAnyLocalAddress
&& !inetAddress.isLinkLocalAddress
&& !inetAddress.isLoopbackAddress
&& !inetAddress.isMulticastAddress
) {
netinterface = NetworkInterface.getByName(networkInterface.name)
}
}
}
socket.joinGroup(InetSocketAddress("239.255.255.250", 1900), netinterface!!)
}
override fun run() {
while (true) {
val buf = ByteArray(256)
val packet = DatagramPacket(buf, buf.size)
try {
socket.receive(packet)
} catch (socketEx: SocketException) {
break
}
// Print for debugging
val message = String(packet.data, 0, packet.length)
println(message)
socket.send(packet)
}
}
override fun close() = with(socket) {
leaveGroup(group)
close()
}
}
When the test fails it fails on that line:
assertThat(list.size).isEqualTo(1)
The list is empty.
After some debugging I found out that the MulticastServer don't receive the message. Therefore the client don't get the response and add the IP address to the list.
I would expect that the MulticastServer will always work without that "flakiness". Do I something wrong with the implementation?
Is there a better example on how to implement multiple async requests.
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc ListSayHello1 (HelloRequest1) returns (stream HelloReply1) {}
rpc ListSayHello2 (HelloRequest2) returns (stream HelloReply2) {}
}
// The request message containing the user's name.
message HelloRequest1 {
string name = 1;
}
message HelloRequest2 {
string name = 1;
}
// The response message containing the greetings
message HelloReply1 {
string message = 1;
}
message HelloReply2 {
string message = 1;
}
I am using below async pattern to handle SayHello1, but now I want to add support for SayHello2. What should be the approach?
new CallData(&service_, cq_.get(), *this);
void* tag; // uniquely identifies a request.
bool ok;
while (true) {
// Block waiting to read the next event from the completion queue. The
// event is uniquely identified by its tag, which in this case is the
// memory address of a CallData instance.
// The return value of Next should always be checked. This return value
// tells us whether there is any kind of event or cq_ is shutting down.
GPR_ASSERT(cq_->Next(&tag, &ok));
GPR_ASSERT(ok);
static_cast<CallData*>(tag)->Proceed();
}
In CallData, I doing this
void CallData::Proceed()
{
if (status_ == CREATE) {
status_ = PROCESS;
service_->RequestListSayHello1(&ctx_, &request_, &writer_, cq_, cq_,
this);
}
...
}
It is not necessary to create extra completion queues. We just need to know how to handle what the completion queue returns. We can solve this by making a common base class, that is able to do the necessary functions:
class CallDataBase
{
protected:
virtual void WaitForRequest() = 0;
virtual void HandleRequest() = 0;
public:
virtual void Proceed() = 0;
CallDataBase() {}
};
Each specialization of CallDataBase know how to Proceed, WaitForRequest and HandleRequest. Some of this is common for all requests, so it is convenient to use a templatized class:
template < class RequestType, class ReplyType>
class CallDataT : CallDataBase
{
protected:
enum CallStatus { CREATE, PROCESS, FINISH };
CallStatus status_;
Greeter::AsyncService* service_;
ServerCompletionQueue* completionQueue_;
RequestType request_;
ReplyType reply_;
ServerAsyncResponseWriter<ReplyType> responder_;
ServerContext serverContext_;
// When we handle a request of this type, we need to tell
// the completion queue to wait for new requests of the same type.
virtual void AddNextToCompletionQueue() = 0;
public:
CallDataT(Greeter::AsyncService* service, ServerCompletionQueue* completionQueue) :
status_(CREATE),
service_(service),
completionQueue_(completionQueue),
responder_(&serverContext_)
{
}
public:
virtual void Proceed() override
{
if (status_ == CREATE)
{
status_ = PROCESS;
WaitForRequest();
}
else if (status_ == PROCESS)
{
AddNextToCompletionQueue();
HandleRequest();
status_ = FINISH;
responder_.Finish(reply_, Status::OK, this);
}
else
{
// We're done! Self-destruct!
if (status_ != FINISH)
{
// Log some error message
}
delete this;
}
}
};
And finally, the actual implementation of the message types:
class CallDataHello : CallDataT<HelloRequest, HelloReply>
{
public:
CallDataHello(Greeter::AsyncService* service, ServerCompletionQueue* completionQueue) : CallDataT(service, completionQueue)
{
Proceed();
}
protected:
virtual void AddNextToCompletionQueue() override
{
new CallDataHello(service_, completionQueue_);
}
virtual void WaitForRequest() override
{
service_->RequestSayHello(&serverContext_, &request_, &responder_, completionQueue_, completionQueue_, this);
}
virtual void HandleRequest() override
{
reply_.set_message(std::string("Hello ") + request_.name());
}
};
class CallDataHelloAgain : CallDataT<HelloAgainRequest, HelloAgainReply>
{
public:
CallDataHelloAgain(Greeter::AsyncService* service, ServerCompletionQueue* completionQueue) : CallDataT(service, completionQueue)
{
Proceed();
}
protected:
virtual void AddNextToCompletionQueue() override
{
new CallDataHelloAgain(service_, completionQueue_);
}
virtual void WaitForRequest() override
{
service_->RequestSayHelloAgain(&serverContext_, &request_, &responder_, completionQueue_, completionQueue_, this);
}
virtual void HandleRequest() override
{
reply_.set_message(std::string("Hello again ") + request_.name());
}
};
Finally, in the GRPC server implementation, we can then handle the different requests in a unified way:
void HandleRpcs()
{
new CallDataHello(&service_, completionQueue.get());
new CallDataHelloAgain(engine_, &service_, completionQueue.get());
void* tag;
bool ok;
while (true) {
bool ret = completionQueue->Next(&tag, &ok);
if (ok == false || ret == false)
{
return;
}
static_cast<CallDataBase*>(tag)->Proceed();
}
}
This works because all the CallData we add to completionQueue, is of baseclass CallDataBase, so we can safely call Proceed() on them. This approach makes it relatively easy to add new requests. All the requests are handled by the same completion queue. However, all the requests will be handled serially, so if you are after more parallel processing, I think you have to make more than one completion queue.
I was able to figure this out.
For every protobuf function, I had to create separate completion queue.
I'm trying to get iOS devices to discover each other with Bonjour and then connect with InputStream and OutputStream.
The devices can connect to each other, but sending bytes from one device's OutputStream will not trigger the "hasBytesAvailable" event on the other device.
Because I want devices to connect with multiple other devices, I've wrapped each connection in an "ASPeer" object, which I can put in an array to keep track of all my connections.
class ASPeer: NSObject {
let service: NetService
var inputStream: InputStream?
var outputStream: OutputStream?
init(_ service: NetService) {
self.service = service
}
func openStreams() {
guard let inputStream = inputStream, let outputStream = outputStream else {
fatalError("openStreams: failed to get streams!")
}
inputStream.delegate = self
inputStream.schedule(in: .current, forMode: .defaultRunLoopMode)
inputStream.open()
outputStream.delegate = self
outputStream.schedule(in: .current, forMode: .defaultRunLoopMode)
outputStream.open()
}
func closeStreams() {
guard let inputStream = inputStream, let outputStream = outputStream else {
fatalError("closeStreams: failed to get streams!")
}
inputStream.remove(from: .current, forMode: .defaultRunLoopMode)
inputStream.close()
inputStream.delegate = nil
outputStream.remove(from: .current, forMode: .defaultRunLoopMode)
outputStream.close()
outputStream.delegate = nil
}
}
extension ASPeer: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch aStream {
case inputStream!:
switch eventCode {
case .openCompleted:
print("inputOpenCompleted:")
case .hasBytesAvailable:
print("inputHasBytesAvailable:")
var readData = [UInt8](Data(capacity: 4096))
let bytesRead = inputStream!.read(&readData, maxLength: 4096)
if bytesRead > 0 {
print(String(bytes: readData, encoding: .ascii)!)
}
case .errorOccurred:
print("inputErrorOccurred")
case .endEncountered:
print("inputEndEncountered")
default:
break
}
case outputStream!:
switch eventCode {
case .openCompleted:
print("outputOpenCompleted:")
case .hasSpaceAvailable:
print("outputHasSpaceAvailable:")
case .errorOccurred:
print("outputErrorOccurred")
case .endEncountered:
print("outputEndEncountered")
default:
break
}
default:
print("got unknown stream!")
}
}
}
I've added print statements to every single "handle" event for my input and output streams. Here are the output logs when I run the app and try to get the devices to talk to each other:
Device 1
inputOpenCompleted:
outputOpenCompleted:
outputHasSpaceAvailable:
Device 2
inputOpenCompleted:
outputOpenCompleted:
outputHasSpaceAvailable:
When I try to send a message from Device 1 to Device 2, I'm expecting Device 2 to print out "inputHasBytesAvailable". However, I just get extra lines of "outputHasSpaceAvailable" from Device 1:
Device 1
inputOpenCompleted:
outputOpenCompleted:
outputHasSpaceAvailable:
outputHasSpaceAvailable: <--
outputHasSpaceAvailable: <--
Device 2
inputOpenCompleted:
outputOpenCompleted:
outputHasSpaceAvailable:
<-- I'm expecting "inputHasBytesAvailable" here!
What could the issue be? I've double checked my run loops and made sure they are correct. Also, there seems to be a bug with "getInputStream" and I made sure to call "getInputStream" on the main queue to avoid that problem. Is there something else I'm missing?
In addition, I also have a BonjourManager object that manages every one of these "ASPeer" connections. The BonjourManager is what actually creates the connections and sends writes to the OutputStreams.
class ASBonjourManager: NetServiceDelegate {
var peers = [ASPeer]()
// ... more code here but omitted
func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) {
if sender == advertiser {
return
}
if let peer = peers.first(where: { $0.service == sender }) {
OperationQueue.main.addOperation {
// Due to a bug <rdar://problem/15626440>, this method is called on some unspecified
// queue rather than the queue associated with the net service (which in this case
// is the main queue). Work around this by bouncing to the main queue.
assert((peer.inputStream == nil) == (peer.outputStream == nil))
if let _ = peer.inputStream, let _ = peer.outputStream {
inputStream.open()
inputStream.close()
outputStream.open()
outputStream.close()
} else {
peer.inputStream = inputStream
peer.outputStream = outputStream
peer.openStreams()
}
}
} else {
OperationQueue.main.addOperation {
let newPeer = ASPeer(sender)
sender.delegate = self
newPeer.inputStream = inputStream
newPeer.outputStream = outputStream
newPeer.openStreams()
self.peers.append(newPeer)
}
}
}
func connectTo(service: NetService) {
var inStream: InputStream?
var outStream: OutputStream?
let peer = peers.first(where: { $0.service.isEqual(service) })!
//assert(peer.inputStream == nil && peer.outputStream == nil)
if peer.inputStream != nil && peer.outputStream != nil {
return
}
if service.getInputStream(&inStream, outputStream: &outStream) {
peer.inputStream = inStream
peer.outputStream = outStream
peer.openStreams()
} else {
print("getInputStream failed!")
}
}
func sendMessage(_ service: NetService) {
let peer = peers.first(where: { $0.service.isEqual(service) })!
if peer.outputStream!.hasSpaceAvailable {
let message = Array("hello world".utf8)
peer.outputStream!.write(message, maxLength: message.count)
}
}
}
I have a weird problem with a ChatServer program I am working on (don't know why I suddenly started it but I want to finish it). First, here is the relevant code:
sealed trait ServerMessage
case class Message(msg: String) extends ServerMessage
case object Quit extends ServerMessage
sealed trait ClientMessage
case class Incoming(conn: Connection, msg: String) extends ClientMessage
case class Remove(conn: Connection) extends ClientMessage
object Server extends App with Actor with Settings {
Console.println(greeting)
Console.println("Server starting up...")
val socket = new ServerSocket(defaultPort);
var connections: Set[Connection] = Set.empty
start
actor {
loop {
val s = socket.accept
val c = Connection(s)
Console.println("New Connection from " + s.getInetAddress)
c ! Message(greeting)
connections += c
}
}
def act = loop {
receive {
// For some reason, this only works once
case Incoming(conn, msg) => {
Console.println(conn.socket.getInetAddress.toString + " said: " + msg)
connections.foreach(_ ! Message(msg))
}
case Remove(conn) => connections -= conn; conn ! Quit
}
}
}
case class Connection(socket: Socket) extends Actor {
val in = new BufferedReader(new InputStreamReader(socket.getInputStream))
val out = new PrintWriter(socket.getOutputStream)
start
actor {
var s: String = in.readLine
while (s != null) {
// This output works
scala.Console.println(s)
if (s == "quit") Server ! Remove(this)
else Server ! Incoming(this, s)
s = in.readLine
}
}
def act = {
var done = false
while (!done) {
receive {
// This seems to work all the time (I can send several messages)
case Message(str) => out.println(str); out.flush
case Quit => done = true
}
}
in.close
out.close
socket.close
}
}
The problem I have is when I connect to it via telnet, I can send 1 message, and it comes back to me. But after that, when I send more messages, they don't come back to me. With the help of the debug messages I can identify where the problem is, but I can't see at all why it doesn't work.
Maybe someone can give me a hint? This is the first time I use actors in such a complex way.
EDIT: Could it have to do with the fact that the receive or react functions will never return?
Try replacing receive with react.