I have to say sorry before, my english's not good.
I'm working on a project related to "external accessory framework".
I need to fetch some data stream from our product through a "USB to lightning" adapter.
I've been spinning my wheel for several days.
My question here is "what's the exact way to use the framework".
I found something on apple dev forum:
pic
Does it mean that if i have a "USB to lightning" adapter and its protocol string(For now, we're not going to make our own adapter, we plan to work with another company), then I plug our product to it, my app would be notified of the attachment or removal of the accessory.
Am i right?
I'm confusing right now, cause a colleague of mine contacted a adapter vendor, and they told her there is NO such thing called "protocol string".
I guess for some reason they just don't want us to know.
thanks!
You have set up these notifications for connection. Make you make connection through BT or USB.
/* Setup notification monitor */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryConnectNotification:) name:EAAccessoryDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryDisconnectNotification:) name:EAAccessoryDidDisconnectNotification object:nil];
/* Turn on EA notifications */
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];
Once connection establishes you will get a call back with accessory info.
- (void) handleAccessoryConnectNotification:(NSNotification*)notification
{
NSDictonary *earInfo = notification.userInfo;
Print(#“Accessory Info: %#”,earInfo);
// Get connected Accessory object.
EAAccessory* currentAccessory = eaInfo[#"EAAccessoryKey"];
// Create Session with base Protocol with Accessory.
EASession* currentEASession = [[EASession alloc] initWithAccessory:currentAccessory forProtocol:“com.accessory.base”];
/* Open input and output steam */
if ( self.currentEASession.inputStream && self.currentEASession.outputStream)
{
[[currentEASession inputStream] setDelegate:self];
[[currentEASession inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession inputStream] open];
[[currentEASession outputStream] setDelegate:self];
[[currentEASession outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession outputStream] open];
}
}
- (void)handleAccessoryDisconnectNotification:(NSNotification*)notification {
Print(#“Accessory Info: %#”notification.userInfo);
}
# Read and Write data delegate method
- ( void ) stream: ( NSStream * ) aStream handleEvent: ( NSStreamEvent ) eventCode {
#try
{
switch ( eventCode )
{
case NSStreamEventOpenCompleted:{
Print(#“[%s] NSStreamEventOpenCompleted",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventHasSpaceAvailable:
{
/* Write data */
break;
}
case NSStreamEventHasBytesAvailable:
{
//TODO
/* Read data */
static uint8_t buffer[65536];
NSInteger n = 65536;
while (n == 65536)
{
n = [self.currentEASession.inputStream read:buffer maxLength:65536];
}
break;
}
case NSStreamEventErrorOccurred:
{
//TODO
Print(#"[%s] NSStreamEventErrorOccurred",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventEndEncountered:
{
//TODO
Print(#"[%s] NSStreamEventEndEncountered",__PRETTY_FUNCTION__);
break;
}
default:
{
break;
}
}
}
#catch (NSException *exception)
{
}
}
Relation between external accessory and protocol is.
When you connect iOS device with accessory. You have to create a session between accessory and iOS device to read and write data. Protocol string will identifier unique connection between accessory and iOS device for sharing and writing data.
Same time one accessory can be connected with multiple iOS devices. Each device will have a unique connection or session in between and it will identify through protocol.
Related
I've got a Java server (and it's able to correctly read a request from my iOS client -- it even generates a response and appears to send it correctly, though I got First message response from server every time but not getting other messages after receiving first message):
sequence of communication
Step 1-> client send login message to server
Step 2-> server validate the user and sends login info to the clients
Step 3-> Message Packet1
Message Packet2
Message Packet3
Message Packet4
step-4-> I have checked server log and it says server has send 4 string
messages
Step-5 -> On client side I am receiving only first message i.e. Message
Packet1, and there no other packets on NSInputStream showing. or NSStreamEventHasBytesAvailable option in delegate method->
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
Not calling most of the time more than one but sometimes It calls and gets MessagePacket2 or MessagePacket4 data.
Please help me out, I am unable to figure out why I am receiving only first packet from server instead of 4 packets, as server sending 4 packets.
I have used code from the below tutorial ->
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server#comments
My code is here->
#interface NetworkManager()<NSStreamDelegate>
#property (strong, nonatomic)NSInputStream *objInputStream;
#property (strong, nonatomic)NSOutputStream *objOutputStream;
#end
- (void)initializeNetworkCommunicationToServer
{
self.networkOpened = NO;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)SERVER_HOSTNAME,
SERVER_PORT_ADDR,
&readStream,
&writeStream);
self.objInputStream = (__bridge_transfer NSInputStream *)readStream;
self.objOutputStream = (__bridge_transfer NSOutputStream*)writeStream;
[self.objInputStream setDelegate:self];
[self.objOutputStream setDelegate:self];
[self.objInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objInputStream open];
[self.objOutputStream open];
}
//------------------------------------------------------
pragma NSStreamDelegate delegate method
//------------------------------------------------------
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
case NSStreamEventNone:
{
NSLog(#"NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
NSLog(#"NSStreamEventOpenCompleted");
}
break;
case NSStreamEventHasBytesAvailable:
{
NSLog(#"NSStreamEventHasBytesAvailable:");
if (theStream == self.objInputStream)
{
while ([self.objInputStream hasBytesAvailable])
{
uint8_t buffer[1024];
unsigned int len = 0;
len = [self.objInputStream read:buffer
maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output =
[[NSString alloc] initWithBytes:buffer
length:len
encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(#"server said: %#", output);
}
}//end of if(len > 0)
}//end of while
} //end of if (theStream == self.objInputStream)
}
break;
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred: Can not connect to the host!");
}
break;
case NSStreamEventEndEncountered:
{
NSLog(#"NSStreamEventEndEncountered & network connection ended");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
theStream = nil;
}
break;
// The NSStreamEventHasSpaceAvailable event indicates that you can write (at least one byte!) to the stream without blocking. That does not mean that previously written data is completely delivered to the other endpoint of the connection.
case NSStreamEventHasSpaceAvailable:
{
NSLog(#"NSStreamEventHasSpaceAvailable");
if(NO == self.networkOpened)
{
self.networkOpened = YES;
[self sendMessage:#"login:username,password"];
}
}
break;
default:
{
NSLog(#"Unknown event");
}
}
}
//------------------------------------------------------
#pragma mark - send packet
//------------------------------------------------------
- (void)sendMessage:(NSString*)lstrMessage
{
NSMutableData *data = [[NSMutableData alloc] initWithData:
[lstrMessage dataUsingEncoding:NSASCIIStringEncoding]];
unsigned char suffixBytes[] = {1, 1, 0};
[data appendBytes:suffixBytes length:3];
[self.objOutputStream write:[data bytes] maxLength:[data length]];
NSLog(#"message sent->%#",[NSString stringWithUTF8String:[data bytes]]);
}
I have no idea what is wrong with your code, but my experience with streams tuning tells me that there will be make sence to try another way to subscribe to Core Foundation streams using CF API instead of toll-free-bridging it to NSInputStream. I mean CFReadStreamSetClient and CFReadStreamScheduleWithRunLoop functions. You can see example how to do so in my helper class for testing POSInputStreamLibrary.
I am trying to use ReactiveCococa for network connection using NSInputStream & NSOutputStream . The connect code looks as follows:
-(RACSignal*) connect: (NSString *) url {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> theSubscriber) {
self.subscriber = theSubscriber;
// create inputStream and outputStream, initialize and open them
[self.inputStream open]
[self.outputStream open];
}];
return nil;
}
-(void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventHasBytesAvailable:
//read from input stream
NSArray * data = read_InputStream;
[subscriber sendNext:data];
break;
}
...
}
I have to store the value of subscriber and call sendNext on it, when I receive data in the callback method.
Is there a better way to handle this in ReactiveCocoa and avoid declaring subscriber property. Besides, this will not work with multiple subscribers.
You can use rac_signalForSelector to turn a delegate callback method into a signal. Then you can subscribe to this signal inside createSignal's didSubscribe block. Something like this:
- (RACSignal*)connect:(NSString *)url
{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> theSubscriber)
{
// create inputStream and outputStream, initialize and open them
[self.inputStream open];
[self.outputStream open];
[[self rac_signalForSelector:#selector(stream:handleEvent:)
fromProtocol:#protocol(NSStreamDelegate)]
subscribeNext:^(RACTuple *tuple)
{
NSStream *aStream = (NSStream *)tuple.first;
NSStreamEvent *eventCode = (NSStreamEvent *)tuple.second;
// check eventCode here
NSArray *data = read_InputStream;
[theSubscriber sendNext:data];
}];
return nil;
}];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
}
When using rac_signalForSelector, the signal will pass the arguments from the method through as a tuple which you can then look at to decide what action to take.
Depending on what you're trying to achieve here, there are probably more elegant reactive solutions, but rac_signalForSelector at least solves the problem of needing the subscriber property.
I am trying to create a simple multiplayer turn-based game between iPhones. Right now all I want to do is pass in some string to my method, and have the method send the string through the NSOutputStream. I think I have properly connected my NSNetServices using NSNetServiceBrowser. Once they connect, my NSNetServiceDelegate has netService:didAcceptConnectionWithInputStream:outputStream: called, which should give me my i/o NSStream pair. My method looks like this:
-(void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream{
[self.myNet getInputStream:&inputStream outputStream:&outputStream];
self.inStream = inputStream;
self.outStream = outputStream;
[self.inStream setDelegate:self];
[self.inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inStream open];
}
I think I have correctly set up NSInputStream. I also have a delegate for NSStream which is implementing stream:handleEvent:
It looks like this:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
NSInputStream *inStream = (NSInputStream *)aStream;
BOOL shouldClose = NO;
switch(eventCode) {
case NSStreamEventEndEncountered:
shouldClose = YES;
// If all data hasn't been read, fall through to the "has bytes" event
if(![inStream hasBytesAvailable]) break;
case NSStreamEventHasBytesAvailable: ; // We need a semicolon here before we can declare local variables
uint8_t *buffer;
NSUInteger length;
BOOL freeBuffer = NO;
// The stream has data. Try to get its internal buffer instead of creating one
if(![inStream getBuffer:&buffer length:&length]) {
// The stream couldn't provide its internal buffer. We have to make one ourselves
buffer = malloc(BUFFER_LEN * sizeof(uint8_t));
freeBuffer = YES;
NSInteger result = [inStream read:buffer maxLength:BUFFER_LEN];
if(result < 0) {
// error copying to buffer
break;
}
length = result;
}
// length bytes of data in buffer
if(freeBuffer) free(buffer);
break;
case NSStreamEventErrorOccurred:
// some other error
shouldClose = YES;
break;
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
}
if(shouldClose){
[inStream close];
}
}
I took that code from: this page. In that code, aStream should be self.inStream. I have looked at pages describing what to do for NSOutputStream, but none of them seem to be geared at a beginner like me. I have a few questions. Firstly, how do I set up a method that I pass the data in (NSData or maybe an NSString) and it sends it out through self.outStream. I would prefer an answer that explains the code, rather than just giving it to me. Secondly, should I open both of my streams in netService:didAcceptConnectionWithInputStream:outputStream:, and should I also scheduleInRunLoop the NSOutputStreamself.outStream`. Finally, am I doing everything wrong?
Thanks for your answers. Also this is my first question, so any constructive criticism is welcomed.
To write to output stream, check stream has space available and write data using
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)length
So, code may be like following.
if ( self.outStream.hasSpaceAvailable ) [ self.outStream write:... maxLength: ];
If output stream doesn't have space available, later
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
method of the delegate of output stream would be called with eventCode = NSStreamEventHasSpaceAvailable.
You should open output stream and schedule it in netService:didAcceptConnectionWithInputStream:outputStream or when you receive NSStreamEventOpenCompleted of input stream. Both are OK.
If you don't want writing block thread, you need to prepare queue.
So code is like following
NSMutableData* uQueue;
NSInputStream* uIStream;
NSOutputStream* uOStream;
:
:
:
uIStream.delegate = self;
uOStream.delegate = self;
[ uIStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode ];
[ uOStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode ];
[ uIStream open ];
[ uOStream open ];
uQueue = NSMutableData.data;
:
:
:
- (void)
Send
{ if ( uQueue.length )
{ NSInteger wLength = [ uOStream write:(const uint8_t*)uQueue.bytes maxLength:uQueue.length ];
if ( wLength > 0 ) [ uQueue replaceBytesInRange:NSMakeRange( 0, wLength ) withBytes:NULL length:0 ];
}
}
- (void)
Write:(NSData*)p
{ [ uQueue appendData:p ];
if ( uOStream.hasSpaceAvailable ) [ self Send ];
}
- (void)
stream:(NSStream*)pS
handleEvent:(NSStreamEvent)p
{ switch( p )
{
:
:
:
case NSStreamEventHasSpaceAvailable:
[ self Send ];
break;
}
}
I am using NSInputStream to grab some resources from web like this:
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
CFRelease(request);
_inputStream = CFBridgingRelease(readStream);
_inputStream.delegate = self;
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
And then to offload the actual work to other thread:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSLog(#"stream: %# handleEvent: %d", aStream, eventCode);
dispatch_async(_custom_serial_queue, ^{
switch(eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(#"Stream has opened");
break;
}
// rest of switch
});
}
I see Stream has opened log and then in 95% of cases my main thread freezes
This happens only on iPhone4 and iOS6.1, on iPhone5s and iOS7 and iPad3 iOS6.1 everything is fine.
Could you please suggest what is wrong with this code.
EDIT 1: I have tracked down problem to some piece of code(in 3rd party lib) which uses __sync_add_and_fetch function. It seems it causes the issue but I still need to use it.
EDIT 2: I have changed __sync_add_and_fetch function by OSAtomicAdd32 but it hasn't changed anything.
EDIT 3: I have checked the problem on other devices. Works fine on iPhone4s iOS7, iPad2 iOS6.1. It seems the problem is only relevant to 1 core devices.
I have a project that connects to an external accessory and communicates a small amount of data to and from an iOS app. I am able to setup the session and streams like apple does in their EADemo reference code and everything seems to work fine.
The problem I have is that after a random amount of time using the app, the output stream stops working, but the input stream still operates fine. I check to make sure hasSpaceAvailable is true before each write attempt and when I read back the number of bytes written, everything looks correct. Also, looking at the run loop doesn't indicate any differences between working and non working, and the stream status still reads as open.
The only thing that I can see that causes this is that my accessory doesn't ACK a few of the app's write attempts in a row, and then it breaks.
How can I detect I am in this state and how can I fix it?
// low level write method - write data to the accessory while there is space available and data to write
- (void)_writeData {
while (([[_session outputStream] hasSpaceAvailable]) && ([_dataToWrite length] > 0))
{
NSInteger bytesWritten = [[_session outputStream] write:[_dataToWrite bytes] maxLength:[_dataToWrite length]];
if (bytesWritten == -1)
{
NSLog(#"write error");
break;
}
else if (bytesWritten > 0)
{
[_dataToWrite replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
}
}
}
// low level read method - read data while there is data and space available in the input buffer
- (void)_readData {
NSLog(#"reading data to buffer");
#define EAD_INPUT_BUFFER_SIZE 128
uint8_t buf[EAD_INPUT_BUFFER_SIZE];
while ([[_session inputStream] hasBytesAvailable])
{
NSInteger bytesRead = [[_session inputStream] read:buf maxLength:EAD_INPUT_BUFFER_SIZE];
if (_dataToRead == nil) {
_dataToRead = [[NSMutableData alloc] init];
}
[_dataToRead appendBytes:(void *)buf length:bytesRead];
}
[[NSNotificationCenter defaultCenter] postNotificationName:EASessionDataReceivedNotification object:self userInfo:nil];
}
// high level write data method
- (void)writeData:(NSData *)data
{
// NSLog(#"writing data to buffer");
if (_dataToWrite == nil) {
_dataToWrite = [[NSMutableData alloc] init];
}
[_dataToWrite appendData:data];
[self _writeData];
}
// high level read method
- (NSData *)readData:(NSUInteger)bytesToRead
{
NSLog(#"reading data");
NSData *data = nil;
if ([_dataToRead length] >= bytesToRead) {
NSRange range = NSMakeRange(0, bytesToRead);
data = [_dataToRead subdataWithRange:range];
[_dataToRead replaceBytesInRange:range withBytes:NULL length:0];
}
return data;
}
- (BOOL)openSession
{
NSLog(#"openSession");
[_accessory setDelegate:self];
if(_session){
[self closeSession];
}
_session = [[EASession alloc] initWithAccessory:_accessory forProtocol:_protocolString];
if (_session)
{
_runLoop = [NSRunLoop currentRunLoop];
[[_session inputStream] setDelegate:self];
[[_session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[_session inputStream] open];
[[_session outputStream] setDelegate:self];
[[_session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[_session outputStream] open];
NSLog(#"creating session succeeded!");
}
else
{
NSLog(#"creating session failed!");
}
return (_session != nil);
}
I think I may be experiencing the same problem. I have a bluetooth connected accessory, and when I am testing range I often end up in a situation with exactly the same symptoms that you describe in your question.
But how do you detect that the problem is caused by the accessory failing to ACK data?
I am guessing in my situation the Accessory is sending ACK's but the because I am on the edge of bluetooth range my phone never receives the ACK.
Right now my best bet is to try and detect the situation in the APP. In my situation I can do this because the accessory will resend the same package if it does not receive any data from the phone. So if I see the same data transmitted from the accessory a number of times I will drop the connection and ask the user to move closer to the accessory, and reconnect.