I'm writing a little application for osx to listen and send commands over a serial connection to an Arduino interface. That works great using AMSerialPort.
But now I need to forward some data to a second Arduino, so I need a second serial connection.
Is this possible with this framework? I've searched almost everywhere how to open a second port and get informed through the delegates.
I can't really answer this question with regard to AMSerialPort. Though I've used AMSerialPort a lot in the past, it's been a while, and I've never tried to open two ports. However, I've just released my own Objective-C serial port library that definitely supports opening multiple ports simultaneously. It's called ORSSerialPort and you can get it here: https://github.com/armadsen/ORSSerialPort .
There's no trick to opening two serial ports with ORSSerialPort. Use the ORSSerialPortManager's availablePorts property to get all the available ports on the system. In the serialPort:didReceiveData: delegate method, you can check which port received the data. Something like this:
NSArray *availablePorts = [[ORSSerialPortManager sharedSerialPortManager] availablePorts];
// The next two lines are simplified by assuming availablePorts has two ports
// and you don't care which is which. Real code will be more sophisticated
self.port1 = [availablePorts objectAtIndex:0];
self.port2 = [availablePorts objectAtIndex:1];
self.port1.delegate = self;
self.port2.delegate = self;
[self.port1 open];
[self.port2 open];
- (void)serialPort:(ORSSerialPort *)port didReceiveData:(NSData *)data
{
if (port == self.port1) {
// Data is from port1
} else if (port == self.port2) {
// Data is from port2
}
}
Related
I'm working on porting a VB program to the PC. It uses serial communication to interact with a physical device. I have a version up and running on the Mac using ORSSerialPort. However, once piece of the VB library that is great is the SerialPort.ReadExisting() function. This essentially reads any messages and discards them.
Has anyone built something similar on the Mac side? I've tried pulling out the ORSSerialPort into a function to directly read values (see below). However, unless I send a message I receive a null response. The readExisting function would be great for a scenario when things get a little out of alignment such as:
I send a message "Message1" to the device and nothing happens (was expecting Response1).
I send a message "Message2" to the device and receive: "Response1" instead of "Response2"
I'd like to detect this, call an equivalent to SerialPort.readExisting() since Response2 is the next to be found if I continue.
My read function:
-(NSString *) directRead
{
// Read Directly
int localPortFD = self.fileDescriptor;
struct timeval timeout;
int result=0;
fd_set localReadFDSet;
FD_ZERO(&localReadFDSet);
FD_SET(localPortFD, &localReadFDSet);
timeout.tv_sec = 0;
timeout.tv_usec = 100000; // Check to see if port closed every 100ms
result = select(localPortFD+1, &localReadFDSet, NULL, NULL, &timeout);
if (!self.isOpen) return nil; // Port closed while select call was waiting
if (result < 0)
{
NSLog(#"No Data To Read");
}
if (result == 0 || !FD_ISSET(localPortFD, &localReadFDSet)) return nil;
// Data is available
char buf[1024];
long lengthRead = read(localPortFD, buf, sizeof(buf));
if (lengthRead>0)
{
NSData *readData = [NSData dataWithBytes:buf length:lengthRead];
if (readData != nil)
return [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding];
}
return nil;
}
You would think just doing:
NSString *result = nil;
do
{
result = [serialPort directRead];
NSLog(#"Past Message is: %#", result);
} while(result != nil);
Would flush out the messages. However, it acts as if there aren't any messages. However, if I call sendData:Message1 again I'd still see Response2 show up (in the above scenario).
Thanks for any and all help.
I'm having a little trouble following exactly what you're trying to do here, but in any case, it's something you'll have to implement at a higher level than ORSSerialPort or the POSIX serial read functions. ORSSerialPort will tell you any time bytes are available, as reported by the underlying standard POSIX file APIs it uses. So, there's nothing to "flush" from its perspective. If you get a response you were not expecting, e.g. a response to an "old" request, there's no way at all for the serial port to know that, it's up to your code to figure it out.
Something like this:
- (void)serialPort:(ORSSerialPort *)serialPort didReceiveData:(NSData *)data
{
if (![self dataIsResponseToLatestRequest:data]) return; // Try again next time around
// Process a valid (expected response)
}
This raises a couple other issues. You can't be guaranteed that data will come in "whole packets". The serial hardware, low level serial APIs, and ORSSerialPort have no way of knowing what a whole packet looks like, and therefore just deliver data a bit at a time as it arrives. See this answer for more on that. So, it's up to you to buffer incoming data and assemble it into packets.
If you need to match requests you sent with responses coming in, you have to do that yourself. (sounds like you're really already doing this).
Lastly, if you're only receiving "Response1" after sending "Message2", this leads me to think there's a problem with the device on the other side of the connection not responding to requests properly. It would probably be worth fixing that, if you can (assuming it's your hardware/software).
Im using MCNearbyServiceBrowser and MCNearbyServiceAdvertiser to join two peers to a MCSession. I am able to send data between them using MCSession's sendData method. All seems to be working as expected until I randomly (and not due to any event I control) receive a MCSessionStateNotConnected via the session's MCSessionDelegate didChangeState handler. Additionally, the MCSession's connectedPeers array no longer has my peers.
Two questions: Why? and How do i keep the MCSession from disconnecting?
This is a bug, which I just reported to Apple. The docs claim the didReceiveCertificate callback is optional, but it's not. Add this method to your MCSessionDelegate:
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
The random disconnects should cease.
UPDATE After using a support ticket to Apple, they confirmed that calling sendData too often and with too much data can cause disconnects.
I have had disconnects when hitting break points and when backgrounding. Since the break points won't happen on the app store, you need to handle the backgrounding case by beginning a background task when your app is about to enter the background. Then end this task when your app comes back to the foreground. On iOS 7 this gives you about 3 background minutes which is better than nothing.
An additional strategy would be to schedule a local notification for maybe 15 seconds before your background time expires by using [[UIApplication sharedApplication] backgroundTimeRemaining], that way you can bring the user back into the app before it suspends and the multi peer framework has to be shutdown. Perhaps the local notification would warn them that their session will expire in 10 seconds or something...
If the background task expires and the app is still in the background, you have to tear down everything related to multi-peer connectivity, otherwise you will get crashes.
- (void) createExpireNotification
{
[self killExpireNotification];
if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
self.expireNotification = n;
self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
self.expireNotification.alertBody = TR(#"Text_MultiPeerIsAboutToExpire");
self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
self.expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
}
}
- (void) killExpireNotification
{
if (self.expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
self.expireNotification = nil;
}
}
- (void) applicationWillEnterBackground
{
self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self shutdownMultiPeerStuff];
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void) applicationWillEnterForeground
{
[self killExpireNotification];
if (self.taskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}
}
- (void) applicationWillTerminate
{
[self killExpireNotification];
[self stop]; // shutdown multi-peer
}
You'll also want this handler in your MCSession delegate due to Apple bug:
- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
if (certificateHandler != nil) { certificateHandler(YES); }
}
There are many causes of this, and the two answers thus far are both correct in my experience. Another which you'll find in other similar questions is this: Only one peer can accept another's invitation.
So, to clarify, if you set up an app where all devices are both advertisers and browsers, any devices can freely invite any others found to join a session. However, between any two given devices, only one device can actually accept the invitation and connect to the other device. If both devices accept each others' invitations they will disconnect within a minute or less.
Note that this limitation does not prevent the desired behavior because - unlike what my intuition stated before I built my multipeer implementation - when one device accepts an invitation and connects to another device they both become connected and receive connection delegate methods and can send each other messages.
Therefore, if you are connecting devices which both browse and advertise, send invitations freely but only accept one of a pair.
The problem of only accepting one of two invitations can be solved a myriad of ways. To begin, understand that you can pass any arbitrary object or dictionary (archived as data) as the context argument in an invitation. Therefore, both devices have access to any arbitrary information about the other (and of course itself). So, you could use at least these strategies:
simply compare: the display name of the peerID. But there's no guarantee these won't be equal.
store the date your multipeer controller was initialized and use that for comparison
give each peer a UUID and send this for comparison (my technique, in which each device - indeed each user of the app on a device - has a persistent UUID it employs).
etc - any object which supports both NSCoding and compare: will do fine.
I've been having similar problems. It seems though that if I have run my app on one iOS device, and connected to another, then quit and relaunch (say when I rerun from Xcode), then I am in a situation where I get a Connected message and then a Not Connected message a little later. This was throwing me off. But looking more carefully, I can see that the Not Connected message is actually meant for a different peerId than the one that has connected.
I think the problem here is that most samples I've seen just care about the displayName of the peerID, and neglect the fact that you can get multiple peerIDs for the same device/displayName.
I am now checking the displayName first and then verifying that the peerID is the same, by doing a compare of the pointers.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(#"remnant connection drop");
return; // note that I don't care if player is nil, since I don't want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...
I was disconnecting immediately after I accepted the connection request. Observing the state, I saw it change from MCSessionStateConnected to MCSessionStateNotConnected.
I am creating my sessions with:
[[MCSession alloc] initWithPeer:peerID]
NOT the instantiation method dealing with security certificates:
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference
Based on Andrew's tip above, I added the delegate method
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
and the disconnects stopped.
I am using Apple's reachability code, and am setting up both initial notifications on when network reachability changes and prior to initiating a server connection. The code works when I am on wi-fi and I turn the wi-fi access point off. However, when I start the app with wi-fi and the underlying broadband connection working, and then once the app is running, and then disconnect the wi-fi router from the broadband router (i.e. Wi-Fi is on but there is no internet connectivity), and I do a reachability check, the network status I get is ReachableViaWiFi. I have tried both reachabilityForInternetConnection and reachabilityWithHostName.
Any ideas on if Apple's reachability code can be used to detect a situation where the wifi is connected but there is no underlying network connectivity?
Thanks!
ok, I found out the answer to this - Apple's reachability does not test actual connectivity to the host. See the answer by #Zhami in the SO link below:
How to write a simple Ping method in Cocoa/Objective-C
Essentially, when you first launch the app and do a reachability check, iOS seems to do a DNS lookup, and if there is no internet, the check fails. So the first time you check reachability , it actually returns a meaningful value. However, if you are conected at app launch, and lose internet connectivity after some time (while still connected to WiFi/3G/4G but no underlying internet connectivity), further reachability checks return reachable even though the internet or your specified host is not reachable anymore.
So if you want to really check for connectivity in real time, consider using the following:
-(BOOL) isConnected
{
NSString* url = [NSURL URLWithString:#"http://www.google.com/m"];
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]];
[request setTimeOutSeconds:10];
//customize as per your needs - note this check is synchronous so you dont want to block the main thread for too long
[request setNumberOfTimesToRetryOnTimeout:0];
[request startSynchronous];
NSError *error = [request error];
if (error)
{
DLog(#"connectivity error");
return NO;
}
else
{
DLog(#"connectivity OK");
return YES;
}
}
This is a very old post, but could stay here for reference. In the reachability example class you can find the code below:
- (BOOL)startNotifier
{
BOOL returnValue = NO;
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
{
if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
{
returnValue = YES;
}
}
return returnValue;
}
This will keep your _reachabilityRef updated to network changes.
I'm trying to set up a TCP connection to a local server, and since pretty much everyone agrees that Asyncsocket is the way to go, I went for it, too. However, I'm running into problems at the most basic level: the Asyncsocket instance doesn't have a local or remote address. I don't have much code, but this is what I do have:
- (void)viewDidLoad {
[super viewDidLoad];
AsyncSocket *socket = [[AsyncSocket alloc] initWithDelegate:self];
[socket connectToHost:#"www.google.com" onPort:80 error:nil];
}
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock {
NSLog(#"%#", sock);
return YES;
}
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
NSLog(#"%#", host);
}
The following shows up in the console (from the NSLog in onSocketWillConnect):
AsyncSocket 0x298de0 local nowhere remote nowhere has queued 0 reads 0 writes, no current read, no current write, read stream 0x299720 not open, write stream 0x299aa0 not open, not connected
Where it says "nowhere", of course it should have IP addresses, but it doesn't. From examples online it looks like my code is fine, yet I get this "nowhere" thing when running on two computers and my phone, so I'm probably doing something wrong. Anybody have an idea?
Thanks!
edit: For clarification: if I use error reporting (by passing an NSError instance to "connectToHost:onPort:error" and NSLogging that), all I get back is (null).
Wow, that's embarrassing. I needed to make an ivar to store the AsyncSocket, because without it, it went out of scope after viewDidLoad completed, which was why my onSocket:didConnectToHost:port wasn't called.
I am creating a socket connection with objective C, using asyncsocket. I do this using the "connectToHost" method. I am trying to handle the case where the socket connection fails. "connectToHost" is supposed to return "YES" when it connects sucessfully, and a NO otherwise. For some reason, it is always returning a yes. I even supplied a blank string as the host and it still returns yes. Any thoughts?
Thanks,
Robin
BOOL connectStatus = NO; //used to check if connection attempt succeeded
testSocket = [[AsyncSocket alloc] initWithDelegate: self];
connectStatus = [testSocket connectToHost: #"" onPort: 5000 error: nil];
if(connectStatus == NO)
{
NSLog(#"Failed to connect to socket ");
}
else {
NSLog(#"Connected to socket sucessfully, connectStatus = %d", connectStatus);
}
Per the header file:
// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in
// and the other accept/connect methods can't be called without disconnecting the socket first.
// If the attempt fails or times out, these methods either return NO or
// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:".
If you check the source – for the love of all things holy, when working with open source software, USE THE SOURCE! –, you will see that the method you are invoking returns NO only when it fails to start the connection process. A return value of YES just says, "Okay, I'm trying to connect:
"If things go wrong, I'll let you know by calling onSocket:willDisconnectWithError: and onSocketDidDisconnect:.
"If all goes well, you'll get a onSocket:didConnectToHost:port: message, mmkay?"
Don't expect synchronous behavior from an asynchronous socket library.