-
Notifications
You must be signed in to change notification settings - Fork 0
MathForward
Being a forward means that it can receive data from one or more sources do something with it and provide the resulting data as a source for multiple targets.
This particular forward:
- Must receive delimited data
- Will apply mathematical operations on the data after splitting it
- Can send commands
Split data received according to a chosen delimiter and alter the numerical elements using mathematical operations.
- Supports addition(+), subtraction(-), multiplication(*), division(/), remainder(%) and power(^).
- Can apply rounding
- The rule of 'order of operations' is followed with the exception that % is below * and / instead of equal.
- All operations are executed for accuracy first and performance second (java double versus bigdecimal)
- Numbers can be in either decimal, hexadecimal or scientific notation.
- The delimiter can be any ascii string, in the future hex will be implemented.
- If there are no targets then it won't receive data from its sources (eg. no overhead if not used)
- Use the result of an operation as part of a command (see cmd)
The GGA string received from a GPS contains coordinates in degrees minutes while degrees are desired.
Consider the following data received from a GPS:
$GPGGA,095835.147,5104.24631,N,00227.67672,E,1,,,-3.6,M,,,,*0E
Splitting this on , and numbering them from i1:
i0 - $GPGGA
i1 - 095835.147 = time of gps fix
i2 - 5104.24631 = latitude 51° 4.24631'
i3 - N
i4 - 00227.67672 = longitude 2° 27.67672'
i5 - E
etc
Latitude and longitude are formatted in degrees minutes, decimal degrees are easier to use so we'll convert them. To do this we need to extract the degrees part and add it to the minutes part divided by 60.
Which is done with (i2-i2%100) to get the degrees part and (i2%100)/60 to get the minutes and convert it to degrees. Putting those two together means that (i2-i2%100) + (i2%100)/60 will be the new i2.
Or i2 = (i2-i2%100) + (i2%100)/60 or i2=(5104.24631-5104.24631%100) + (5104.24631%100)/60 = 51.7077183
This will be used to explain MathForward.
The Easiest way is using a command through the telnet interface.
mf:addblank,id,source
Create a blank math in the xml with the given id and optional source
Applied to the problem, this becomes 'mf:addblank,degrees,raw:gps' will add the following node to the settings.xml.
Degrees is the id we use to refer to the operations and data is the raw data from the gps.
Note: raw:gps is explained in the Streampool section.
<maths>
<!-- Some info on what the test math does -->
<math delimiter="," id="degrees" src="raw:gps">
<!-- Operations go here, possible types: complex, scale -->
</math>
</maths>
Next up is adding the operations to it, this can be done with another telnet command:
mf:addop,id,operation
Add the given operation to the math with the provided id
Or by adding a default operation with mf:addop,id
and then editing this in the xml file and then use mf:reload,id
to reload/apply it.
So executing:
mf:addop,degrees,i2 = (i2-i2%100) + (i2%100)/60
to add the latitude
mf:addop,degrees
to add a default one for the longitude
Below is the result of both addop commands
<!-- Some info on what the degrees math does -->
<math delimiter="," id="degrees" src="raw:id:gps">
<!-- Operations go here, possible types: complex, scale -->
<op>i2=(i2-i2%100) + (i2%100)/60</op> <!-- latitude -->
<op>.</op> <!-- ready for longitude -->
</math>
In the end, if longitude is filled in, the result would be: $GPGGA,095835.147,51.7077183,N,2.46127866,E,1,,,-3.6,M,,,,*0E
mf:addblank,id,source
Create a blank math in the xml with the given id and optional source
mf:addop,id,index=equation
Add an operation to the given math that writes the result to the given index
mf:addsource,id,source
Add the given source to the id
mf:reload,id
Reload the given id
mf:list
List all the available maths and their operations
mf:debug,on/off
Turn debugging on or off this will show how the op's are executed
mf:test,id,data
Give data to test it
Defaults
If an attribute is considered default, it can be omitted from the node.
- The default delimiter is ','
- The default type attribute of operation is 'complex'
- The default label is empty
Short notation If the math only contains a single op, it can be written like this:
<math id="degrees">i2=(i2-i2%100) + (i2%100)/60</math>
Using realtime data If the realtimevalues contain a double named offset, this can be added to the expression like this:
<math id="degrees">i2=(i2-i2%100) + (i2%100)/60 + {d:offset}</math>
Or if you want to save the result of the op in such double, this can be done like this:
<math id="degrees">{d:latitude},i2=(i2-i2%100) + (i2%100)/60</math>
Or
<math id="degrees">i2,{r:latitude}=(i2-i2%100) + (i2%100)/60</math>
If you don't care about i2 in the following steps, it can be omitted
<math id="degrees">{r:latitude}=(i2-i2%100) + (i2%100)/60</math>
Flags can also be referenced, using {f:flagid} but NOT on the left side of the =. On the right these will be replaced with either 1 or 0.
Multiple sources
Multiple sources is possible and this is how this looks in the node, but no use case for this yet...
<math id="degrees">
<src>raw:id:gps</src>
<src>raw:id:rtk</src>
<op>i2=(i2-i2%100) + (i2%100)/60</op> <!-- latitude -->
</math>
Label
Another main attribute is label. This does the same as the label of a stream (see Streampool) and can be used to give the altered data to a generic (see Generics).
<math id="degrees" src="raw:gps" label="generic:coords">
<op>i2=(i2-i2%100) + (i2%100)/60</op> <!-- latitude -->
</math>
Scale This attribute allows to have the result of an operation scaled to a certain amount of digits.
<op scale="3">i2=(i2-i2%100) + (i2%100)/60</op><!-- latitude 51.7077183 becomes 51.708-->
This feature allows the result of an op to be used in a command. So if we wanted to store the earlier calculated latitude
as an rtval, this could be done by adding the cmd attribute and the relevant command as value.
In such command, the $ will be replaced with the calculated value.
<!-- Some info on what the test math does -->
<math delimiter=";" id="test" src="raw:id:sensor">
<op cmd="doubles:new,latitude,$" >i2=(i2-i2%100) + (i2%100)/60</op> <!-- latitude -->
</math>
Note: See 'RealtimeValues' for explanation on the doubles command
When using this to calibrate scientific values from a sensor outputting raw data, there are always calibration coefficients
involved. Given that these tend to change overtime, it's possible to have mentioned separately instead of in the calculation.
Just make sure you don't use 'i' followed by a number.
<math id="convert">
<!-- Calibration values of the pt100 -->
<def ref="pt1_x0">-196,2332201</def>
<def ref="pt1_x1">9,7149867E-06</def>
<def ref="pt1_x2">2,4479084E-13</def>
<!-- Second order calculation -->
<op scale="3">i0=pt1_x2*i0^2 + pt1_x1*i0 + pt1_x0</op> <!-- Calculate second order, than scale to 3 fractional digits -->
</math>
This allows for certain (calculated) data to be appended to the final output.
For now the only one is nmea, which will add the NMEA checksum (including the *) at the end.
- Spaces are allowed but not required
- Every operation will be executed in order, if the first op writes to i1 the next op will use the updated value
- Both . or , are valid decimal indicators
- Brackets are not required if the order is followed, but might improve readability
- If parts are repeated, they are only calculated once in a single operation
fe. i1=(i1+1)+(i1+1) will result in i1+1 only executed once, see 'how it's implemented' for explanation
Consider the data: 12,16,2,4,6
and the op: i1=((i1+8)*2+(i1+8))*(i2+i4)
Splitting the operation in subs follows these rules:
- brackets from left to right (starting with first closed bracket)
- order of operations
- one operand at a time
Based on this, the first encountered sub is i1+8, this will be stored as a function and the result is written to o1. This simplifies the expression to (o1*2+o1)*(i2+i4).
Using this logic for the whole operation
Sub | Function | New expression |
---|---|---|
i1+8 | o1=i1+8 | i1=(o1*2+o1)*(i2+i4) |
o1*2 | o2=o1*2 | i1=(o2+o1)*(i2+i4) |
o2+o1 | o3=o2+o1 | i1=o3*(i2+i4) |
i2+i4 | o4=i2+i4 | i1=o3*o4 |
o3*o4 | o5=o3*o4 | i1=o5 |
So the computations are:
o1 = 12 + 8 = 20
o2 = 20 * 2 = 40
o3 = 20 + 40 = 60
o4 = 16 + 4 = 20
o5 = 60 * 20 = 1200
So the operation required five computations to change the data to 1200;16;2;4;6
- Getting to know dcafs
- Filterforward
- MathForward
- EditForward (todo)
- Combining
- TaskManager
- LabelWorker
- CommandPool
- DebugWorker
- Realtimevalues
- IssuePool