How to send control commands to POS printer from Delphi - delphi

I use this code to print text file to POS printer (EPSON):
AssignFile(prnfile, 'file.txt');
Reset(prnfile, 1);
AssignFile(port, 'COM3');
Rewrite(port, 1);
repeat
BlockRead(prnfile, buffer, SizeOf(buffer), Read);
BlockWrite(port, buffer, Read);
until EOF(prnfile) or (Read <> SizeOf(buffer));
CloseFile(prnfile);
CloseFile(port);
Text is printed, but I need to cut a receipt.
I have EPSON command codes, but I don't know how to send them to printer.
Can anybody write an example?
Thank You.

I've tried a lot and finally I've written this code that works:
procedure Cut();
var epsonprn : System.Text;
begin
try
AssignFile(epsonprn,'COM3');// the name of printer port, can be a network share
Rewrite(epsonprn);
Write(epsonprn,#29#86#66#0);//cut sequence
finally
CloseFile(epsonprn);
end;
end;
so the solution is:
procedure TForm1.Button1Click(Sender: TObject);
var prnfile,port:System.Text;
var buffer:String;
begin
try
AssignFile(prnfile, 'c:\file.txt');
Reset(prnfile);
AssignFile(port, 'COM3');
Rewrite(port);
while not eof(prnfile) do
begin
Readln(prnfile, buffer);
Writeln(port, buffer);
end;
finally
CloseFile(port);
CloseFile(prnfile);
end;
cut();
end;
Anyway, my suggestion is to use a tComPort component instead of a direct use of Writeln. Using a tComPort you can handle the return value from the printer in case of errors like "End Paper", "Printer OffLine" and so on.

You have to send an ESC/POS sequence like this
Definition of cut command:
//ASCII GS V m
//Hex 1D 42 m
//Decimal 29 66 m
var cut:String;
begin
cut:=Chr(29)+'V'+Chr(66)+Chr(0);
// send this sequence direct to com after the text file
end;
the complete esc/pos code here

Related

TStringlist not loading Google Contacts file

I'm trying to use a Stringlist to load a CSV file generated by Google Contacts. When i open this file in an text editor like Sublime Text, i can see the contents properly, with 75 lines. This is a sample from the Google Contacts file :
Name,Given Name,Additional Name,Family Name,Yomi Name,Given Name Yomi,Additional Name Yomi,Family Name Yomi,Name Prefix,Name Suffix,Initials,Nickname,Short Name,Maiden Name,Birthday,Gender,Location,Billing Information,Directory Server,Mileage,Occupation,Hobby,Sensitivity,Priority,Subject,Notes,Group Membership,Phone 1 - Type,Phone 1 - Value,Phone 2 - Type,Phone 2 - Value,Phone 3 - Type,Phone 3 - Value
H,H,,,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,,,,,,
H - ?,H,-,?,,,,,,,,,,, 1-01-01,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-863-64393,,,,
H - ?,H,-,?,,,,,,,,,,,,,,,,,,,,,,,* My Contacts ::: Importado 01/02/16,Mobile,031-986-364393,,,,
BUT when i try to load this same file using Stringlist, this is what i see in the Stringlist.text property :
'ÿþN'#$D#$A
Here is my code :
procedure Tform1.loadfile;
var sl : tstringlist;
begin
sl := tstringlist.create;
sl.loadfromfile('c:\google.csv');
showmessage('lines : '+inttostr(sl.count)+' / text : '+ sl.text);
end;
This is the result i get :
'1 / 'ÿþN'#$D#$A'
What is happening here ?
Thanks
According to the hex dump you provided, the BOM indicates that your file is encoded using UTF-16LE. You a few options in front of you, as I see it:
Switch to Unicode and use the TnT Unicode controls to work with this file.
Read the file as an array of bytes. Convert to ANSI and then continue using ANSI encoded text. Obviously you'll lose information for any characters than cannot be encoded by your ANSI code page. A cheap way to do this would be to read the file as a byte array. Copy the content after the first two bytes, the BOM, into a WideString. Then assign that WideString to an ANSI string.
Port your program to a Unicode version of Delphi (anything later than Delphi 2007) and work natively with Unicode.
I rather suspect that you are not very familiar with text encodings. If you were then I think you would have been able to answer the question yourself. That's just fine but I urge you to take the time to learn about this issue properly. If you rush into coding now, before having a sound grounding, you are sure to make a mess of it. And we've seen so many people make that same mistake. Please don't add to the list of text encoding casualties.
Thanks to the information of David, i could achieve the task by using the function below ; because Delphi 2007 does not have unicode support, it needs third-party function to do it.
procedure loadUnicodeFile( const filename: String; strings: TStringList);
Procedure SwapWideChars( p: PWideChar );
Begin
While p^ <> #0000 Do Begin
// p^ := Swap( p^ ); //<<< D3
p^ := WideChar( Swap( Word(p^)));
Inc( p );
End; { While }
End; { SwapWideChars }
Var
ms: TMemoryStream;
wc: WideChar;
pWc: PWideChar;
Begin
ms:= TMemoryStream.Create;
try
ms.LoadFromFile( filename );
ms.Seek( 0, soFromend );
wc := #0000;
ms.Write( wc, sizeof(wc));
pWC := ms.Memory;
If pWc^ = #$FEFF Then // normal byte order mark
Inc(pWc)
Else If pWc^ = #$FFFE Then Begin // byte order is big-endian
SwapWideChars( pWc );
Inc( pWc );
End { If }
Else; // no byte order mark
strings.Text := WideChartoString( pWc );
finally
ms.free;
end;
End;

Serial Port Synchronization in Delphi

I am still having issues with the TComPort component but this time is not the component itself is the logic behind it. I have a device witch sends some ascii strings via serial port i need to prase those strings the problem is the computer reacts very fast so in the event char it captures only a part of the string the rest of the string comes back later... so parsing it when it is recived makes it impossible.
I was thinking in writing a timer witch verify if there was no serial activity 10 secons or more and then prase the string that i am saving into a buffer. But this method is unprofessional isn't there a idle event witch i can listen...Waiting for the best solution for my problem. Thanks.
After using a number of serial-port-components, I've got the best results until now, by using CreateFile('\\?\COM1',GENERIC_READ or GENERIC_WRITE,0,nil,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0), passing that handle to a THandleStream instance, and starting a dedicated thread to read from it. I know threads take a little more work than writing an event handler, but it still is the best way to handle any synchronization issues that arise from using serial ports.
Typical handler for OnRXChar event:
procedure XXX.RXChar(Sender: TObject; Count: Integer);
begin
ComPort.ReadStr(s, Count);
Accumulator := Accumulator + s;
if not AccumContainsPacketStart then
Accumulator := ''
else if AccumContainsPacketEndAfterStart then begin
ExtractFullStringFromAccum;
ParseIt;
end;
end;
Note.
Most com-port components do not have a clue when to report back to the owner. Normally the thread that is responsible to gather the bytes from the port is informed by the OS that one or more bytes are ready to be processed. This information is then simply popped up to your level. So when you expect the message to be transferred, you get what the OS is giving you.
You have to buffer all incoming characters in a global buffer. When you get the final character in your message string, handle the message.
Here is an example where the message start is identified with a special character and the end of the message is identified with another character.
If your message is constructed in another way, I'm sure you can figure out how to adapt the code.
var
finalBuf: AnsiString;
{- Checking message }
Function ParseAndCheckMessage(const parseS: AnsiString) : Integer;
begin
Result := 0; // Assume ok
{- Make tests to confirm a valid message }
...
end;
procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var
i,err: Integer;
strBuf: AnsiString;
begin
ComPort.ReadStr(strBuf, Count);
for i := 1 to Length(strBuf) do
case strBuf[i] of
'$' :
finalBuf := '$'; // Start of package
#10 :
begin
if (finalBuf <> '') and (finalBuf[1] = '$') then // Simple validate check
begin
SetLength( finalBuf, Length(finalBuf) - 1); // Strips CR
err := ParseAndCheckMessage(finalBuf);
if (err = 0) then
{- Handle validated string }
else
{- Handle error }
end;
finalBuf := '';
end;
else
finalBuf := finalBuf + strBuf[i];
end;
end;
If your protocol has begin/end markers, you can use TComDataPacket to provide you full packets, when they are available.
For certain amount of character we can use delay some miliseconds before ReadStr to make sure the data is completely sent. Example for 4 amount of character:
procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer);
var
Str: String;
tegangan : real;
begin
sleep(100); //delay for 100ms
ComPort1.ReadStr(Str, 4);
...

Sending printer specific commands

I have an issue here, which I am trying to encode magnetic stripe data to an Fargo DTC400 printer, in the specifications it says I need to send the following string commands from example notepad, wordpad etc etc :
~1%TRACK NUMBER ONE?
~2;123456789?
~3;123456789?
this example encodes the string in track one, and the numbers 123456789 in both track 2 and 3.. this works from Notepad.exe.
EDIT:
Current delphi code I use works on another printer:
procedure SendQuote(MyCommand : AnsiString);
var
PTBlock : TPassThrough;
begin
PTBlock.nLen := Length(MyCommand);
StrPCopy(#PTBlock.SData, MyCommand);
Escape(printer.handle, PASSTHROUGH, 0, #PTBlock, nil);
end;
when I am trying to encode this string from my own application I get trouble, it seems the printer is totally ignoring my commands, when I choose print to file, I can read the binary data and see my string in the printed file, when I try to print to file from example notepad.exe I get just rubish binary data and cannot find my strings at all...
so I wonder what does notepad do to send this string command which I dont ?
hope someone can shed light on this because I have been eager to implement fargo support in my application for a longer period of time .
thanks
Update.
the following code is ancient but it does the job, however is there another way I can use this with the Passthrough code above?
var
POutput: TextFile;
k: Integer;
begin
with TPrintDialog.Create(self) do
try
if Execute then
begin
AssignPrn(POutput);
Rewrite(POutput);
Writeln(POutput,'~1%TESTENCODER?');
Writeln(POutput,'~2;123456789?');
Writeln(POutput,'~2;987654321?');
CloseFile(POutput);
end;
finally
free;
end
end;
TPassThrough should be declared like this :
type
TPassThrough = packed record
nLen : SmallInt;
SData : Array[0..255] of AnsiChar;
end;
You might be using a modern Delphi (2009 or newer) or forgotten the packed directive.
See also this SO question for a correct-way-to-send-commands-directly-to-printer.
At Torry's there is an example snippet (written by Fatih Ölçer):
Remark : Modified for use with Unicode Delphi versions as well.
{
By using the Windows API Escape() function,
your application can pass data directly to the printer.
If the printer driver supports the PASSTHROUGH printer escape,
you can use the Escape() function and the PASSTHROUGH printer escape
to send native printer language codes to the printer driver.
If the printer driver does not support the PASSTHROUGH printer escape,
you must use the DeviceCapabilities() and ExtDevMode() functions instead.
Mit der Windows API Funktion Escape() kann man Daten direkt zum Drucker schicken.
Wenn der Drucker Treiber dies nicht unterstützt, müssen die DeviceCapabilities()
und ExtDevMode() Funktionen verwendet werden.
}
// DOS like printing using Passthrough command
// you should use "printer.begindoc" and "printer.enddoc"
type
TPrnBuffRec = packed record
bufflength: Word;
Buff_1: array[0..255] of AnsiChar;
end;
function DirectToPrinter(S: AnsiString; NextLine: Boolean): Boolean;
var
Buff: TPrnBuffRec;
TestInt: Integer;
begin
TestInt := PassThrough;
if Escape(Printer.Handle, QUERYESCSUPPORT, SizeOf(TESTINT), #testint, nil) > 0 then
begin
if NextLine then S := S + #13 + #10;
StrPCopy(Buff.Buff_1, S);
Buff.bufflength := StrLen(Buff.Buff_1);
Escape(Printer.Canvas.Handle, Passthrough, 0, #buff, nil);
Result := True;
end
else
Result := False;
end;
// this code works if the printer supports escape commands
// you can get special esc codes from printer's manual
// example:
printer.BeginDoc;
try
DirectToPrinter('This text ');
finally
printer.EndDoc;
end;

Delphi: How to send MIDI to a hosted VST plugin?

I want to use VST plugins in my Delphi program which acts as a VST host. I have tried the tobybear examples, used the delphiasiovst stuf, got some of it even working, but... I don't know how to send MIDI messages to the plugin (I am aware that most plugins will not handle MIDI, but I have an example plugin that does).
To be more specific: I expect that when I send a MIDI message, I have to either use one or other method in the VST plugin or reroute the MIDI output. I just don't know how.
Can anyone point me to documentation or code on how to do this? Thanks in advance.
Arnold
I use two test plugins: the one compiled from the DelphiAsioVst package and PolyIblit. Both work in Finale and LMMS. Loaded into my test program both show their VST editor.
I did insert the TvstEvent record and initialized it, I inserted the MIDIData and the AddMIDIData procedures and a timer to provide test data and to execute the ProcessEvents routine of the plugin. ProcessEvents gets the correct test data, but no sound is heard. I hear something when I send it directly to the midi output port.
In the code below the PropcessEvents should be sufficient imho, the additional code is a test whether the MIDI information is correctly sent. VstHost [0] is the first plugin, being either the PolyIblit or the VSTPlugin, depending on the test.
procedure TMain_VST_Demo.TimerTimer (Sender: TObject);
var i: Int32;
begin
// MIDIOutput.PutShort ($90, 60, 127);
MIDIData (0, $90, 60, 127);
if FMDataCnt > 0 then
begin
FMyEvents.numEvents := FMDataCnt;
VSTHost[0].ProcessEvents(#FMyEvents);
// if (FCurrentMIDIOut > 0) and MIMidiThru.Checked then
// begin
for i := 0 to FMDataCnt - 1 do
MIDIOutput.PutShort (PVstMidiEvent (FMyEvents.events[i])^.midiData[0],
PVstMidiEvent (FMyEvents.events[i])^.midiData[1],
PVstMidiEvent (FMyEvents.events[i])^.midiData[2]);
// FMidiOutput.Send(//FCurrentMIDIOut - 1,
// PVstMidiEvent(FMyEvents.events[i])^.midiData[0],
// PVstMidiEvent(FMyEvents.events[i])^.midiData[1],
// PVstMidiEvent(FMyEvents.events[i])^.midiData[2]);
// end;
FMDataCnt := 0;
end;
end; // TimerTimer //
So I don't get the events in the plugin. Any idea what do I wrong?
You should really look at the minihost core example (Delphi ASIO project, v1.4).
There is a use of midi events. Basically
you have a TVstEvents variable ( let's say MyMidiEvents: TvstEvents).
for the whole runtime you allocate the memory for this variable ( in the app constructor for exmaple)
When you have an event in your MIDI callback, you copy it on the TVstEvents stack.
Before calling process in the TVstHost, you call MyVstHost.ProcessEvents( #MyMidiEvents ).
this is how it's done in the example (minihost core), for each previously steps:
1/ at line 215, declaration
FMyEvents: TVstEvents;
2/ at line 376, allocation:
for i := 0 to 2047 do
begin
GetMem(FMyEvents.Events[i], SizeOf(TVSTMidiEvent));
FillChar(FMyEvents.Events[i]^, SizeOf(TVSTMidiEvent), 0);
with PVstMidiEvent(FMyEvents.Events[i])^ do
begin
EventType := etMidi;
ByteSize := 24;
end;
end;
3/ at line 986 then at line 1782, the midi event is copied from the callback:
the callback
procedure TFmMiniHost.MidiData(const aDeviceIndex: Integer; const aStatus, aData1, aData2: Byte);
begin
if aStatus = $FE then exit; // ignore active sensing
if (not Player.CbOnlyChannel1.Checked) or ((aStatus and $0F) = 0) then
begin
if (aStatus and $F0) = $90
then NoteOn(aStatus, aData1, aData2) //ok
else
if (aStatus and $F0) = $80
then NoteOff(aStatus, aData1)
else AddMidiData(aStatus, aData1, aData2);
end;
end;
event copy
procedure TFmMiniHost.AddMIDIData(d1, d2, d3: byte; pos: Integer = 0);
begin
FDataSection.Acquire;
try
if FMDataCnt > 2046
then exit;
inc(FMDataCnt);
with PVstMidiEvent(FMyEvents.events[FMDataCnt - 1])^ do
begin
EventType := etMidi;
deltaFrames := pos;
midiData[0] := d1;
midiData[1] := d2;
midiData[2] := d3;
end;
finally
FDataSection.Release;
end;
end;
4/ at line 2322, in TAsioHost.Bufferswitch, the TVstHost.ProcessEvents is called
FDataSection.Acquire;
try
if FMDataCnt > 0 then
begin
FMyEvents.numEvents := FMDataCnt;
VSTHost[0].ProcessEvents(FMyEvents);
if (FCurrentMIDIOut > 0) and MIMidiThru.Checked then
begin
for i := 0 to FMDataCnt - 1 do
FMidiOutput.Send(FCurrentMIDIOut - 1,
PVstMidiEvent(FMyEvents.events[i])^.midiData[0],
PVstMidiEvent(FMyEvents.events[i])^.midiData[1],
PVstMidiEvent(FMyEvents.events[i])^.midiData[2]);
end;
FMDataCnt := 0;
end;
finally
FDataSection.Release;
end;
this should help you a lot if you were not able to analyse the method used.
If you are hosting VST 2.x plugins, you can send MIDI events to the plugin using AudioEffectX.ProcessEvents().
From the VST docs.
Events are always related to the current audio block.
For each process cycle, processEvents() is called once before a processReplacing() call (if new events are available).
I don't know of any code examples. There might be something in DelphiAsioVST.
If you're up for a change of programming language you could try VST.NET that allows you to write plugins and hosts in C# and VB.NET.
Hope it helps.

How can I get this File Writing code to work with Unicode (Delphi)

I had some code before I moved to Unicode and Delphi 2009 that appended some text to a log file a line at a time:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F, C1 : dword;
begin
if LogFileName <> '' then begin
F := CreateFileA(Pchar(LogFileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0);
if F <> 0 then begin
SetFilePointer(F, 0, nil, FILE_END);
S := S + #13#10;
WriteFile(F, Pchar(S)^, Length(S), C1, nil);
CloseHandle(F);
end;
end;
end;
But CreateFileA and WriteFile are binary file handlers and are not appropriate for Unicode.
I need to get something to do the equivalent under Delphi 2009 and be able to handle Unicode.
The reason why I'm opening and writing and then closing the file for each line is simply so that other programs (such as WordPad) can open the file and read it while the log is being written.
I have been experimenting with TFileStream and TextWriter but there is very little documentation on them and few examples.
Specifically, I'm not sure if they're appropriate for this constant opening and closing of the file. Also I'm not sure if they can make the file available for reading while they have it opened for writing.
Does anyone know of a how I can do this in Delphi 2009 or later?
Conclusion:
Ryan's answer was the simplest and the one that led me to my solution. With his solution, you also have to write the BOM and convert the string to UTF8 (as in my comment to his answer) and then that worked just fine.
But then I went one step further and investigated TStreamWriter. That is the equivalent of the .NET function of the same name. It understands Unicode and provides very clean code.
My final code is:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F: TStreamWriter;
begin
if LogFileName <> '' then begin
F := TStreamWriter.Create(LogFileName, true, TEncoding.UTF8);
try
F.WriteLine(S);
finally
F.Free;
end;
end;
Finally, the other aspect I discovered is if you are appending a lot of lines (e.g. 1000 or more), then the appending to the file takes longer and longer and it becomes quite inefficient.
So I ended up not recreating and freeing the LogFile each time. Instead I keep it open and then it is very fast. The only thing I can't seem to do is allow viewing of the file with notepad while it is being created.
For logging purposes why use Streams at all?
Why not use TextFiles? Here is a very simple example of one of my logging routines.
procedure LogToFile(Data:string);
var
wLogFile: TextFile;
begin
AssignFile(wLogFile, 'C:\MyTextFile.Log');
{$I-}
if FileExists('C:\MyTextFile.Log') then
Append(wLogFile)
else
ReWrite(wLogFile);
WriteLn(wLogfile, S);
CloseFile(wLogFile);
{$I+}
IOResult; //Used to clear any possible remaining I/O errors
end;
I actually have a fairly extensive logging unit that uses critical sections for thread safety, can optionally be used for internal logging via the OutputDebugString command as well as logging specified sections of code through the use of sectional identifiers.
If anyone is interested I'll gladly share the code unit here.
Char and string are Wide since D2009. Thus you should use CreateFile instead of CreateFileA!
If you werite the string you shoudl use Length( s ) * sizeof( Char ) as the byte length and not only Length( s ). because of the widechar issue. If you want to write ansi chars, you should define s as AnsiString or UTF8String and use sizeof( AnsiChar ) as a multiplier.
Why are you using the Windows API function instead of TFileStream defined in classes.pas?
Try this little function I whipped up just for you.
procedure AppendToLog(filename,line:String);
var
fs:TFileStream;
ansiline:AnsiString;
amode:Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename,{mode}amode);
try
if (amode<>fmCreate) then
fs.Seek(fs.Size,0); {go to the end, append}
ansiline := AnsiString(line)+AnsiChar(#13)+AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(ansiline)^,Length(ansiline));
finally
fs.Free;
end;
Also, try this UTF8 version:
procedure AppendToLogUTF8(filename, line: UnicodeString);
var
fs: TFileStream;
preamble:TBytes;
outpututf8: RawByteString;
amode: Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename, { mode } amode, fmShareDenyWrite);
{ sharing mode allows read during our writes }
try
{internal Char (UTF16) codepoint, to UTF8 encoding conversion:}
outpututf8 := Utf8Encode(line); // this converts UnicodeString to WideString, sadly.
if (amode = fmCreate) then
begin
preamble := TEncoding.UTF8.GetPreamble;
fs.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end
else
begin
fs.Seek(fs.Size, 0); { go to the end, append }
end;
outpututf8 := outpututf8 + AnsiChar(#13) + AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(outpututf8)^, Length(outpututf8));
finally
fs.Free;
end;
end;
If you try to use text file or Object Pascal typed/untyped files in a multithreaded application you gonna have a bad time.
No kidding - the (Object) Pascal standard file I/O uses global variables to set file mode and sharing. If your application runs in more than one thread (or fiber if anyone still use them) using standard file operations could result in access violations and unpredictable behavior.
Since one of the main purposes of logging is debugging a multithreaded application, consider using other means of file I/O: Streams and Windows API.
(And yes, I know it is not really an answer to the original question, but I do not wish to log in - therefor I do not have the reputation score to comment on Ryan J. Mills's practically wrong answer.)

Resources