I have a label printer, a Brady i3300, that I am connecting to from my iPad using a tcp socket (using the CocoaAsyncSocket framework). Each time I send data to the printer, it prints, but the label is always blank.
Process
First, I run connectToPrinter, and the didConnectToHost delegate fires. After the successful connection, I call printLabelToPrinter, where the printer is sent the data. The didWriteDataWithTag delegate is called just after this, the printer begins / finishes printing, but the label is blank.
It has not mattered thus far what content is in the data file (explained below), so it feels like a connection issue...Regardless, the printer seems to be receiving the request without any data along with it.
Why is the printer printing blank labels?
Code & Files
Here is my connection setup:
- (void)connectToPrinter {
// Create the socket
self.socket = [GCDAsyncSocket.alloc initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
// Connect to the printer via IP and port
NSError *err = nil;
if (![self.socket connectToHost:#"10.0.0.147" onPort:9100 error:&err]) {
// Something went wrong!
NSLog(#"I goofed: %#", err);
}
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(#"connected to host!: %#", host);
[self printLabelToPrinter];
}
Note: the .zpl file ref below contains a ZPL printing script, one of the supported languages of Brady printers
- (void)printLabelToPrinter {
// Get the ZPL file and convert it over to data
NSString *filePath = [NSBundle.mainBundle pathForResource:#"label" ofType:#".zpl"];
NSData *data = [NSFileManager.defaultManager contentsAtPath:filePath];
// Write the data to the printer
[self.socket writeData:data withTimeout:10 tag:1];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(#"did write data: %ld", tag);
}
Here is the .zpl file
^XA
^CF0,60
^FO50,50^GB100,100,100^FS
^FO75,75^FR^GB100,100,100^FS
^FO50,300
^FO504,300^FDTest
^XZ
Related
I'm trying to write a simple pair of "client app" & "XPC service". I was able to launch xpc service from client (i.e I can see service running in the Activity monitor processes list), but when I try to send any request, that has a response block, I get an error: "Couldn’t communicate with a helper application."
The worst thing here is that error doesn't give me any info about what went wrong. And I'm also unable to debug the service properly. As I understand, the correct way to do this is to attach a debugger to process (Debug->Attach to process, also see here). I have both client and service projects in a single workspace.
When I run client from xcode and try to attach debugger to launched service, that ends with a "Could not attach to pid : X" error.
If I archive the client app run it from app file and then try to attach debugger to service the result is the same.
The only way to record something from the service I could imagine is to write a logger class, that would write data to some file. Haven't tried this approach yet, however that looks insane to me.
So my question is:
a) How to find out what went wrong, when receiving such non-informative response like: "Couldn’t communicate with a helper application"?
b) And also, what's the correct way to debug the xpc service in the first place? The link above is 5 years old from now, however I can see that some people were saying that "attach to debugger" wasn't working.
The code itself is fairly simple:
XPC service, listener implementation:
#import "ProcessorListener.h"
#implementation ProcessorListener
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
[newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:#protocol(TestServiceProtocol)]];
[newConnection setExportedObject: self];
self.xpcConnection = newConnection;
newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Progress)];
// connections start suspended by default, so resume and start receiving them
[newConnection resume];
return YES;
}
- (void) sendMessageWithResponse:(NSString *)receivedString reply:(void (^)(NSString *))reply
{
reply = #"This is a response";
}
- (void) sendMessageWithNoResponse:(NSString *)mString
{
// no response here, dummy method
NSLog(#"%#", mString);
}
And the main file for service:
#import <Foundation/Foundation.h>
#import "TestService.h"
#interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
#end
#implementation ServiceDelegate
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
// This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.
// Configure the connection.
// First, set the interface that the exported object implements.
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestServiceProtocol)];
// Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.
TestService *exportedObject = [TestService new];
newConnection.exportedObject = exportedObject;
// Resuming the connection allows the system to deliver more incoming messages.
[newConnection resume];
// Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO.
return YES;
}
#end
int main(int argc, const char *argv[])
{
// [NSThread sleepForTimeInterval:10.0];
// Create the delegate for the service.
ServiceDelegate *delegate = [ServiceDelegate new];
// Set up the one NSXPCListener for this service. It will handle all incoming connections.
NSXPCListener *listener = [NSXPCListener serviceListener];
listener.delegate = delegate;
// Resuming the serviceListener starts this service. This method does not return.
[listener resume];
return 0;
}
For client app, the UI contains a bunch of buttons:
- (IBAction)buttonSendMessageTap:(id)sender {
if ([daemonController running])
{
[self executeRemoteProcessWithName:#"NoResponse"];
}
else
{
[[self.labelMessageResult cell] setTitle: #"Error"];
}
}
- (IBAction)buttonSendMessage2:(id)sender {
if ([daemonController running])
{
[self executeRemoteProcessWithName:#"WithResponse"];
}
else
{
[[self.labelMessageResult cell] setTitle: #"Error"];
}
}
- (void) executeRemoteProcessWithName: (NSString*) processName
{
// Create connection
NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Processor)];
NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: #"bunldeID"]; // there's a correct bundle id there, really
[connection setRemoteObjectInterface: myCookieInterface];
connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(Progress)];
connection.exportedObject = self;
[connection resume];
// NOTE that this error handling code is not called, when debugging client, i.e connection seems to be established
id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle: #"OK"];
[alert setMessageText: err.localizedDescription];
[alert setAlertStyle: NSAlertStyleWarning];
[alert performSelectorOnMainThread: #selector(runModal) withObject: nil waitUntilDone: YES];
}];
if ([processName containsString:#"NoResponse"])
{
[theProcessor sendMessageWithNoResponse:#"message"];
}
else if ([processName containsString:#"WithResponse"])
{
[theProcessor sendMessageWithResponse:#"message" reply:^(NSString* replyString)
{
[[self.labelMessageResult cell] setTitle: replyString];
}];
}
}
Jonathan Levin's XPoCe tool is helpful when you can't attach a debugger.
You can add logging NSLog() or fprintf(stderr,...) to your service and clients, specifically around the status codes. You just have to specify the path of the file to write stdout and stderr. <key>StandardErrorPath</key> <string>/tmp/mystderr.log</string>
There's a section on Debugging Daemons at this article on objc.io .
I am new to Cocoa programming...I am learning IPC with Distributed Objects.
I have made a simple example where I am vending objects from the server and calling them in the client.I am successful in passing messages from client object to the server But I want to pass messages from server to client[Bidirectional]...How do I do that?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
MYMessageServer *server = [[MYMessageServer alloc] init];
NSConnection *defaultConnection=[NSConnection defaultConnection];
[defaultConnection setRootObject:server];
if ([defaultConnection registerName:#"server"] == NO)
{
NSLog(#"Error registering server");
}
else
NSLog(#"Connected");
[[NSRunLoop currentRunLoop] configureAsServer];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
//Getting an Vended Object
server = [NSConnection rootProxyForConnectionWithRegisteredName:#"server" host:nil];
if(nil == server)
{
NSLog(#"Error: Failed to connect to server.");
}
else
{
//setProtocolForProxy is a method of NSDistantObject
[server setProtocolForProxy:#protocol(MYMessageServerProtocol)];
[server addMessageClient:self];
[server broadcastMessageString:[NSString stringWithFormat:#"Connected: %# %d\n",
[[NSProcessInfo processInfo] processName],
[[NSProcessInfo processInfo] processIdentifier]]];
}
}
- (void)appendMessageString:(NSString *)aString
{
NSRange appendRange = NSMakeRange([[_messageView string] length], 0);
// Append text and scroll if neccessary
[_messageView replaceCharactersInRange:appendRange withString:aString];
[_messageView scrollRangeToVisible:appendRange];
}
- (void)addMessageClient:(id)aClient
{
if(nil == _myListOfClients)
{
_myListOfClients = [[NSMutableArray alloc] init];
}
[_myListOfClients addObject:aClient];
NSLog(#"Added client");
}
- (BOOL)removeMessageClient:(id)aClient
{
[_myListOfClients removeObject:aClient];
NSLog(#"Removed client");
return YES;
}
- (void)broadcastMessageString:(NSString *)aString
{
NSLog(#"Msg is %#",aString);
self.logStatement = aString;
[_myListOfClients makeObjectsPerformSelector:#selector(appendMessageString:)
withObject:aString];
}
#protocol MYMessageServerProtocol
- (void)addMessageClient:(id)aClient;
- (BOOL)removeMessageClient:(id)aClient;
- (void)broadcastMessageString:(NSString *)aString;
You haven't shown the code for MYMessageServer or MYMessageServerProtocol, specifically the -addMessageClient: method. However, once you have passed the server a reference to an object in the client, the server can message that object like normal and that message will be sent over D.O. to the client.
So, the client is sending self (its application delegate) to the server via -addMessageClient:. The server can simply call methods on the object it receives in its implementation of -addMessageClient: and that will call methods on the client's application delegate object. The server can keep that reference somewhere, such as an array of clients in an instance variable, and continue to message the client at later times, too, if it wants. The server will want to clear that reference out when the connection closes, which it can detect from notifications that NSConnection posts.
I was hoping someone would be able to help with an Objective-C problem I have relating to sending and receiving information between two devices using GameCenter once they have been through matchmaking.
I am using a textbook called Beginning iOS Game Center and Game Kit as my guide, and it is going through an example program, but I am stuck on the part where I would like to receive data from a device playing the game.
I can successfully matchmake two devices and the appropriate view to appear. I have two functions in my GameCenterManager.m file to send information - one being the following:
- (void)sendStringToAllPeers:(NSString *)dataString reliable:(BOOL)reliable
{
NSLog(#"Send String To All Peers");
NSLog(#"Data String: %#", dataString);
NSLog(#"match or session %#", self.matchOrSession);
if (self.matchOrSession == nil)
{
NSLog(#"GC Manager matchorsession ivar was not set - this needs to be set with the GKMatch or GKSession before sending or receiving data");
return;
}
NSData *dataToSend = [dataString dataUsingEncoding:NSUTF8StringEncoding];
GKSendDataMode mode;
if (reliable)
{
mode = GKSendDataReliable;
}
else{
mode = GKSendDataUnreliable;
}
NSError *error = nil;
if ([self.matchOrSession isKindOfClass:[GKSession class]])
{
NSLog(#"Match or session 1");
NSLog(#"Data to send: %#", dataToSend);
[self.matchOrSession sendDataToAllPeers:dataToSend withDataMode:mode error:&error];
}
else if ([self.matchOrSession isKindOfClass:[GKMatch class]])
{
NSLog(#"Match or session 2");
NSLog(#"Data to send: %#", dataToSend);
[self.matchOrSession sendDataToAllPlayers:dataToSend withDataMode:mode error:&error];
}
else
{
NSLog(#"GC Manager matchOrSession was not a GKMatch or a GK Session, we are unable to send data");
}
if (error != nil)
{
NSLog(#"An error occurred while sending data %#", [error localizedDescription]);
}
}
this function I call from a function in my racetohundredViewController.m file:
- (void)generateAndSendHostNumber;
{
NSLog(#"Generate and send host number");
randomHostNumber = arc4random();
NSString *randomNumberString = [NSString stringWithFormat: #"$Host:%f", randomHostNumber];
NSLog(#"the random number string is: %#", randomNumberString);
[self.gcManager sendStringToAllPeers:randomNumberString reliable: YES];
}
I successfully get the following NSLog output resulting from this:
2013-01-02 22:27:43.519 First to 50[1376:907] Send String To All Peers
2013-01-02 22:27:43.520 First to 50[1376:907] Data String: $Host:2087825492.000000
2013-01-02 22:27:43.521 First to 50[1376:907] match or session <GKMatch 0x200853d0 expected count: 0 seqnum: 2
G:1656671636:connected
reinvitedPlayers:(
)>
2013-01-02 22:27:43.522 First to 50[1376:907] Match or session 2
2013-01-02 22:27:43.523 First to 50[1376:907] Data to send: <24486f73 743a3230 38373832 35343932 2e303030 303030>
So I can see the 'Data to send' output which is great.
However I now have the command
[self.matchOrSession sendDataToAllPeers:dataToSend withDataMode:mode error:&error];
which doesn't seem to take me anywhere at all. I have the following function in GameCenterManager.m:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context
{
NSLog(#"*****Receive Data In Session");
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *dataDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:dataString, peer, session, nil] forKeys:[NSArray arrayWithObjects: #"data", #"peer", #"session", nil]];
[self callDelegateOnMainThread: #selector(receivedData:) withArg: dataDictionary error: nil];
}
But I am not seeing the NSLog output from this. Likewise, I have a function in my racetohundredViewController.m file
- (void)receivedData:(NSDictionary *)dataDictionary
{
NSLog(#"------Received Data");
}
which also doesn't get called; presumably because the previous function isn't able to call it.
I have been trying to work out why this doesn't work for a while now without any avail. Can anyone point out where I am going wrong? I hope I have put in all the relevant code but if you have any questions please ask.
Thanks to all, in advance.
I realised what I had done after looking online at other people's problems with the same thing. I had not set my delegate after finding a match, a very rookie mistake!
Can anyone help me? I intensively exchange data between two devices over TCP protocol by using GCDAsyncSocket. I send data like this:
NSMutableDictionary *packet = [[[NSMutableDictionary alloc] init] autorelease];
[packet setObject:[NSNumber numberWithInt:MultiPlayerTypeInfoNextRoundConfirm] forKey:#"type_info"];
[packet setObject:[NSNumber numberWithBool:YES] forKey:#"connection_confirmation"];
NSMutableData *data = [[NSMutableData alloc] initWithData:[NSKeyedArchiver archivedDataWithRootObject:packet]]; //[NSKeyedArchiver archivedDataWithRootObject:packet];
if (currentGameMode == GameModeServer)
[(ServerMultiplayerManager *)multiplayerManager sendNetworkPacket:data withTag:MultiPlayerTypeInfoNextRoundConfirm];
- (void)sendNetworkPacket:(NSData *)data withTag:(long)tag
{
[asyncSocket writeData:data withTimeout:-1 tag:tag];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(#"DID WRITE DATA tag is %ld", tag);
[sock readDataWithTimeout:-1 tag:0];
}
I read data like this:
- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag
{
NSString *receivedInfo = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
[info_data setData:data];
NSLog(#"DID READ DATA WITH TAG %ld", tag);
if ([receivedInfo isEqualToString:ROOM_FILLED])
{
isMaster = (tcpRequest.identifier == MASTER_CHAR);
NSLog(#"IS MASTER SET %d", isMaster);
[multiplayerDelegate setGameModeServer];
[multiplayerDelegate startGame];
}
else
[self dataProcessing:info_data];
[sender readDataWithTimeout:-1 tag:0];
}
- (void)dataProcessing:(NSData *)data
{
NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
MultiPlayerTypeInfo typeInfo = [[dict objectForKey:#"type_info"] intValue];
}
My issue that these packets of data get messed. Say a packet marked with tag 10 is read at the receiver device as packet marked with tag 11, which was sent immediately after packet 10, and when it comes to unarchiving of actual packet 11 NSKeyedUnarchiver throws exception Incomprehensible archive.
As far as i understand i should separate the packets somehow. What i tried was appending separatory symbols to the data being sent:
[data appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
and trying to read it like this:
[socket readDataToData:[GCDAsyncSocket CRLFData] timeout:-1 tag:some_tag];
But it didn't help. What am i doing wrong and what should i do instead?
I guess, you misunderstood the role of the tag. GCDAsyncSocketis (as the name suggests) an asynchrone socket. The tag helps you to match the received data with the receive order and the send succes with the send order, resp.
E.g., if you want to send data, you use writeData:messageA withTimeout:-1 tag: tagA (or something similar) to give your socket the order to send somewhen in the near future. It won't be necessarily right now. And you can immediately give the next order to send another message, say messageB with tag tagB.
To know, that the messageA was really sent, you get the notification via socket:aSocket didWriteDataWithTag:aTag. Here, aTaghas the value of tagA if messageAwas sent, and the value of tagB if messageB was sent. The tag is not sent with the message; it only helps you to identify your order.
It is the very same thing at the receiving side. You give the order to receive (somewhen) some data and assign a tag to that very order. Once you did receive data, the notification (via socket:didReadData:withTag:) shows you the tag to let you know, which order succeed.
You may use the tag for some semantic information and put it in your message. But even then, the tag in the notification is the tag of the receive order, but never the one of the send order. If you want to use the tag you put in the message at the receiving side, you have to receive (at least parts of) the message first and parse it.
To come to the core of your issue: You have basically two possibilities to know, which kind of data is arriving:
Know the sequence of sent data and receive it in the very same order.
Use a message head that identifies the kind of data. Receive only the head and receive and parse the remains of your message in dependence of the head data.
EDIT
Here is an example for the 2nd approach. Assume you can sent a number of object of classes A, B, etc. Your header could include type and size of your data:
typedef struct {
NSUInteger type_id;
NSUInteger size;
} header_t;
#define typeIdA 1
#define typeIdB 2
// ...
Once you want to send an object obj with objKey:
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:obj forKey: objKey];
header_t head;
if ([obj class] == [A class]) {
head.type_id = typeIdA;
} else if ([obj class] == [B class]) {
head.type_id = typeIdB;
} else ...
// ....
header.size = data.lengh;
NSData* headData = [NSData dataWithBytes: &header length: sizeof(header)];
dataWithBytes:length:
header = NSData.length;
[asyncSocket writeData:headData withTimeout:-1 tag:headTag];
[asyncSocket writeData:data withTimeout:-1 tag:dataTag];
If you want, you can get notifications on successful sending or errors, but I skip this here.
At receiver side, you expect a header first:
[receiveSocket readDataToLength:sizeof(header_t) withTimeout:-1 tag:rcvHdrTag];
// rcvHdrTag must not match one of the typeIdX tags
In your socket:didReadData:withTag: you have to distinguish, if you get the header or the remains (the receiving of the remains is initiated here!)
- (void)socket:(GCDAsyncSocket *)aSocket didReadData:(NSData *)data withTag:(long)tag {
header_t head;
id obj;
id key;
switch (tag) {
case rcvHdrTag:
[data getBytes:&head length:sizeof(header)];
// now you know what to receive
[aSocket readDataToLength:header.size withTimeout:-1 tag:header.type];
return;
break; // I know, redundancy :-)
case typeIdA:
objKey = objKeyA; // whatever it is...
break;
case typeIdB:
objKey = objKeyB;
// ....
}
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
obj = [unarchiver decodeObjectForKey:objKey];
// store your object ...
}
This is not the most elegant example, and it ignores object trees and inter-object dependencies in archives, but you should get the idea.
I'm trying to send some small data over UDP using the AsyncUdpSocket library. There is a lot of docs for the TCP connection but none for the UDP connection.
I wrote this class to send 5 bytes to a remote host but it looks like nothing is actually going to the wire. I'm monitoring the network using wireshark but I see no outgoing packets. The delegate method "didSendDataWithTag" is never called :(
Any ideas what I forgot ?
#import "UDPController.h"
#implementation UDPController
- (id)init
{
self = [super init];
if (self) {
socket = [[AsyncUdpSocket alloc] initWithDelegate:self];
}
return self;
}
- (void) sendUDPTest {
NSLog(#"%s", __PRETTY_FUNCTION__);
NSString * string = #"R/103";
NSString * address = #"192.168.1.130";
UInt16 port = 21001;
NSData * data = [string dataUsingEncoding:NSUTF8StringEncoding];
[socket sendData:data toHost:address port:port withTimeout:-1 tag:1];
}
/**
* Called when the datagram with the given tag has been sent.
**/
- (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
- (void)dealloc
{
[socket release];
[super dealloc];
}
#end
cheers
The problem here was that this code was not running in an "application loop" and the main was exiting before the AsyncUdpSocket had time to send the packet.
The same code works fine if it's put in an application.