FB_FileOpen stays busy, Statemachine not working - TwinCat3 - plc

i am trying to get into the beckhoff/twincat universe, therefore is was following along with some twincat tutorials. While programming a simple event-logger I encountered the following problem:
After executing FB_FileOpen, it´s bBusy variable stays True - therefore my statemachine won´t execute any further and is stuck in FILE_OPEN. Any idea, what I did wrong? Here is my code:
VAR
FileOpen : FB_FileOpen := (sPathName := 'C:\Events-log.txt', nMode := FOPEN_MODEAPPEND OR FOPEN_MODETEXT);
FileClose :FB_FileClose;
FilePuts : FB_FilePuts;
stEventWrittenToFile : ST_Event;
CsvString : T_MaxString;
eWriteState :(TRIGGER_FILE_OPEN, FILE_OPEN, WAIT_FOR_EVENT,TRIGGER_WRITE_EVENT, WRITE_EVENT, FILE_CLOSE, ERROR);
END_VAR
CASE eWriteState OF
TRIGGER_FILE_OPEN :
FileOpen(bExecute := TRUE);
eWriteState := FILE_OPEN;
FILE_OPEN :
FileOpen(bExecute := FALSE);
IF FileOpen.bError THEN
eWriteState := ERROR;
ELSIF NOT FileOpen.bBusy AND FileOpen.hFile <> 0 THEN
eWriteState := WAIT_FOR_EVENT;
END_IF
WAIT_FOR_EVENT :
//Do nothing, triggered externally by method
TRIGGER_WRITE_EVENT :
CsvString := ConvertStructureToString(stEvent := stEventWrittenToFile);
FilePuts( sLine:= CsvString,
hFile := FileOpen.hFile,
bExecute := TRUE,);
eWriteState := WRITE_EVENT;
WRITE_EVENT :
FilePuts(bExecute := FALSE);
IF FilePuts.bError THEN
eWriteState := ERROR;
ELSIF NOT FilePuts.bBusy THEN
eWriteState := FILE_CLOSE;
END_IF
FILE_CLOSE :
FileClose( hFile := FileOpen.hFile,
bExecute := TRUE);
IF FileClose.bError = TRUE THEN
FileClose.bExecute := FALSE;
eWriteState := ERROR;
ELSIF NOT FileClose.bBusy THEN
FileClose.bExecute := FALSE;
eWriteState := TRIGGER_FILE_OPEN;
END_IF
ERROR : // Do nothing
END_CASE

The issue probably lies in how you call the function block. You need to make sure to call the function block with the input bExecute := FALSE and only after that calling it with bExecute := TRUE will trigger the function block execution. Caliing the fb with its "exectue" input to false after it has had the input triggered, will always work so just invert your order of TRUE and FALSE executes for all your states.
TRIGGER_FILE_OPEN:
fileOpen(bExecute := FALSE);
eWriteState := FILE_OPEN;
FILE_OPEN:
fileOpen(bExecute := TRUE);
...
You could also follow the Beckhoff example provided on their website, not a fan of this, but calling the function block twice, back to back in a single PLC cycle :
(* Open source file *)
fbFileOpen( bExecute := FALSE );
fbFileOpen( sNetId := sSrcNetId,
sPathName := sSrcPathName,
nMode := FOPEN_MODEREAD OR FOPEN_MODEBINARY,
ePath := PATH_GENERIC,
tTimeout := tTimeOut,
bExecute := TRUE );
Full example can be found here : https://infosys.beckhoff.com/english.php?content=../content/1033/tcplclib_tc2_system/30977547.html&id=

I found the error.
My mistake was, that I started the state machine with a positive edge from a start variable. Since I am running the task in a 1ms cycle, the whole thing would´ve needed to complete within 1ms then.

Related

How to implement FB GetLocalAmsNetId?

this is my first question here on stackoverflow and im hoping someone might be able to help me out.
I'm trying to get the local AmsNetId of my TwinCat PLC system. The Code is running on the TwinCat System locally.
The function is working properly, no problems compiling. But the functionblock FB_GetLocalAmsNetId never seems to return the Ams Net Id. fbGetAmsNetId.bBusy is always busy.
I dont know what i'm doing wrong.
Variables:
FUNCTION_BLOCK FB_GetAmsNetId
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
fbGetAmsNetId : FB_GetLocalAmsNetId;
bRequestStarted : BOOL := FALSE;
sAmsNetId : T_AmsNetId;
END_VAR
Programcode:
IF(bRequestStarted = FALSE) THEN
fbGetAmsNetId(bExecute := TRUE, tTimeOut := T#2S);
bRequestStarted := TRUE;
ELSE
IF(NOT fbGetAmsNetId.bBusy) THEN
sAmsNetId := fbGetAmsNetId.AddrString;
fbGetAmsNetId.bExecute := FALSE;
bRequestStarted := FALSE;
END_IF
END_IF
You need to cyclically call fbGetAmsNetId in your code, otherwise FB_GetLocalAmsNetId will not be able to finish it's internal operations beeing executed for only one plc cycle.
For example:
fbGetAmsNetId();
IF(bRequestStarted = FALSE) THEN
fbGetAmsNetId(bExecute := TRUE, tTimeOut := T#2S);
bRequestStarted := TRUE;
ELSE
IF(NOT fbGetAmsNetId.bBusy) THEN
sAmsNetId := fbGetAmsNetId.AddrString;
fbGetAmsNetId.bExecute := FALSE;
bRequestStarted := FALSE;
END_IF
END_IF

Is there a timer function or variable in Codesys as in arduino millis()?

Is there a timer function or variable in Codesys as in arduino millis() ?
If not, how can I create a timer?
Thanks!
In CoDeSys function TIME() return time in milliseconds from PLC start. If you want to start the count on the event you can use triggers to create a time point.
VAR
tStarted, tElapsed : TIME;
END_VAR
fbR_TRIG(CLK := xStart);
IF (fbR_TRIG.Q) THEN
tStarted := TIME();
END_IF;
tElapsed := TIME() - tStarted;
And rest follows like reset the timer, pause counting, etc.
You can build one yourself.
Here an example:
Declaration part:
FUNCTION_BLOCK FB_Millis
VAR_INPUT
timer : TON := (IN:=TRUE,PT:=maxTime);
END_VAR
VAR_OUTPUT
tElapsedTime : TIME;
END_VAR
VAR
maxTime : TIME := UDINT_TO_TIME(4294967295);
//timer : TON := (IN:=TRUE,PT:=maxTime);
END_VAR
Implementation part:
timer();
tElapsedTime := timer.ET;
You call it cyclically like this:
fbMillis();
And retrieve the result like this:
tElapasedTime := fbMillis.tElapsedTime;
FB_Millis overflows after 49days 17hours 2minutes 47seconds and 295ms.
If you want to compare the elapsed time from fbMillis.tElapsedTime with another variable you do like this:
IF fbMillis.tElapsedTime < tAnotherTimeVar
THEN
; //Do something
ELSE
; //Do something else
END_IF
If you instead just want a simple timer you need the TON Function Block:
Declaration part:
//2 seconds timer
mySimpleTimer : TON := (PT:=T#2s);
Implementation part:
mySimpleTimer();
// your code here
//Start timer
mySimpleTimer.IN := TRUE;
//Check if timer has reached desired time
IF mySimpleTime.Q
THEN
//Do something here
mySimpleTimer.IN := FALSE;
END_IF

Delphi XE5 TRESTRequest getting ssl3_read_bytes:sslv3 alert handshake failure

I'm getting the error
REST request failed! Error connecting with SSL. error:14094410:SSL
routines:ssl3_read_bytes:sslv3 alert handshake failure
using the TREST components, here is the example code -- full unit code below
seems to be specific to the url link e.g. other https: calls work fine
function Getcall_UsingRest : String;
var
fRstclnt1: TRESTClient;
fRstrqst1: TRESTRequest;
fRstrspns1: TRESTResponse;
begin
result := '';
try
fRstclnt1:= TRESTClient.Create('https://au-api.basiq.io');
fRstrqst1:= TRESTRequest.Create(nil);
fRstrspns1:= TRESTResponse.Create(nil);
try
fRstrqst1.Client := fRstclnt1;
fRstrqst1.Response := fRstrspns1;
fRstrqst1.Execute;
result := fRstrspns1.Content;
finally
fRstclnt1.Free;
fRstrqst1.Free;
fRstrspns1.Free;
end;
except
on E:Exception do begin
result := e.Message;
end;
end;
end;
I have been trying the following to try and fix but i haven't been able to get it to work
using TIdHTTP component i get the same error
function Getcall_UsingHTTP_WithSameIssue : String;
var
fIdHTTP1: TIdHTTP;
fIdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
fresp: TMemoryStream;
fMySL : TStringList;
begin
result := '';
try
fIdHTTP1:= TIdHTTP.Create(nil);
try
fIdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(fIdHTTP1);
fIdHTTP1.IOHandler := fIdSSLIOHandlerSocketOpenSSL1;
fresp := TMemoryStream.Create;
fMySL := TStringList.Create;
try
fIdHTTP1.Get('https://au-api.basiq.io', fresp);
fresp.Position := 0;
fMySL.LoadFromStream( fresp );
result := fMySL.Text;
finally
fMySL.Free;
fresp.Free;
end;
finally
fIdHTTP1.Free;
end;
except
on E:Exception do begin
result := e.Message;
end;
end;
end;
there is a fix for this which is to put
procedure OnStatusInfoEx(ASender: TObject; const AsslSocket: PSSL;
const AWhere, Aret: TIdC_INT; const AType, AMsg: String);
begin
SSL_set_tlsext_host_name(AsslSocket, Request.Host);
end;
onto the fIdSSLIOHandlerSocketOpenSSL1.OnStatusInfoEx := OnStatusInfoEx;
the TIdSSLIOHandlerSocketOpenSSL component is buried deep in the TRESTClient and i haven't been able to find a way to see if i can apply the fix above to work on the TRESTClient
can someone help with how to get the TREST to be able to communicate
here is the full source code for the examples above
unit RestExample;
interface
uses
System.Generics.Collections
, REST.Types
, REST.Client
, sysutils
, IdHTTP
, IdSSLOpenSSL
, IdSSLOpenSSLHeaders, IdCTypes
, system.Classes
;
function Getcall_UsingRest : String;
function Getcall_UsingHTTP_WithSameIssue : String;
function Getcall_UsingHTTP_WithFix : String;
type
TCustomIdHTTP = class(TIdHTTP)
public
constructor Create(AOwner: TComponent);
private
procedure OnStatusInfoEx(ASender: TObject; const AsslSocket: PSSL; const AWhere, Aret: TIdC_INT; const AType, AMsg: String);
end;
implementation
function Getcall_UsingRest : String;
var
fRstclnt1: TRESTClient;
fRstrqst1: TRESTRequest;
fRstrspns1: TRESTResponse;
begin
result := '';
try
fRstclnt1:= TRESTClient.Create('https://au-api.basiq.io');
fRstrqst1:= TRESTRequest.Create(nil);
fRstrspns1:= TRESTResponse.Create(nil);
try
fRstrqst1.Client := fRstclnt1;
fRstrqst1.Response := fRstrspns1;
fRstrqst1.Execute;
result := fRstrspns1.Content;
finally
fRstclnt1.Free;
fRstrqst1.Free;
fRstrspns1.Free;
end;
except
on E:Exception do begin
result := e.Message;
end;
end;
end;
function Getcall_UsingHTTP_WithSameIssue : String;
var
fIdHTTP1: TIdHTTP;
fIdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
fresp: TMemoryStream;
fMySL : TStringList;
begin
result := '';
try
fIdHTTP1:= TIdHTTP.Create(nil);
try
fIdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(fIdHTTP1);
fIdHTTP1.IOHandler := fIdSSLIOHandlerSocketOpenSSL1;
fresp := TMemoryStream.Create;
fMySL := TStringList.Create;
try
fIdHTTP1.Get('https://au-api.basiq.io', fresp);
fresp.Position := 0;
fMySL.LoadFromStream( fresp );
result := fMySL.Text;
finally
fMySL.Free;
fresp.Free;
end;
finally
fIdHTTP1.Free;
end;
except
on E:Exception do begin
result := e.Message;
end;
end;
end;
function Getcall_UsingHTTP_WithFix : String;
var
fTCustomIdHTTP: TCustomIdHTTP;
fresp: TMemoryStream;
fMySL : TStringList;
begin
result := '';
try
fTCustomIdHTTP:= TCustomIdHTTP.Create(nil);
try
fresp := TMemoryStream.Create;
fMySL := TStringList.Create;
try
fTCustomIdHTTP.Get('https://au-api.basiq.io', fresp);
fresp.Position := 0;
fMySL.LoadFromStream( fresp );
result := fMySL.Text;
finally
fMySL.Free;
fresp.Free;
end;
finally
fTCustomIdHTTP.Free;
end;
except
on E:Exception do begin
result := e.Message;
end;
end;
end;
{ TCustomIdHTTP }
constructor TCustomIdHTTP.Create(AOwner: TComponent);
begin
IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
with IOHandler as TIdSSLIOHandlerSocketOpenSSL do begin
OnStatusInfoEx := Self.OnStatusInfoEx;
end;
inherited Create(AOwner);
end;
procedure TCustomIdHTTP.OnStatusInfoEx(ASender: TObject; const AsslSocket: PSSL;
const AWhere, Aret: TIdC_INT; const AType, AMsg: String);
begin
SSL_set_tlsext_host_name(AsslSocket, Request.Host);
end;
end.

Cannot receive contentType "application/pdf" using RestResponse

My application sends data(personal info) using "application/json". If data is valid, then, server sends me back a PDF File through "application/pdf". But when RestResponse arrives, an exception pops out: "No mapping for the unicode character exists in the target multibyte code page". I don't know whats happening. I'm working on this in the past 4 days and I can't fix this. Exception pops out at line: "RRequest.Execute;". Here's my code guys:
procedure TThreadBoleto.Execute;
var
RCtx : TRttiContext;
RType : TRttiType;
RProp : TRttiProperty;
I : integer;
PDF : TFile;
begin
try
try
RClient := TRESTClient.Create('');
with RClient do begin
AcceptEncoding := 'identity';
FallbackCharsetEncoding := 'UTF-8';
Accept := 'application/json;text/plain;application/pdf;';
AcceptCharset := 'UTF-8';
BaseURL := 'https://sandbox.boletocloud.com/api/v1/boletos';
ContentType := 'application/x-www-form-urlencoded';
HandleRedirects := true;
RCtx := TRttiContext.Create;
RType := RCtx.GetType(THRBoleto.ClassType);
I := 0;
for RProp in RType.GetProperties do
begin
Params.AddItem;
Params.Items[I].name := LowerCase(RProp.Name.Replace('_','.'));
Params.Items[I].Value := RProp.GetValue(THRBoleto).AsString;
I := I + 1;
end;
end;
RRequest := TRESTRequest.Create(RRequest);
with RRequest do begin
Accept := 'application/json;text/plain;application/pdf;';
Client := RClient;
Method := rmPost;
SynchronizedEvents := false;
AcceptCharset := 'UTF-8';
end;
RResponse := TRESTResponse.Create(RResponse);
RResponse.ContentType := 'application/pdf;*/*;';
RResponse.ContentEncoding := 'UTF-8';
RAuth := THTTPBasicAuthenticator.Create('','');
with RAuth do begin
Username := 'anAPItokenAccess';
Password := 'token';
end;
RClient.Authenticator := RAuth;
RRequest.Response := RResponse;
RRequest.Execute;
PDF.WriteAllBytes(ExtractFilePath(Application.ExeName)+'boleto.pdf',RResponse.RawBytes);
OutputStrings.Add(RResponse.Content);
OutputStrings.Add('');
OutputStrings.Add('');
OutputStrings.AddStrings(RResponse.Headers);
except on E:Exception do
ShowMessage('Error: '+E.Message);
end;
finally
THRBoleto.Free;
end;
end;
Are you sure the error is happening on the RRequest.Execute() call? You are trying to receive the PDF data as a String when you read the RResponse.Content property, so I would expect a Unicode error on that call instead. A PDF file is not textual data, it is binary data, so the only safe way to receive it is with the RResponse.RawBytes property.
Also, you should not be setting the RResponse.ContentType or RResponse.ContentEncoding properties at all (not to mention that you are setting them to invalid values anyway). They will be filled in according to the actual response that is received.
Since you are setting the RRequest.Accept property to include 3 different media types that you are willing to accept in a response, you need to look at the RResponse.ContentType property value to make sure you are actually receiving a PDF file before saving the RawBytes to a .pdf file. If you receive a text or JSON response instead, you can't process them as a PDF.
Personally, I find the REST components to be quite buggy. You might have better luck using Indy's TIdHTTP instead, eg:
uses
..., IdGlobal, IdGlobalProtocols, IdHTTP, IdSSLOpenSSL;
procedure TThreadBoleto.Execute;
var
RCtx : TRttiContext;
RType : TRttiType;
RProp : TRttiProperty;
Client: TIdHTTP;
Params: TStringList;
Response: TMemoryStream;
begin
Client := TIdHTTP.Create;
try
with Client.Request do begin
AcceptEncoding := 'identity';
Accept := 'application/json;text/plain;application/pdf';
AcceptCharset := 'UTF-8';
ContentType := 'application/x-www-form-urlencoded';
BasicAuthentication := True;
Username := 'anAPItokenAccess';
Password := 'token';
end;
Client.HandleRedirects := true;
RCtx := TRttiContext.Create;
RType := RCtx.GetType(THRBoleto.ClassType);
Response := TMemoryStream.Create;
try
Params := TStringList.Create;
try
for RProp in RType.GetProperties do
Params.Add(LowerCase(RProp.Name.Replace('_','.')) + '=' + RProp.GetValue(THRBoleto).AsString);
Client.Post('https://sandbox.boletocloud.com/api/v1/boletos', Params, Response);
finally
Params.Free;
end;
Response.Position := 0;
case PosInStrArray(ExtractHeaderMediaType(Client.Response.ContentType), ['application/pdf', 'application/json', 'text/plain'], False) of
0: begin
// save PDF
Response.SaveToFile(ExtractFilePath(Application.ExeName)+'boleto.pdf');
OutputStrings.Add('[PDF file]');
end;
1: begin
// process JSON as needed
OutputStrings.Add(ReadStringAsCharset(Response, Client.Response.Charset));
end;
2: begin
// process Text as needed
OutputStrings.Add(ReadStringAsCharset(Response, Client.Response.Charset));
end;
else
// something else!
OutputStrings.Add('[Unexpected!]');
end;
finally
Response.Free;
end;
OutputStrings.Add('');
OutputStrings.Add('');
OutputStrings.AddStrings(Client.Response.RawHeaders);
finally
Client.Free;
end;
end;
procedure TThreadBoleto.DoTerminate;
begin
if FatalException <> nil then
begin
// Note: ShowMessage() is NOT thread-safe!
ShowMessage('Error: ' + Exception(FatalException).Message);
end;
THRBoleto.Free;
inherited;
end;

Blob to String : How to convert Blob to string from StoredProcedure Parameter in PostgreSQL?

I have stored procedure (function in Postgres) with type of parameter like this :
Params[0] : result = ftBlob // postgresql function = text
Params[1] : 1 = ftString
Params[2] : 2 = ftInteger
Params[3] : 3 = ftInteger
my code is like this :
procedure TForm1.Button1Click(Sender: TObject);
var
ResultStr: TResultStr;
BlobField: TBlobField;
bStream: TStream;
DataSet: TDataSet;
StoredProc: TSQLStoredProc;
begin
sp01.Close;
sp01.Params[1].AsString := '2010/2011';
sp01.Params[2].AsInteger := 2;
sp01.Params[3].AsInteger := 1;
sp01.ExecProc;
if sp01.ParamByName('result').Value.IsBlob then
begin
BlobField := StoredProc.ParamByName('result') as TBlobField;
bStream := sp01.CreateBlobStream(BlobField, bmRead);
try
bStream.Read(ResultStr,sizeof(TResultStr));
finally
bStream.Free;
end;
end;
ShowMessage(ResultStr.Hasil);
end;
the question is, how do I want to get the result (Blob) become string ?
I don't know what TResultString is, but you can do it with a string:
var
BlobResult: string; // Changed to make clearer where changes were below
begin
// Your other code here
if sp01.ParamByName('result').Value.IsBlob then
begin
BlobField := StoredProc.ParamByName('result') as TBlobField;
bStream := sp01.CreateBlobStream(BlobField, bmRead);
try
SetLength(BlobResult, bStream.Size); // Note changes here
bStream.Read(BlobResult[1], bStream.Size); // and here
finally
bStream.Free;
end;
end;
This is an old post but if anyone needs in the future.
ShowMessage(BlobField.AsString);