Time and number counter in one? - siemens

I want to check how long a motor is working since the last check up and how many times the motor startet up since then.
#xSekundenzaehler := "Clock_1Hz";
REGION Allgemein
#Betriebszeit_Anlage(CU:="eHauptschuetz" AND #xSekundenzaehler,
R:="Lebensdauer".Allgemein.xResetBetriebszeit,
PV:=0,
CV=>#rSecondsAnlage);
"Lebensdauer".Allgemein.rLaufzeitAnlage := #rSecondsAnlage / 3600;
END_REGION
This is the code i used. Do i have to use another "CTU" again or is there a smarter way to do this in the already existing one that i don't know of?

Usually storing important values on CTU is not recommendable because you have no control on whether TiaPortal may restore the CTU instance.
It is preferable to build a counter by yourself so its value is kept on a non volatile db var:
"R_TRIG_1Hz"(CLK := "Clock_1Hz");
// Condition to count up running time
IF "eHauptschuetz" THEN
// Only at rising edges of seconds
IF "R_TRIG_1Hz".Q THEN
"DBZeit".LebensdauerSeconds := "DBZeit".LebensdauerSeconds + 1;
END_IF;
END_IF;
// Reset prevails over increment
IF #xReset THEN
#xReset := false;
"DBZeit".LebensdauerSeconds := 0;
END_IF;
// Hour total
"DBZeit".LebensdauerHours := "DBZeit".LebensdauerSeconds / 3600;
// Hour total with a decimal place
"DBZeit".LebensdauerDeciHours := "DBZeit".LebensdauerSeconds / 360;
This is the definition of the DB
Please note that only seconds are remanent, as hours and decihours are calculated on every cycle.
It would be better to store hours and decihours in other DB, so this one is dedicated to remanent values. When you modify the DB variables their values are reset to the start values.
Please remember to make a snapshot and copy them to start values prior to any changes.

Related

Count machine runtime in PLC

I am working on a simple machine project in PLC using structured text programming language. Now, I want to count the runtime (hours, mins and seconds) of a machine after Start Command is pressed. As I am new, I struggle to develop an idea. I tried to do it by using 3 counter for each seconds, minutes and hours. But could not achieve solid result. Can you please share the idea how to count runtime hours of a machine on PLC (ST or FB) ?
There are two issues here:
Correctly handling delta time
Using data types to your advantage
Since you want the time since the Start Command, you need to first store the time when the start command is pressed. Then you calculate the delta time (current time - start time) to obtain the seconds elapsed. Then take the seconds elapsed and get your hours, minutes, seconds. You can even simply use a TON FUNCTION_BLOCK for this - the ET output is in the TIME data type.
There are a number of good built-in data times for date and time. TOD (or TIME_OF_DAY) automatically is in the HH:MM:SS.mmm format for you. If your max elapsed time is less than 24 hours, you can use this format. If it could be longer, then use the TIME will go to 49 and some days, otherwise DT (DATE_AND_TIME) will count until the year 2106. You will get YYYY-MM-DD-HH:MM:SS in that format.
The following is a TON trick you can use, assuming 49 days or less is true.
PROGRAM PLC_PRG
VAR
Running : BOOL := FALSE;
Start : BOOL := FALSE;
MyTimer : TON;
Elapsed : TOD;
MyEt : TIME;
END_VAR
//calculate Elapsed time only if running
IF Running THEN
//ET is in milliseconds, so can easily convert to TOD
Elapsed := TIME_TO_TOD(MyEt);
ELSE
Elapsed := TOD#0:0:0.000;
END_IF
//run a TON timer, but set the PT to the absolute limit
//this trick is only valid for 49 days or less!
MyTimer(IN:=Running,PT:=DWORD_TO_TIME(16#FFFFFFFF),ET=>MyEt);
//only start once
IF Start AND NOT Running THEN
Running := TRUE;
Start := FALSE;
END_IF
If you are adverse to the trick, you can also do something like below (requires the SysTime library). In your online debugger, you will see Elapsed in HH:MM:SS format. Of course you should create a FUNCTION_BLOCK for this.
PROGRAM PLC_PRG
VAR
Running : BOOL := FALSE;
Start : BOOL := FALSE;
Now : ULINT;
StartTime : ULINT;
Elapsed : TOD;
pResult: SysTimeRtc.RTS_IEC_RESULT;
END_VAR
//get current time (result is in seconds)
Now := DWORD_TO_ULINT(SysTimeRtcGet(pResult));
//calculate Elapsed time only if running
IF Running THEN
//TOD is in milliseconds, so we x1000
Elapsed := ULINT_TO_TOD((Now - StartTime) * 1000);
ELSE
Elapsed := TOD#0:0:0.000;
END_IF
//only start once
IF Start AND NOT Running THEN
StartTime := Now;
Running := TRUE;
Start := FALSE;
END_IF

Pine scripting: how to find the price X days ago

In Pine Script, how do I find the price based on a certain number of days ago? I've tried something like this...
// Find the price 90 days ago
target = time - 90 * 60 * 60 * 24 * 1000
valuewhen(time < target, close, 1)
...however time < target never seems to return true – presumably because the current bar's time cannot also be in the past at the same time. Perhaps valuewhen() wasn't designed to be used with dynamic values that change on every bar?
Do I need to use a loop instead, and scan through every past bar until I find the date I'm looking for?
Perhaps there's a better way, but the workaround I'm using currently using is a function with a for loop, scanning backwards until the appropriate date is found. Here is my function:
priceXDaysAgo(numDays) =>
targetTimestamp = time - numDays*60*60*24*1000
// Declare a result variable with a "void" value
float result = if false
1
// We'll scan backwards through the preceding bars to find the first bar
// earlier than X days ago (it might be a little greater than X days if
// there was a break in trading: weekend, public holiday, etc.)
for i = 1 to 1000
if time[i] < targetTimestamp
result := close[i]
break
result
You can then call the function anywhere in your script:
priceXDaysAgo(90)

Instantiating of numerous function block in CODESYS (IEC61131)

I have made a function block using CODESYS to perform energy metering. The inputs of the function block is current and voltage and the output is energy.
Now, we need to have 1000 instances of this function block to run the code for 1000 meter we have. Writing (and possibly copy and pasting) of these instances doesn't seem to be the most interesting work.
Wondering if anybody has a smarter way of doing this numerous instantiation.
For example, here is how the code (in CODESYS) looks like for 2 instances:
meter_instance1(CURRENT:=I1, VOTAGE:=V2);
Energy1:= meter_instance1.ENERGY;
meter_instance2(CURRENT:=I2, VOTAGE:=V2);
Energy2:= meter_instance2.ENERGY;
And we like to have 1000 instances of it.
Any idea is highly appreciated.
Just make an array of the function block:
aEnergyMeter : array [0..999] of FB_EnergyMeter;
Also make arrays of the voltage and the current:
aVoltage : array [0..999] of INT; //if you use INT as type
aCurrent : array [0..999] of INT;
Then you can use it like that:
aEnergyMeter[0](CURRENT:= aCurrent[0], VOLTAGE := aVoltage[0]);
As you use different arrays with the same size, I would prefer to define some global constant:
VAR GLOBAL CONSTANT
firstDevice : UINT := 0;
lastDevice : UINT := 999;
END_VAR
Then you can define the array like that:
aEnergyMeter : array [firstDevice..lastDevice] of FB_EnergyMeter;
I agree with Arndt that you should use an array of function blocks and an array for voltage and current. Depending on your scan rate of your task you should be able to use a for loop to scan through all of your function blocks in a couple lines of code
var
meterInstance : array[1..1000] of FB_EnergyMeter;
voltage : array[1..1000] of int;
current : array[1..1000] of int;
energy : array[1..1000] of int;
end_var
for i:=1 to 1000 by 1 do
meterInstance[i](Voltage := voltage[i],Current:= current[i]);
energy[i] := meterInstance.Energy;
end_for;
in that for loop you could also combine some error checks while you're at it
for i:=1 to 1000 by 1 do
meterInstance[i](Voltage := voltage[i],Current:= current[i]);
if meterInstance.Energy > MaxEnergy then
//sound alarm
end_if;
end_for;
The only caveat with this approach is if the scan time is too fast for your task. You could possibly get a watch dog error as the task would overrrun. However since you are only doing 1000 elements and I am assuming your function block is not extremely complex you should be ok with this approach. If you have problems try extending scan time or watch error time.

Change system time of Beckhoff controller by programming

For the last couple of hours, I am facing problems with changing system date and time by structured-text programming. I have used function block FB_LocalSystemTime where I can read the system time. But I could not find any function or function block to write new system time. I have checked NT_SetLocalTime, that also did not work. Do you have any idea how can I do that?
For further information: I have included the sample code like:
/**
Declaration Part
**/
fbLocalSystemTime:FB_localSystemTime;
fbSetLocalTime:NT_SetLocalTime;
newTime:TIMESTRUCT:=(wHour:=5);
/**
DEFINITION PART
**/
fbLocalSystemTime(); /*This gives system time */
fbSetLocalTime.TIMESTR:=newTimne; /* New time to set */
fbSetLocalTime.START:=TRUE;
fbSetLocalTime(); /** This does NOT set the system time which I guess should set **/
I understand the question, but unfortunately I don't have much experience with beckhoff plcs. Have you tried calling their support line? This should be a non application specific question that should be easy for them to help you with.
You may consider using FB_LocalSystemTime in the way indicated below. This will synchronize the local PLC time with the system with the given AMS ID passed to the parameter sNetID. If you do not pass sNetID parameter local OS system will be used as reference for setting the local PLC time. The time will be synchronized on rising edge of the signal bEnable and then in the interval given by the parameter dwCycle
VAR
{attribute 'hide'}
LocalSysTime : FB_LocalSystemTime;
SynchNodeAmsId : STRING := '10.10.10.1.1.1';
END_VAR
LocalSysTime(
sNetID:= SynchNodeAmsId,
bEnable:= TRUE,
dwCycle:= 60,
dwOpt:= ,
tTimeout:= ,
bValid=> ,
systemTime=> ,
tzID=> );
You are correct. You should be using NT_SetLocalTime.
If you open the function block fbSetLocalTime(), you will realize your function block returns an error with error ID 1862.
The definition of the errors can be found here: https://infosys.beckhoff.com/english.php?content=../content/1033/tcplclib_tc2_utilities/18014398544490635.html&id=
1862 means error in win32 system.
This is because TIMESTRUCT consists of Year, Month, Week, etc, but you only initialize the Hour as 5. This means that other things will become 0. The Year needs to be between 1970 and 2106, and there are many things to follow, shown below:
After you use a valid TIMESTRUCT variable, your code should be able to execute without problem and your computer system will be changed.
Had a similiar problem, using TwinCat3. Sometimes it works to change local system time, sometimes not.
I use a small state machine to solve that (also there could be a more advanced solution) - just write 3 times the new time....
Here is an code example:
VAR
nState : BYTE := 0; //local state machine
ntSetLocalTime : Tc2_Utilities.NT_SetLocalTime;
tTimestructSet : Tc2_Utilities.TIMESTRUCT; // time to set
nErrId : UDINT;
nRetryCnt : BYTE := 0;
and the state machine:
CASE nState OF
0: //wait for change
bBusy := FALSE;
nRetryCnt := 0;
1: //trigger change
bBusy := TRUE;
ntSetLocalTime(
NETID:= '',
TIMESTR:= tTimestructSet,
START:= TRUE,
TMOUT:= ,
BUSY=> ,
ERR=> ,
ERRID=> );
nState := 2; //wait for writing
2: //wait till written
ntSetLocalTime(
START:= FALSE,
ERRID => nErrId);
IF NOT ntSetLocalTime.BUSY THEN
nState := 3;
END_IF
3: //retry and after some retries go back to init
nRetryCnt := nRetryCnt + 1;
IF nRetryCnt >=3 THEN
nState := 0;
ELSE
nState := 1;
END_IF
END_CASE
Otherwise you could call Beckhoff hotline, in most cases they have a really good support.

Cystal Report Accumulated Balance First Record Missing

I have used the following formula in the UnboundNumber (Accumulated balance), but the blue circled field is missing and wrong coming:
If Next({SP_Aging;1.AHead}) = Previous ({SP_Aging;1.AHead}) Then
numberVar X := (X + {SP_Aging;1.Balance} )
Else
X := 0 +{SP_Aging;1.Balance}
;
Try:
// {#Accumulated Balance}
// declare variable; mark as `global` to be explicit (w/o this, the variable is `global`, but this makes it clearer)
Global Numbervar balance;
// if this is the first row, increment the balance; always test for NULL values first in CR
If PreviousIsNull({SP_Aging;1.AHead}) Then
balance := {SP_Aging;1.Balance}
// if the current row's AHead is the same as previous row's value, increment balance
Else If {SP_Aging;1.AHead} = Previous({SP_Aging;1.AHead}) Then
balance := balance + {SP_Aging;1.Balance}
// otherwise, reset
Else
balance := {SP_Aging;1.Balance}