Based on this question I have implemented the following code to send direct commands to my Zebra TLP2844
var
cmm: AnsiString;
i: integer;
begin
commands.saveToFile('path\to\a\file');
Printer.BeginDoc;
cmm := '';
for i := 0 to commands.Count-1 do
cmm := cmm + commands[i] + #10;
Escape(Printer.Canvas.Handle, PASSTHROUGH, Length(cmm), PAnsiChar(cmm), nil);
Printer.EndDoc;
end;
commands is a TSringList containing all the commands I want to send to the printer.
Note that I save all the commands to a text file.
Well, if I send this text file to print, via the driver preferences, using tools -> Action -> Send File, it prints perfectly.
If I use the code above, it spits some extra rows of labels after printing.
It shows me, obviously, that I am doing something wrong here, but I can't figure out what.
What I have tried
Send commands one by one and not concatenating them like in the code. Results: Nothing gets printed.
Changing #10 for a #13#10. Results: Same crazy behaviour (indeed Zebra EPL documentatins says it will ignore any #13 it finds)
What else should I try in order to send to the printer the commands the exact same way Zebra's tool does?
AFAIK you need to format the buffer as expected by the ExtEscape() API layout. I never used Escape(), but ExtEscape() - and it worked with a Zebra printer.
Here is what the MSDN doc states:
lpszInData [in] A pointer to the input structure required for the
specified escape. The first word in the buffer contains the number of
bytes of input data. The remaining bytes of the buffer contain the
data itself.
So you may code this as such:
cmm := '00'; // reserve space for the initial `word`
for i := 0 to commands.Count-1 do
cmm := cmm + commands[i] + #10;
pword(cmm)^ := length(cmm)-2; // store the length
if ExtEscape(Printer.Canvas.Handle, PASSTHROUGH, Length(cmm), pointer(cmm), 0, nil)<0 then
raise Exception.Create('Error at printing to printer');
Printer.EndDoc;
Be aware that if your command is not well formatted (e.g. missing chars), it may just create an out of memory error in the printer spooler - yes, I've seen that! In this case, you may have to kill then restart the Printer Spooler service... fix your code... and try again...
And do not forget to put the ESC character at the beginning of each of your commands[], as requested by the Zebra doc.
you can use this procedure: where the LabelFile is the full path of the label file,we are using this code and works with generic text driver printer and the printer is set as the default printer. it works with zebra printer and windows xp operating system.
https://stackoverflow.com/a/27647044/2977139
i hope this will help you.
If you want to use the Windows printer driver, you should use WritePrinter defined un the WinSpool unit. If I see this correctly, the TPrinter object from the Printers unit doesn't expose it's FPrinterHandle member, so you might need to use OpenPrinter and ClosePrinter yourself.
Having worked with MarkPoint printers at work, which are somewhat similar to Zebra printers: if the printer is connect to a serial port, I would warmly suggest to try and access the printer directly by connecting to the serial port with one of the several components available.
Related
I have written a function that prints out ZPLII data to a Zebra printer as shown below
var
St : string;
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
Escape(PrinterHandle,PASSTHROUGH,Length(St),PAnsiChar(St),Nil);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I have tested the label data by using the command prompt to print it from a text file and the label prints out correctly but i cannot print it from my application. If i pause the printer i can see the job gets sent to the printer and the size of the job is 2.12Kb, roughly the size the label should be, but nothing prints out. The light on the Zebra printer for data lights up but nothing will print. I have tried this with two of the Zebra printers we own, so it is not a printer issue. My guess at this point is that maybe the program isn't sending the entire label data to the printer and the end is never received but when i trace through the send request, everthing is sent properly. the printer also shows the job has 0/0 pages for the label but i cannot understand why it is not sending the label. Is there something special that needs to go at the end of the label data besides the ^XZ termination character? I am also using Delphi XE3 if that helps.
Thanks to everyone's help I was able to successfully print my labels using the following changes:
St: AnsiString;
...
StartDocument(DocName);
StartPage;
Try
For I:=0 to DataToPrint.Count-1 do
Begin
St:=FormatPrintLine(I);
Try
WritePrinter(PrinterHandle,PChar(St),Length(St),N);
Except on E:Exception do
begin
GetWin32ApiErrorMessage(FErrorCode,FErrorText);
Raise EPrinter.CreateFmt('Printer Write Error %d'+#13+
'While Printing To '+PrinterName+#13+
ErrorText,[ErrorCode]);
end;
End;
end;
Finally
EndPage;
EndDocument;
I also had to change to writeprinter instead of escape. Escape did not print anything out after i change st to type AnsiString but writeprinter was successful.
I am trying to send commands directly to a Zebra TLP2844 printer. I followed the suggestion made here and my final code came to be as follows:
var
cm: String;
p: TPrinter;
i: integer;
begin
p := Printer;
p.BeginDoc;
for i := 0 to memo1.Lines.Count-2 do
begin
cm := memo1.Lines[i];
if Escape(p.Canvas.Handle,
PASSTHROUGH,
Length(cm),
PAnsiChar(cm),
nil) = 0 then
ShowMessage('Command error: ' + IntToStr(GetLastError));
end;
p.EndDoc;
end;
The content of memo1 is (first line is empty) as purposed here:
N
q609
Q203,26
B26,26,0,UA0,2,2,152,B,"603679025109"
A253,26,0,3,1,1,N,"SKU 6205518 MFG 6354"
A253,56,0,3,1,1,N,"2XIST TROPICAL BEACH"
A253,86,0,3,1,1,N,"STRIPE SQUARE CUT TRUNK"
A253,116,0,3,1,1,N,"BRICK"
A253,146,0,3,1,1,N,"X-LARGE"
P1,1
The commands don't seem to be properly received or interpreted by the printer. I checked that the printer is in Page Mode (EPL2), with the suggested code I am able to open the printer handle. But nothing is printed, only a new line of labels is feeded.
I tried to completely change the commands to something obviously wrong and the behaviour is the same.
What else should I be looking to get things printed?
Most printers that take raw commands require a prefix (starting sequence of characters) and suffix (ending sequence of chars) wrapping each command. I don't know what the prefix and suffix are for the Zebra, but the documentation should tell you.
Just add a pair of constants to define the prefix and suffix, and add them to your command before sending it.
The other issue might be that you're reading the content of your commands from a TMemo, which in Delphi 2009 and higher contains Unicode strings. You're then casting them down to PAnsiChar, which may be truncating the content. Do the conversion ahead of time by defining cm as an AnsiString, and then assigning to it first (as you are) before typecasting to pass to the Escape function. I've done this in my code to illustrate it.
var
cm: AnsiString;
p: TPrinter;
i: integer;
const
ZPrefix = AnsiString('$('); // Replace values for each of these with what
ZSuffix = AnsiString(')$'); // your documentation says you should use
begin
p := Printer;
p.BeginDoc;
for i := 0 to memo1.Lines.Count-2 do
begin
cm := ZPrefix + memo1.Lines[i] + ZSuffix;
if Escape(p.Canvas.Handle,
PASSTHROUGH,
Length(cm),
PAnsiChar(cm),
nil) = 0 then
ShowMessage('Command error: ' + IntToStr(GetLastError));
end;
p.EndDoc;
end;
I program in php which is like C
I can send things to the printer just fine
my code looks like your code the only thing is I am not sure how your programming language handles the newline in php it's \n at the end of each line
if the newline is not there the print job will not print
and if the " are not sent it will not print
your EPL looks fine and should print
there is somewhere on the zebra web site a download where you can send commands to a printer which is hooked up to your computer by USB cable
think it is called Zebra Setup Utilities
I tried to employ Indy 10.5.5 (shipped with Delphi 2010) for:
connecting to telnet server
performing username/password authentication (gaining access to the command shell)
executing a command with returning resulting data back to application
and had no success, additionally i'm completely lost in spaghetti logic of Indy's internals and now have no idea why it didnt work or how i supposed to send strings to the server and grab the results. Need some sample code to study.
Formal form of the question: Where can i get 3-rd party contributed demo covering TIdTelnet component? (indyproject.org demos webpage do not have one)
The main problem with Telnet is that it DOES NOT utilize a command/response model like most other Internet protocols do. Either party can send data at any time, and each direction of data is independant from the other direction. This is reflected in TIdTelnet by the fact that it runs an internal reading thread to receive data. Because of this, you cannot simply connect, send a command, and wait for a response in a single block of code like you can with other Indy components. You have to write the command, then wait for the OnDataAvailable event to fire, and then parse the data to determine what it actually is (and be prepared to handle situations where partial data may be received, since that is just how TCP/IP works).
If you are connecting to a server that actually implements a command/response model, then you are better off using TIdTCPClient directly instead of TIdTelnet (and then implement any Telnet sequence decoding manually if the server really is using Telnet, which is rare nowadays but not impossible). For Indy 11, we might refactor TIdTelnet's logic to support a non-threaded version, but that is undecided yet.
done with indy.
no comments.. just som old code :-)
telnet don't like the send string kommand.. use sendch.
telnetdude.Host := 1.1.1.1;
try
telnetdude.connect;
except
on E: Exception do begin
E.CleanupInstance;
end; {except}
if telnetdude.Connected then begin
for i := 1 to length(StringToSend) do telnetdude.sendch(StringToSend[i]);
telnetdude.sendch(#13);
end;
end; {while}
end; {if}
if telnetdude.Connected then telnetdude.Disconnect;
end;
I hope this helps anyone looking for answers to a similar question.
Firstly, It would seem the typical command/response model (as mentioned above, does indeed NOT apply).
So I just got it working for some very simple application (rebooting my router).
Specific additions to above code from Johnny Lanewood (and perhaps some clarification)
a) You have to send #13 to confirm the command
b) I got "hangs" on every command I sent / response I requested UNTIL I enabled ThreadedEvent. (this was my big issue)
c) the OnDataAvailable event tells you when new data is available from the Telnet Server - however there are no guarantees as to what this data is - i.e. it's pretty what you get in the command line / what ever is appended to the previous responses. But is is NOT a specific response line to your command - it's whatever the telnet server returns (could be welcome info, ASCII drawings etc etc.)
Given (c) above, one would rather check the OnDataAvailable event and parse the data (knowing what you'd expect). When the output stops (i.e. you need build a mechanism for this), you can parse the data and determine whether the server is ready for something new from the client. For the purpose of my code below, I set a read timemout and I just used Sleep(2000) - ignorantly expecting no errors and that the server would be ready after the sleep for the next command.
My biggest stumbling block was ThreadedEvent := True (see above in b)
Thus, my working solution (for specific application, and possibly horrible to some).
lIDTelnet := TIdTelnet.Create(nil);
try
lIdTelnet.ReadTimeout := 30000;
lIDTelnet.OnDataAvailable := TDummy.Response;
lIDTelnet.OnStatus := TDummy.Status;
lIdTelnet.ThreadedEvent := True;
try
lIDTelnet.Connect('192.168.0.1', 23);
if not lIDTelnet.Connected then
Raise Exception.Create('192.168.0.1 TELNET Connection Failed');
Sleep(2000);
lIdtelnet.SendString(cst_user + #13);
Sleep(2000);
lIdtelnet.SendString(cst_pass + #13);
Sleep(2000);
lIdtelnet.SendString(cst_reboot + #13);
Sleep(2000);
if lIDTelnet.Connected then
lIDTelnet.Disconnect;
except
//Do some handling
end;
finally
FreeAndNil(lIdTelnet);
end;
and then
class procedure TDummy.Response(Sender: TIdTelnet; const Buffer: TIdBytes);
begin
Write(TDummy.ByteToString(Buffer));
end;
class function TDummy.ByteToString(
const aBytes: TIdBytes): String;
var
i : integer;
begin
result := '';
for i := 0 to Length(aBytes) -1 do
begin
result := result + Char(aBytes[i]);
end;
end;
Im struggling with getting information back from the printer when sending PJL to the printer. The printer supports PJL and it is a USB printer. Now getting information / examples seems to be a problem or I'm looking at the wrong places. I know on MSDN there are a lot of information, but I've tried everything I got there, from docinfo's to write/read printers and nothing seems to work.
Some people say you can use writeprinter and readprinter. I've tried this, when I writeprinter, the printer seems to "do" something, but readprinter returns or errors or blanks. Now I think this could be because the print driver is "locking" the port, so you cant read information back from it?
The other option I saw somewhere is to use writefile and readfile. Here you would get the physical port part for the printer, for example '\?\USB#VID_05CA&PID_0403#S5208603411#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}'. Then you change the port to "FILE". Use the writefile and readfile with the path as the physical path from above. Get the information you need and then set the port back to the original port. Tried this as well, also getting errors.
Im just trying to do a simple #PJL INFO PAGECOUNT (I left out the escape chars etc, etc). The string is correct, because using the string on networked printers, its working 100% and I can get the information. But local printers is a problem.
Is there anyone that has this working or a working example? Any help would be much appreciated.
PS: Below is 1 of the 100's of examples I've tried. This is the writeprinter example:
procedure TForm1.Button5Click(Sender: TObject);
Const
Defaults: TPrinterDefaults = (
pDatatype : Nil;
pDevMode : nil;
DesiredAccess : PRINTER_ACCESS_USE or PRINTER_ACCESS_ADMINISTER ) ;
Var
Device : array[0..255] of char;
FPrinterHandle:THandle;
DocInfo1: TDocInfo1;
Buffer, Buffer2:PChar;
Written, Len:Cardinal;
i: Integer;
sPath: String;
Begin
StrCopy(Device,PChar('RICOH Aficio SP 4210N PCL 6'));
OpenPrinter(#Device, FPrinterHandle, #Defaults);
DocInfo1.pDocName := 'test';
DocInfo1.pOutputFile := Nil;
DocInfo1.pDatatype := 'RAW';
StartDocPrinter(FPrinterHandle, 1, #DocInfo1);
StartPagePrinter(FPrinterHandle);
Buffer := #27+'%-12345X#PJL COMMENT'+#13+#10+'#PJL INFO PAGECOUNT'+#13+#10+#27+'%-12345X';
WritePrinter(FPrinterHandle,#Buffer,Length(Buffer), Written);
EndPagePrinter(FPrinterHandle);
EndDocPrinter(FPrinterHandle);
// everithing is OK here, BUT
ReadPrinter(FPrinterHandle, #Buffer2, Length(Buffer2), len );
end;
Check http://www.undocprint.org/winspool/tips_and_tricks for an explanation of what to do, and some sample C code.
Even with this code the chances of this working for you are minimal. To be able to read back from the printer, the port monitor must support bidirectional mode, and the standard USB port monitor does not.
Also, in your code above, Buffer2 passed to ReadPrinter() is not correct. You need to pre-allocate buffer space, and then pass the address of the buffer, not the address of the pointer to the buffer...
var
Buffer2 : array[0..255] of Char;
begin
...
ReadPrinter( FPrinterHandle, #Buffer2[0], Length(Buffer2), len );
end;
How do I check if another application is busy?
I have a program that sends text to a console. The text that I will send contains #13 char (e.g. ls#13cd documents#13dir). In other words I want to send many commands at one time and the console will process them one by one. I am sending the text character by character. Sometimes the console only executes ls and cd documents. I think maybe this is because my program continuously sends character even if the console is busy, in which case the console does not receive incoming characters.
This is my code:
procedure TForm1.SendTextToAppO(Str: String; AHandle: Integer);
var
iWindow, iPoint, i: Integer;
SPass: PChar;
sList: TStringList;
begin
sList := TStringList.Create;
ExtractStrings([#13],[' '],PChar(Str),sList);
iWindow := AHandle;// AHandle is the handle of the console
iPoint := ChildWindowFromPoint(iWindow, Point(50,50));
for i:=0 to sList.Count-1 do begin
SPass := PChar(sList[i]);
try
while(SPass^ <> #$00) do begin
SendMessage(iPoint,WM_CHAR,Ord(SPass^),0);
Inc(SPass);
end;
SendMessage(iPoint,WM_KEYDOWN,VK_RETURN,0);
except
// do nothing;
end;
end;
end;
I am using Delphi 7.
If I interpret you question correctly you are sending the text to some sort of shell/command line interpreter and you want it to execute your commands.
Usually command line interpreters output a certain prompt (like $ on a Linux system or C:\ for DOS) that indicate that they can accept new commands. You need to read the output to wait for the appropriate prompt before you send another command. If you don't your sent text will be consumed as input by the currently running command (like you experienced).
lothar is on the right track; what you want to do is, instead of using ShellExecute, use CreateProcess. Look around Stack Overflow and Google for "Console Redirection" - that'll get you what you're looking for.
I think I understand what's going on, not that I have a fix for it:
You send a command to the console. While the command is running that program will receive the keys you send.