Display query in ABB HMI using software panel builder 600 - display

I am using ABB HMI and programming it on panel builder 600. I have used meters to display angles and set the scale from -100 to +100. I have acheived success in displaying angles but the problem is the change in angle is very frequent and the needle of the meter gets out of control. For example: the angle is 5 degree then it suddenly increased to 10 degree and the decreased to 3 degree again in a very short span of time and my needle in display meter gets out of control. What should I do to resolve this issue? I am using ABB plc and writing my code in codesys in CFC language. Awaiting for the helpful replies TIA

Decreasing Sampling Rate
VAR
plcValue: INT; // this value changes a lot
hmiValue: INT := plcValue; // this value is sent to the HMI to be displayed
sampleRate: TIME := T#2S; // hmiValue will change every 2 seconds
timer: TON; // the timer
END_VAR
timer(IN := TRUE, PT := sampleRate);
IF (timer.Q) THEN
hmiValue := plcValue;
timer(IN := FALSE, PT := sampleRate); // reset
END_IF
Moving Average
VAR CONSTANT
SIZE: INT := 100; // the number of values to average
END_VAR
VAR
plcValue: INT; // this value changes a lot
hmiValue: INT := plcValue; // this value is sent to the HMI to be displayed
movingAverage: ARRAY [0..SIZE] OF INT; // last SIZE number of values of plcValue
maIndex: INT := 0;
maFilled: BOOL;
sum: REAL;
i: INT;
END_VAR
movingAverage[maIndex] := plcValue;
sum := 0;
IF (maFilled) THEN
FOR i := 0 TO SIZE DO
sum := sum + movingAverage[i];
END_FOR
hmiValue := REAL_TO_INT(sum / SIZE);
ELSE
FOR i := 0 TO maIndex DO
sum := sum + movingAverage[i];
END_FOR
hmiValue := REAL_TO_INT(sum / (maIndex + 1));
END_IF
IF (maIndex = SIZE) THEN
maIndex := 0;
maFilled := TRUE;
ELSE
maIndex := maIndex + 1;
END_IF
Comparison
running this code:
IF (plcValue = 5) THEN
plcValue := 10;
ELSIF (plcValue = 10) THEN
plcValue := 3;
ELSE
plcValue := 5;
END_IF
Reduced sampling rate results in the hmiValue still jumping every 2 seconds (or whatever sampleRate was set), while moving average was stuck at 6, which usually makes it the more preferred of the two, though a little bigger codewise, as well as slower to execute (though it shouldn't matter, unless you are counting thousands of averages every cycle). You can also change the average size: The bigger it is, the smoother the value, but also slower to react to change. Try not to make it too big

You can use some different blocks on OSCAT library (It's a 3rd party free library. You need to downloade it if you want to use it). I know you work in CFC and perhaps you are not familiar with ST, but this is best way to represent how to solve your task.
FADE
This block allows slowly change value from one value to another.
PROGRAM PLC_PRG
VAR
iValue: INT(-100..100); (* Value input *)
iGauge: INT(-100..100); (* Smoothed Value for HMI *)
fbFade: FADE; (* fade block *)
END_VAR
(* Play with TF parameter to achieve desired smoothness *)
fbFade(IN1 := INT_TO_REAL(iValue), IN2 := INT_TO_REAL(iGauge), F := FALSE, TF := T#500MS);
iGauge := REAL_TO_INT(fbFade.Y);
END_PROGRAM
FILTER_I
This block averages value for a given time interval. FILTER_I is a filter of the first degree for 16-bit INT data.
PROGRAM PLC_PRG
VAR
iValue: INT(-100..100); (* Value input *)
iGauge: INT(-100..100); (* Smoothed Value for HMI *)
fbFilter: FILETR_I; (* filter block *)
END_VAR
(* Play with T parameter to achieve desired smoothness *)
fbFilter(X := iValue, T := T#500MS, Y => iGauge);
END_PROGRAM
FILTER_MAV_W
And another filter is like #Guiorgy made en example based not on time but on number of values stored which is called MA (Moving Average).
PROGRAM PLC_PRG
VAR
iValue: INT(-100..100); (* Value input *)
iGauge: INT(-100..100); (* Smoothed Value for HMI *)
fbFilter: FILTER_MAV_W; (* filter block *)
END_VAR
(* Play with N parameter to achieve desired smoothness *)
fbFilter(X := INT_TO_WORD(iValue), N := INT#32);
iGauge := WORD_TO_INT(fbFilter.Y);
END_PROGRAM

Related

Unexpected behaviour reusing a TMemoryStream in Delphi

I am trying to read two strings of varying length from a TMemoryStream, but both streams end up being the same length. So if, for example, the first string is 'abcdefghijkl', and the second one is 'wxyz', the value I get for the second string is 'wxyzefghijkl' (the first four characters of my new string ('wxyz') followed by the remaining characters of the 1st string that have not been replaced by 'wxyz'
My code is:-
var
L : LongInt
S : string;
...
msRecInfo.Position := 0;
msRecInfo.Read(L, SizeOf(L)); // read size of following string ...
SubStream.Clear;
SubStream.CopyFrom(msRecInfo, L); // copy next block of data to a second TMemoryStream
if (L > 0) then S := StreamToString(SubStream); //convert the stream into a string
msRecInfo.Read(L, SizeOf(L)); // get size of following string ...
SubStream.CopyFrom(msRecInfo, L);
if (L > 0) then S := StreamToString(SubStream);
I have been battling with this for hours without success. Can anyone point out what I am doing wrong?
You are not calling SubStream.Clear() before the 2nd call to SubStream.CopyFrom(). So, the 1st call to StreamToString(SubStream) leaves SubStream.Position at the end of the stream, then the subsequent SubStream.CopyFrom() adds more data to the stream, preserving the existing data. Then the subsequent StreamToString(SubStream) reads all of the data from SubStream.
Also, be aware that if L is 0 when you pass it to SubStream.CopyFrom(), it will copy the entire msRecInfo stream. This is documented behavior:
https://docwiki.embarcadero.com/Libraries/en/System.Classes.TStream.CopyFrom
If Count is 0, CopyFrom sets Source position to 0 before reading and then copies the entire contents of Source into the stream. If Count is greater than or less than 0, CopyFrom reads from the current position in Source.
So, you need to move up your L > 0 check, eg:
msRecInfo.Read(L, SizeOf(L));
if (L > 0) then
begin
SubStream.Clear;
SubStream.CopyFrom(msRecInfo, L);
S := StreamToString(SubStream);
end
else
S := '';
I would suggest wrapping this logic into a reusable function, eg:
var
L : LongInt;
S : string;
function ReadString: string;
begin
msRecInfo.Read(L, SizeOf(L)); // read size of following string ...
if (L > 0) then
begin
SubStream.Clear;
SubStream.CopyFrom(msRecInfo, L); // copy next block of data to a second TMemoryStream
Result := StreamToString(SubStream); //convert the stream into a string
end else
Result := '';
end;
begin
...
msRecInfo.Position := 0;
S := ReadString;
S := ReadString;
...
Although, if feasible, I would suggest just getting rid of SubStream altogether, update StreamToString() to take L as an input parameter, so that you can read the string from msRecInfo directly, eg:
msRecInfo.Read(L, SizeOf(L));
S := StreamToString(msRecInfo, L);
No need for a 2nd TMemoryStream if you can avoid it.

Transforming Go's PutUint16 to Python

I want to get the equivalent of the Go code given below in Python:
func Make(op Opcode, operands ...int) []byte {
def, ok := definitions[op]
if !ok {
return []byte{}
}
instructionLen := 1
for _, w := range def.OperandWidths {
instructionLen += w
}
instruction := make([]byte, instructionLen)
instruction[0] = byte(op)
offset := 1
for i, o := range operands {
width := def.OperandWidths[i]
switch width {
case 2:
binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
case 1:
instruction[offset] = byte(o)
}
offset += width
}
return instruction
}
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
operands := make([]int, len(def.OperandWidths))
offset := 0
for i, width := range def.OperandWidths {
switch width {
case 2:
operands[i] = int(ReadUint16(ins[offset:]))
case 1:
operands[i] = int(ReadUint8(ins[offset:]))
}
offset += width
}
return operands, offset
}
op above is any of:
type Opcode byte
const (
OpConstant Opcode = iota
OpAdd
OpPop
OpSub
OpMul
OpDiv
)
The code above comes from the book Writing a Compiler in Go and can be found here
I am not exactly sure about what is going on here with byte transformations and packing but in order to understand it better I am writing the whole thing in Python. Can someone help me translate those two functions in Python?
You can use the to_bytes method of integers. o.to_bytes(2, byteorder='big') will give the same effect as PutUint16. Likewise int.from_bytes can be used for reading. There is also struct.pack which handles similar things in a format-string kind of way.
Instead of building the buffer and writing into offsets, as done in the Go code, it makes more sense simply to use + to append to a bytes which begins empty.

Point in polygon hit test algorithm

I need to test if a point hits a polygon with holes and isles. I'd like to understand how I'm supposed to do this. That's not documented and I can't find any explanation or examples.
What I do is count +1 for every outer polygon hit and -1 for every inner polygon hit. The resulting sum is:
> 0: hit;
<= 0: miss (outside or in a hole).
The HitData class separates paths based on winding number to avoid unnecessary recomputation of orientation. With Clipper.PointInPolygon() applied to every path the sum is easy to compute.
But there are two major drawbacks:
I have to apply Clipper.PointInPolygon() to EVERY path;
I can't leverage the hierarchy of PolyTree.
Can someone who has hands-on experience with Clipper (#angus-johnson?) clear up this confusion?
Again, my question is: how am I supposed to implement this? Am I re-inventing the wheel, while there's an actual solution readily available in the Clipper Library?
Side note: PolyTree still requires to test EVERY path to determine which PolyNode the point is in. There's no Clipper.PointInPolyTree() method and, thus, AFAIK PolyTree doesn't help.
The structure that separates outer and inner polygons:
public class HitData
{
public List<List<IntPoint>> Outer, Inner;
public HitData(List<List<IntPoint>> paths)
{
Outer = new List<List<IntPoint>>();
Inner = new List<List<IntPoint>>();
foreach (List<IntPoint> path in paths)
{
if (Clipper.Orientation(path))
{
Outer.Add(path);
} else {
Inner.Add(path);
}
}
}
}
And this is the algorithm that tests a point:
public static bool IsHit(HitData data, IntPoint point)
{
int hits;
hits = 0;
foreach (List<IntPoint> path in data.Outer)
{
if (Clipper.PointInPolygon(point, path) != 0)
{
hits++;
}
}
foreach (List<IntPoint> path in data.Inner)
{
if (Clipper.PointInPolygon(point, path) != 0)
{
hits--;
}
}
return hits > 0;
}
Can someone who has hands-on experience with Clipper (#angus-johnson?) clear up this confusion?
It's not clear to me what your confusion is. As you've correctly observed, the Clipper library does not provide a function to determine whether a point is inside multiple paths.
Edit (13 Sept 2019):
OK, I've now created a PointInPaths function (in Delphi Pascal) that determines whether a point is inside multiple paths. Note that this function accommodates the different polygon filling rules.
function CrossProduct(const pt1, pt2, pt3: TPointD): double;
var
x1,x2,y1,y2: double;
begin
x1 := pt2.X - pt1.X;
y1 := pt2.Y - pt1.Y;
x2 := pt3.X - pt2.X;
y2 := pt3.Y - pt2.Y;
result := (x1 * y2 - y1 * x2);
end;
function PointInPathsWindingCount(const pt: TPointD;
const paths: TArrayOfArrayOfPointD): integer;
var
i,j, len: integer;
p: TArrayOfPointD;
prevPt: TPointD;
isAbove: Boolean;
crossProd: double;
begin
//nb: returns MaxInt ((2^32)-1) when pt is on a line
Result := 0;
for i := 0 to High(paths) do
begin
j := 0;
p := paths[i];
len := Length(p);
if len < 3 then Continue;
prevPt := p[len-1];
while (j < len) and (p[j].Y = prevPt.Y) do inc(j);
if j = len then continue;
isAbove := (prevPt.Y < pt.Y);
while (j < len) do
begin
if isAbove then
begin
while (j < len) and (p[j].Y < pt.Y) do inc(j);
if j = len then break
else if j > 0 then prevPt := p[j -1];
crossProd := CrossProduct(prevPt, p[j], pt);
if crossProd = 0 then
begin
result := MaxInt;
Exit;
end
else if crossProd < 0 then dec(Result);
end else
begin
while (j < len) and (p[j].Y > pt.Y) do inc(j);
if j = len then break
else if j > 0 then prevPt := p[j -1];
crossProd := CrossProduct(prevPt, p[j], pt);
if crossProd = 0 then
begin
result := MaxInt;
Exit;
end
else if crossProd > 0 then inc(Result);
end;
inc(j);
isAbove := not isAbove;
end;
end;
end;
function PointInPaths(const pt: TPointD;
const paths: TArrayOfArrayOfPointD; fillRule: TFillRule): Boolean;
var
wc: integer;
begin
wc := PointInPathsWindingCount(pt, paths);
case fillRule of
frEvenOdd: result := Odd(wc);
frNonZero: result := (wc <> 0);
end;
end;
With regards leveraging the PolyTree structure:
The top nodes in PolyTree are outer nodes that together contain every (nested) polygon. So you'll only need to perform PointInPolygon on these top nodes until a positive result is found. Then repeat PointInPolygon on that nodes nested paths (if any) looking for a positive match there. Obviously when an outer node fails PointInPolygon test, then its nested nodes (polygons) will also fail. Outer nodes will increment the winding count and inner holes will decrement the winding count.

Strange behaviour when simply adding strings in Lazarus - FreePascal

The program has several "encryption" algorithms. This one should blockwise reverse the input. "He|ll|o " becomes "o |ll|He" (block length of 2).
I add two strings, in this case appending the result string to the current "block" string and making that the result. When I add the result first and then the block it works fine and gives me back the original string. But when i try to reverse the order it just gives me the the last "block".
Several other functions that are used for "rotation" are above.
//amount of blocks
function amBl(i1:integer;i2:integer):integer;
begin
if (i1 mod i2) <> 0 then result := (i1 div i2) else result := (i1 div i2) - 1;
end;
//calculation of block length
function calcBl(keyStr:string):integer;
var i:integer;
begin
result := 0;
for i := 1 to Length(keyStr) do
begin
result := (result + ord(keyStr[i])) mod 5;
result := result + 2;
end;
end;
//desperate try to add strings
function append(s1,s2:string):string;
begin
insert(s2,s1,Length(s1)+1);
result := s1;
end;
function rotation(inStr,keyStr:string):string;
var //array of chars -> string
block,temp:string;
//position in block variable
posB:integer;
//block length and block count variable
bl, bc:integer;
//null character as placeholder
n : ansiChar;
begin
//calculating block length 2..6
bl := calcBl(keyStr);
setLength(block,bl);
result := '';
temp := '';
{n := #00;}
for bc := 0 to amBl(Length(inStr),bl) do
begin
//filling block with chars starting from back of virtual block (in inStr)
for posB := 1 to bl do
begin
block[posB] := inStr[bc * bl + posB];
{if inStr[bc * bl + posB] = ' ' then block[posB] := n;}
end;
//adding the block in front of the existing result string
temp := result;
result := block + temp;
//result := append(block,temp);
//result := concat(block,temp);
end;
end;
(full code http://pastebin.com/6Uarerhk)
After all the loops "result" has the right value, but in the last step (between "result := block + temp" and the "end;" of the function) "block" replaces the content of "result" with itself completely, it doesn't add result at the end anymore.
And as you can see I even used a temp variable to try to work around that.. doesnt change anything though.
I am 99.99% certain that your problem is due to a subtle bug in your code. However, your deliberate efforts to hide the relevant code mean that we're really shooting in the dark. You haven't even been clear about where you're seeing the shortened Result: GUI Control/Debugger/Writeln
The irony is that you have all the information at your fingertips to provide a small concise demonstration of your problem - including sample input and expected output.
So without the relevant information, I can only guess; I do think I have a good hunch though.
Try the following code and see if you have a similar experience with S3:
S1 := 'a'#0;
S2 := 'bc';
S3 := S1 + S2;
The reason for my hunch is that #0 is a valid character in a string: but whenever that string needs to be processed as PChar, #0 will be interpreted as a string terminator. This could very well cause the "strange behaviour" you're seeing.
So it's quite probable that you have at least one of the following 2 bugs in your code:
You are always processing 1 too many characters; with the extra character being #0.
When your input string has an odd number of characters: your algorithm (which relies on pairs of characters) adds an extra character with value #0.
Edit
With the additional source code, my hunch is confirmed:
Suppose you have a 5 character string, and key that produces block length 2.
Your inner loop (for posB := 1 to bl do) will read beyond the length of inStr on the last iteration of the outer loop.
So if the next character in memory happens to be #0, you will be doing exactly as described above.
Additional problem. You have the following code:
//calculating block length 2..6
bl := calcBl(keyStr);
Your assumption in the comment is wrong. From the implementation of calcBl, if keyStr is empty, your result will be 0.

Integer to String goes wrong in Synthesis (Width Mismatch)

I am trying to convert a integer to string (using integer'image(val)) and either pad or limit it to a specific length. I have made this function which does the job just fine when I use a report statement and simulate.
function integer2string_pad(val: integer; stringSize: integer) return string is
variable imageString: string(1 to integer'image(val)'length);
variable returnString: string(1 to stringSize);
begin
imageString := integer'image(val);
-- Are we smaller than the desired size?
if integer'image(val)'length < stringSize then
-- Pad the string if we are
returnString := integer'image(val) & (1 to stringSize-integer'image(val)'length => ' ');
-- Are we to big for the desired size
elsif integer'image(val)'length > stringSize then
-- Only use the top most string bits and append a "." to the end signifing that there is more
returnString := imageString(1 to stringSize-1) & ".";
-- Otherwise we are just the right size
else
returnString := integer'image(val);
end if;
return returnString;
end function;
Here is some sample input, output of that function (underscore = space because SO inline code truncates extra space):
integer2string_pad(12, 6) : 12____
integer2string_pad(123456, 6) : 123456
integer2string_pad(1234567890, 6) : 12345.
integer2string_pad(0, 6) : 0_____
integer2string_pad(-123, 6) : -123__
integer2string_pad(-1, 6) : -1____
integer2string_pad(-123456, 6) : -1234.
But when I synthesize, I get width mismatch errors on all 4 lines where I assign values to pongScoreLeft or pongScoreRight. It also says they have a constant value of 0 and get trimmed out.
Width mismatch. <pongScoreLeft> has a width of 48 bits but assigned
expression is 6-bit wide.
Width mismatch. <pongScoreRight> has a width
of 48 bits but assigned expression is 6-bit wide.
Width mismatch. <pongScoreLeft> has a width of 48 bits but assigned expression is 6-bit wide.
Width mismatch. <pongScoreRight> has a width of 48 bits but assigned expression is 6-bit wide.
VHDL that produces those width mismatch errors:
type type_score is
record
left : integer range 0 to 255;
right : integer range 0 to 255;
end record;
constant init_type_score: type_score := (left => 0, right => 0);
signal pongScore: type_score := init_type_score;
signal pongScoreLeft: string(1 to 6) := (others => NUL);
signal pongScoreRight: string(1 to 6) := (others => NUL);
...
scoreToString: process(clk)
begin
if rising_edge(clk) then
if reset = '1' then
pongScoreLeft <= (others => NUL);
pongScoreRight <= (others => NUL);
else
pongScoreLeft <= integer2string_pad(pongScore.left, 6);
pongScoreRight <= integer2string_pad(pongScore.right, 6);
--report "|" & integer2string_pad(pongScore.left, 6) & "|";
end if;
end if;
end process;
What is wrong with my integer2string_pad function? What goes wrong in synthesis?
I would not expect 'image or 'value to be supported for synthesis - other than in asserts that run at elaboration time. They would involve a lot of processing.
Whenever I have converted integers to ASCII I have processed a character at a time, using character'val and character'pos, which are synthesisable, because they involve no processing; they just convert a character to/from its underlying binary representation.
EDIT:
Think how you would implement 'image! It involves multiple divisions by 10 : that's a LOT of hardware if you unroll it into a single delta cycle (as required by the semantics of an unclocked function call)
Processing a digit per (several) clock cycle(s) you can reduce that to a single division, or successive subtraction, or excess-6 addition, or however you want according to your hardware resources and time budget.
It really doesn't make sense for the synthesis tool to make these decisions on your behalf. So - while I concede it's theoretically possible, I would be surprised to see a synth tool that did it correctly. (OTOH it's such an unlikely scenario I'd not be surprised to see bugs in synth tool's error reporting should you try it)

Resources