InputStream never calls hasBytesAvailable - networking

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)
}
}
}

Related

RxAndroidBle can't disconnect

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

MutlicastSocket receive not always

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?

Appending pointer to a struct slice empty

I have a piece of code that receives a JSON and creates a instance of a struct depending on it's deviceID.
type Ctrl struct {
Instance []*VD
}
var device *VD
if integrationResult == "successful"{
if len(sensorList.Instance) == 0 {
device = VirtualDevice(client, deviceID)
oldDeviceID = deviceID
sensorList.Instance = append(sensorList.Instance, device)
} else if oldDeviceID != deviceID{
device = VirtualDevice(client, deviceID)
sensorList.Instance = append(sensorList.Instance, device)
}
fmt.Println(*sensorList.Instance[0]) //nothing is in here
}
In another file I have:
type Device struct{
Type string `json:"type"`
Value []interface{} `json:"value"`
CaptureTime string `json:"capture-time"`
}
type VD struct {
Passport struct {
MessageTopic string `json:"message-topic"`
PrivateKey string `json:"private-key"`
} `json:"passport"`
Data struct {
Sensor []Device `json:"sensor"`
Actuator struct {
} `json:"actuator"`
} `json:"data"`
}
func VirtualDevice(client MQTT.Client, deviceID string) *VD {
sensorData := new(VD)
var g MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
err := json.Unmarshal(msg.Payload(), &sensorData)
if err != nil {
panic(err)
} else {
//fmt.Printf("%+v\n", *sensorData) //data_update
}
}
client.Subscribe("data-update/" + deviceID, 0, g)
return sensorData
}
The issue that I have is that *sensorList.Instance[0] prints out an empty JSON. Why is this the case?
You're not waiting for sensorData to actually be filled with data before you return it, so you're returning an empty structure. You can wait for it with
token := client.Subscribe("data-update/" + deviceID, 0, g)
token.wait()
if token.Error() != nil {
// do something useful here
}
return sensorData
You can also use WaitTimeout which lets you specify a time.Duration which is the maximum time you will wait for the data before giving up.

Android BLE: writing >20 bytes characteristics missing the last byte array

I have been implementing the module to send the bytes in chunks, 20 bytes each onto the MCU device via BLE. When it comes to writing the bytes more than 60 bytes and so on, the last chunk of the bytes ( usually less than 20 bytes) is often missed. Hence, the MCU device cannot get the checksum and write the value. I have modified the call back to Thread.sleep(200) to change it but it sometimes works on writing 61 bytes or sometimes not. Would you please tell me are there any synchronous method to write the bytes in chunks ? The below is my working :
#Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
try {
Thread.sleep(300);
if (status != BluetoothGatt.GATT_SUCCESS) {
disconnect();
return;
}
if(status == BluetoothGatt.GATT_SUCCESS) {
System.out.println("ok");
broadcastUpdate(ACTION_DATA_READ, mReadCharacteristic, status);
}
else {
System.out.println("fail");
broadcastUpdate(ACTION_DATA_WRITE, characteristic, status);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized boolean writeCharacteristicData(BluetoothGattCharacteristic characteristic ,
byte [] byteResult ) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
return false;
}
boolean status = false;
characteristic.setValue(byteResult);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
status = mBluetoothGatt.writeCharacteristic(characteristic);
return status;
}
private void sendCommandData(final byte [] commandByte) {
// TODO Auto-generated method stub
if(commandByte.length > 20 ){
final List<byte[]> bytestobeSent = splitInChunks(commandByte);
for(int i = 0 ; i < bytestobeSent.size() ; i ++){
for(int k = 0 ; k < bytestobeSent.get(i).length ; k++){
System.out.println("LumChar bytes : "+ bytestobeSent.get(i)[k] );
}
BluetoothGattService LumService = mBluetoothGatt.getService(A_SERVICE);
if (LumService == null) { return; }
BluetoothGattCharacteristic LumChar = LumService.getCharacteristic(AW_CHARACTERISTIC);
if (LumChar == null) { System.out.println("LumChar"); return; }
//Thread.sleep(500);
writeCharacteristicData(LumChar , bytestobeSent.get(i));
}
}else{
....
You need to wait for the onCharacteristicWrite() callback to be invoked before sending the next write. The typical solution is to make a job queue and pop a job off the queue for each callback you get to onCharacteristicWrite(), onCharacteristicRead(), etc.
In other words, you can't do it in a for loop unfortunately, unless you want to set up some kind of lock that waits for the callback before going on to the next iteration. In my experience a job queue is a cleaner general-purpose solution though.

Problem with Actors and Networking

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.

Resources