boris.nihil 0 Posted February 11, 2021 hi i need to send some HEX to POS terminal, amount of money, and POS will then check if credit card if valid and send me some answers..like this: (<STX><STX>message<ETX><LRC>) ECR-->POS: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C POS-->ECR: <ACK> POS-->ECR: <STX>32303030303030305542414349204B415254494355<ETX>2E ECR-->POS: <ACK> ------------------------------------------------------------------- I try with Indy IdTCPClient1 but I dont receive nothing from POS. Am i sending wrong code or I dont know how to receive answer ? They say i need do send HEX string.. So i translate this HEX: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C to 02023030303030303030303030313030301C313030301C1C2B301C3139311C1C1C1C1C1C1C1C1C1C1C30301C310C1C1C1C1C1C1C1C1C032C and send it like IdTCPClient1.IOHandler.WriteLn(edit7.Text, IndyTextEncoding_OSDefault); timer1.Enabled := true; This is how i wait for server ansver: procedure TForm1.Timer1Timer(Sender: TObject); var receivedtext:string; begin with IdTCPClient1 do begin try if IOHandler.InputBufferIsEmpty then begin IOHandler.CheckForDataOnSource(0); IOHandler.CheckForDisconnect; if IOHandler.InputBufferIsEmpty then Exit; end; receivedtext := IOHandler.ReadLn; except Timer1.Enabled := False; Exit; end; if receivedtext <> '' then ShowMessage(receivedtext); end; end; Share this post Link to post
Lars Fosdal 1792 Posted February 11, 2021 That protocol looks more like a Serial cable protocol, than a TCP protocol? What happens if you send it as binary bytes instead of text hex? I.e. each hex value converted to a byte. Share this post Link to post
boris.nihil 0 Posted February 11, 2021 7 minutes ago, Lars Fosdal said: That protocol looks more like a Serial cable protocol, than a TCP protocol? no, it is connected to local network with fixed ip 192.168.1.23 and port 3000.. on POS screen its written: LISTEN 192.168.1.23:3000 Share this post Link to post
Remy Lebeau 1405 Posted February 11, 2021 3 hours ago, boris.nihil said: i need to send some HEX to POS terminal, amount of money, and POS will then check if credit card if valid and send me some answers..like this: (<STX><STX>message<ETX><LRC>) Do you need to send an actual *hex-encoded string* for the <message> (ie, '303030...', bytes $33 $30 $33 $30 $33 $30 ...), or do you need to send actual binary bytes ($30 $30 $30 ...)? It makes a BIG difference. Do you have documentation for this POS protocol that you can share? 3 hours ago, boris.nihil said: I try with Indy IdTCPClient1 but I dont receive nothing from POS. Am i sending wrong code or I dont know how to receive answer ? Yes, you are using the wrong code. On the send, you should not be using WriteLn() at all, since there is no CRLF in this protocol. Use Write() instead. But whether you should use Write(string) or Write(TIdBytes) depends on what you are actually expected to send. On the receive, to use ReadLn() properly, you would need to specify ETX, #3, as the line terminator (or, you can use WaitFor(#3)), and then do a ReadByte() afterwards to read the checksum that follows the ETX. Otherwise, you would need to call ReadByte() in a loop until you encounter ETX (or, you could copy the code from ReadLn()/WaitFor() and adapt it for bytes instead of strings, not that hard). 3 hours ago, boris.nihil said: They say i need do send HEX string.. So i translate this HEX: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C to 02023030303030303030303030313030301C313030301C1C2B301C3139311C1C1C1C1C1C1C1C1C1C1C30301C310C1C1C1C1C1C1C1C1C032C That is wrong. I have worked in the past with STX/ETX protocols and a few POS protocols, you definitely need to send the <STX>, <FS> and <ETX> as binary bytes, not as hex-encoded strings. Try something more like this: var s: string; s := #2#2'303030303030303030303031303030'#28'31303030'#28#28'2B30'#28'313931'#28#28#28#28#28#28#28#28#28#28#28'3030'#28'31'#12#28#28#28#28#28#28#28#28#3'2C'; IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit); Though I suspect the POS is actually expecting binary data for the message content, not hex-encoded strings, so try something more like this: var s: string; s := #2#2'000000000001000'#28'1000'#28#28'+0'#28'191'#28#28#28#28#28#28#28#28#28#28#28'00'#28'1'#12#28#28#28#28#28#28#28#28#3','; IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit); 3 hours ago, boris.nihil said: This is how i wait for server ansver: That is fine, if you change the reading according to what I said above. Though, if you send a request and are expecting an immediate reply, why use a timer at all? Just read the reply immediately after sending the request. Share this post Link to post
Remy Lebeau 1405 Posted February 11, 2021 3 hours ago, Lars Fosdal said: That protocol looks more like a Serial cable protocol, than a TCP protocol? There are POS systems that use serial-like protocols over TCP. 3 hours ago, Lars Fosdal said: What happens if you send it as binary bytes instead of text hex? I.e. each hex value converted to a byte. That is what I'm thinking, too. Share this post Link to post
boris.nihil 0 Posted February 11, 2021 thanks a lot for a help, i will try it tommorow and see... Share this post Link to post
boris.nihil 0 Posted February 15, 2021 >> Though I suspect the POS is actually expecting binary data for the message content, not hex-encoded strings, so try something more like this: >> s := #2#2'000000000001000'#28'1000'#28#28'+0'#28'191'#28#28#28#28#28#28#28#28#28#28#28'00'#28'1'#12#28#28#28#28#28#28#28#28#3','; >> IdTCPClient1.IOHandler.Write(s, IndyTextEncoding_8bit); you were right, this works ! But now program hangs for a while waiting for some response or something.. How to receive ACK from POS terminal and other data from POS ? I need to send ACK after every returned answer from POS.. ECR-->POS: <STX><STX>303030303030303030303031303030<FS>31303030<FS><FS>2B30<FS>313931<FS><FS><FS><FS><FS><FS><FS><FS><FS><FS><FS>3030<FS>31<FF><FS><FS><FS><FS><FS><FS><FS><FS><ETX>2C POS-->ECR: <ACK> POS-->ECR: <STX>32303030303030305542414349204B415254494355<ETX>2E (insert CARD) ECR-->POS: <ACK> POS-->ECR: <STX>(CARD in process) ECR-->POS: <ACK> POS-->ECR: <STX>(wait) ECR-->POS: <ACK> POS-->ECR: <STX(aproved) ECR-->POS: <ACK> Maybe this code is not good ? answer := IdTCPClient1.IOHandler.ReadLn(); showmessage(answer ); Here is Protocol what POS terminal use.. Share this post Link to post
Remy Lebeau 1405 Posted February 16, 2021 (edited) 15 hours ago, boris.nihil said: you were right, this works ! But now program hangs for a while waiting for some response or something.. How to receive ACK from POS terminal and other data from POS ? I already explained to you earlier why your use of the IOHandler's ReadLn() method is incorrect in this situation, and how to use ReadLn() (or WaitFor()) to read the POS's messages correctly. The ACK doesn't change what I said earlier. You would simply do an extra read with ACK, #6, as the line terminator instead. However, since the ACK is only 1 byte, the IOHandler's ReadByte() method would be more appropriate, especially if the POS can ever send a NAK instead of an ACK. Quote I need to send ACK after every returned answer from POS.. You can use the IOHandler's Write(Byte) method for that. Quote Maybe this code is not good ? No, it is not sufficient by itself. I am not at a computer right now to write you working code, it will have to wait until tomorrow. Edited February 16, 2021 by Remy Lebeau Share this post Link to post
boris.nihil 0 Posted February 22, 2021 i tryed IdTCPClient1.IOHandler.InputBuffer.Clear; IdTCPClient1.IOHandler.Write(s + chr(strtoint(lrc)), IndyTextEncoding_8bit); and then i activate timer: if IdTCPClient1.IOHandler.InputBufferIsEmpty then begin if IdTCPClient1.IOHandler.CheckForDataOnSource(500) then begin receivedtext1 := IdTCPClient1.IOHandler.WaitFor(#6,true,true,IndyTextEncoding_8bit); memo1.SelStart := memo1.GetTextLen; memo1.SelLength := 0; memo1.SelText := 'Received ACK:' + receivedtext1 + #13#10; sleep(500); receivedtext := IdTCPClient1.IOHandler.WaitFor(#3,true,true,IndyTextEncoding_8bit); memo1.SelStart := memo1.GetTextLen; memo1.SelLength := 0; memo1.SelText := 'Received message:' + receivedtext + #13#10; end; Share this post Link to post
Remy Lebeau 1405 Posted February 23, 2021 (edited) On 2/22/2021 at 8:36 AM, boris.nihil said: IdTCPClient1.IOHandler.InputBuffer.Clear; Why are you clearing the InputBuffer? If you are reading messages correctly, you should never need to do that. Quote receivedtext1 := IdTCPClient1.IOHandler.WaitFor(#6,true,true,IndyTextEncoding_8bit); As I said earlier, since the ACK/NAK is only 1 byte, you should use ReadByte() instead of WaitFor(), eg: var b: Byte; b := IdTCPClient1.IOHandler.ReadByte; if b = $6 then // ACK begin // read POS's next message... end else if b = $15 then // NAK begin // failure of previous message you sent... end else begin // unexpected, do something... end; Personally, I would just get rid of the timer completely. Following the flow chart you posted earlier is easier when using blocking I/O operations with the IOHandler's ReadTimeout property, eg: var iTry: Integer; b, lrc: Byte; ReceivedText: string; procedure AddToMemo(const S: string); begin Memo1.SelStart := Memo1.GetTextLen; Memo1.SelLength := 0; Memo1.SelText := S + sLineBreak; end; begin iTry := 0; try IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit; IdTCPClient1.IOHandler.ReadTimeout := 1000; repeat IdTCPClient1.IOHandler.Write(...); // send message to POS try b := IdTCPClient1.IOHandler.ReadByte; // wait for ACK/NAK except on E: EIdReadTimeout do begin Inc(iTry); if iTry < 3 then Continue; raise; end; end; if b <> $6 then // not ACK? begin Inc(iTry); if iTry < 3 then Continue; raise Exception.Create('No ACK received'); end; repeat // wait for message... ReceivedText := IdTCPClient1.IOHandler.WaitFor(#3, True, True); lrc := IdTCPClient1.IOHandler.ReadByte; AddToMemo('Received message:' + ReceivedText + Char(lrc)); if (ReceivedText is not valid) then begin IdTCPClient1.IOHandler.Write(Byte($15)); // send NAK Continue; // keep waiting... end; IdTCPClient1.IOHandler.Write(Byte($6)); // send ACK if (ReceivedText is HOLD message) then Continue; // keep waiting... // use ReceivedText as needed... Break; // all done... until False; until False; except on E: Exception do AddToMemo('ERROR! ' + E.Message); end; end; But, if you really want to use a separate timer, you should implement a state machine, eg: type TState = (stIdle, stSendingMsg, stWaitingForAck, stWaitingForMsg, stError); private iTry: Integer; CurrentMsg: string; State: TState; ... procedure TMyForm.AddToMemo(const S: string); begin Memo1.SelStart := Memo1.GetTextLen; Memo1.SelLength := 0; Memo1.SelText := S + sLineBreak; end; procedure TMyForm.IdTCPClient1Connected(Sender: TObject); begin IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit; IdTCPClient1.IOHandler.ReadTimeout := 1000; State := stIdle; end; procedure TMyForm.DoSomething; begin iTry := 0; CurrentMsg := ...; State := stSendingMsg; Timer1.Interval := 10; Timer1.Enabled := True; end; procedure TMyForm.Timer1Timer(Sender: TObject); var b, lrc: Byte; ReceivedText: string; begin case State of stSendingMsg: begin try IdTCPClient1.IOHandler.Write(CurrentMsg); // send message to POS except on E: Exception do begin State := stError; Timer1.Enabled := False; AddToMemo('ERROR! ' + E.Message); Exit; end; end; State := stWaitingForAck: end; stWaitingForAck: begin try b := IdTCPClient1.IOHandler.ReadByte; // wait for ACK/NAK except on E: Exception do begin if E is EIdReadTimeout then begin Inc(iTry); if iTry < 3 then begin State := stSendingMsg; Exit; end; end; State := stError; Timer1.Enabled := False; AddToMemo('ERROR! ' + E.Message); Exit; end; end; if b <> $6 then // not ACK? begin Inc(iTry); if iTry < 3 then begin State := stSendingMsg; end else begin State := stError; Timer1.Enabled := False; AddToMemo('ERROR! No ACK received'); end; Exit; end; State := stWaitingForMsg; end; stWaitingForMsg: begin try ReceivedText := IdTCPClient1.IOHandler.WaitFor(#3, True, True); lrc := IdTCPClient1.IOHandler.ReadByte; except on E: Exception do begin State := stError; Timer1.Enabled := False; AddToMemo('ERROR! ' + E.Message); Exit; end; end; AddToMemo('Received message:' + ReceivedText + Char(lrc)); if (ReceivedText is not valid) then begin IdTCPClient1.IOHandler.Write(Byte($15)); // send NAK Exit; // keep waiting... end; IdTCPClient1.IOHandler.Write(Byte($6)); // send ACK if (ReceivedText is HOLD message) then Exit; // keep waiting... // use ReceivedText as needed... State := stIdle; Timer1.Enabled := False; end; end; end; Edited February 23, 2021 by Remy Lebeau Share this post Link to post
boris.nihil 0 Posted February 25, 2021 Thank a lot for a time you spend for this.. Solution 2 with timer works on some way...it's hard to work since i can't play with this device so i need to improvise.. Problem is that timer must work all the time until transaction is complete.. After i received message 'INSERT CARD' timer stops and i can't read anymore. Could you help me how to send some message from server I made with IdTCPServer1 component to Client program with some Button on server form ? Something like "AContext.Connection.IOHandler.Write"... Share this post Link to post
Remy Lebeau 1405 Posted February 25, 2021 (edited) 1 hour ago, boris.nihil said: Problem is that timer must work all the time until transaction is complete.. After i received message 'INSERT CARD' timer stops and i can't read anymore. The code I presented is only handling 1 request/response at a time, per the documented flow chart. If there are multiple messages involved in a single transaction, then of course you will have to adjust the code to account for that. In the part where it says "use ReceivedText as needed...", you would have to actually look at the message data, and if it is INSERT CARD then progress to the next state and act accordingly to continue the transaction, not go back to the stIdle state until the transaction is finished. Quote Could you help me how to send some message from server I made with IdTCPServer1 component to Client program with some Button on server form ? Something like "AContext.Connection.IOHandler.Write"... I have posted examples of that MANY times before, in many different forums. Should not be hard to find with some searching around. The best way to handle this is to implement a per-client thread-safe queue for each TIdContext, then your UI thread can push data into the queue of the desired client as needed, and the server's OnExecute event can send queued data for the calling TIdContext when it is safe to do so. Edited February 25, 2021 by Remy Lebeau Share this post Link to post
boris.nihil 0 Posted February 26, 2021 ok..thanks a lot for all the help! Share this post Link to post
NLM 0 Posted November 10, 2023 Hi sorry for my request but i need to integrate same procedure in c#. Could you send me protocol manual to help me? Share this post Link to post