Rule based scripts: Difference between revisions
(22 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
===Introduction=== | ===Introduction=== | ||
This plug-in implements | This plug-in implements a language that is a mixture of a rule-based and a sequential based language. The idea is that a number of situations need permanent - or semi permanent - monitoring to get a good situation awareness, and at the same time accept that a large number of the robot tasks are better described by a sequential language. | ||
This implementation attempts to cover this gab. | This implementation attempts to cover this gab. | ||
Line 83: | Line 83: | ||
<rule name="foo"> | <rule name="foo"> | ||
<parameters x="0.28" y="0" th="pi"/> | <parameters x="0.28" y="0" th="pi" b="'empty'"/> | ||
statements | statements | ||
</rule> | </rule> | ||
<rule name="bar"> | <rule name="bar"> | ||
foo() | foo() | ||
foo(1.5, 0.0, pi/2) | foo(1.5, 0.0, pi/2, "pose") | ||
</rule> | </rule> | ||
Here the rule "bar" uses the "foo" rule as function, first by using the default values, and after this with new values replacing the default values in "foo". | Here the rule "bar" uses the "foo" rule as function, first by using the default values, and after this with new values replacing the default values in "foo". | ||
The parameters in "foo" works like local variables in the "foo" plan. | The parameters in "foo" works like local variables in the "foo" plan. The last parameter is a string, that must be initialized to a string constant, here shown as the string ''empty'', this value is in the last call replaced with the string ''pose''. | ||
A rule may be called recursively, and each call will have its own set of local variables. | A rule may be called recursively, and each call will have its own set of local variables. | ||
There is a limit to the number nested calls. | There is a limit to the number nested calls. | ||
Line 113: | Line 113: | ||
Here x and x2 are newly established local variables | Here x and x2 are newly established local variables | ||
The rule "bar" is active as long as the plan "foo" is active. | The rule "bar" is active as long as the plan "foo" is active. | ||
String variables are partially implemented, and may be used in some situations, like: | |||
<init> | |||
b = 5 | |||
aa = 'the value of b is ' b 'meters' | |||
</init> | |||
This should result in the string "the value of b is 5meters" to be assigned to the aa variable. | |||
A variable may have more than one value, i.e.: | |||
<init> | |||
b[2] = 5 # allocates space for 3 elements | |||
b[1] = 22.7 # assigns value to second element | |||
b[0] = 0.5 # assigns value to first element | |||
a = b # copies all elements in b to a | |||
c = atan2(a[2] - 3.3, a[1] - 1) | |||
</init> | |||
Some calls may support parameters that take such arrays, i.e. coordinate conversion | |||
All non-string variables are implemented as doubles. | |||
====Init and Post blocks==== | ====Init and Post blocks==== | ||
Line 142: | Line 165: | ||
<rule name="foo" if="utmpose.posey < 1000"> | <rule name="foo" if="utmpose.posey < 1000"> | ||
<init> | <init> | ||
global.equatorWarning = false | global.equatorWarning = false | ||
</init> | </init> | ||
global.equatorWarning = true | global.equatorWarning = true | ||
</rule> | </rule> | ||
This rule is triggered by the Northing in the utmPose module, "utmpose.posey" is here a global variable. | This rule is triggered by the Northing in the utmPose module, "utmpose.posey" is here a global variable. | ||
The rule defined a new global variable "global.equatorWarning", this must start with the keyword "global", and is then available for all other rules (in all scopes). | The rule defined a new global variable "global.equatorWarning", this must start with the keyword "global", and is then available for all other rules (in all scopes). | ||
====Strings==== | ====Strings==== | ||
Line 156: | Line 180: | ||
print("The value of a is " a "meter") | print("The value of a is " a "meter") | ||
is evaluated to (if a has the value 1.47 | is evaluated to "''The value of a is 1.47meter''" (if a has the value 1.47) | ||
A string parameter must start with a quote (") or apostrophe ('), if the first part of the string should be a number, then start with an empty string. | A string parameter must start with a quote (") or apostrophe ('), if the first part of the string should be a number, then start with an empty string. | ||
Line 171: | Line 193: | ||
Numbers will be inserted as a string using the %g 'printf(...)' format specifier | Numbers will be inserted as a string using the %g 'printf(...)' format specifier | ||
====Variable name in string==== | |||
Some plugins generate global variables with value-dependent names, e.g. guidemark details, where a ''gmk.gmk30'' structure when a guidemark with ID=30 is found. To access this structure, you must construct a string with the variable name and use a ''getvar(s)'' or ''setvar(sc)'' call, like: | |||
<rule name="foo" if="gmk.imageSerial[1] != 0"> | |||
<init> | |||
gmkId = 0 | |||
gmkTime = 0 | |||
gp = 0 | |||
z = 0 | |||
</init> | |||
// get ID of first found guidemark | |||
gmkId = gmk.IDs[0] | |||
// construct base name in string variable | |||
varname = "gmk.gmk" gmkId | |||
// get value with ad-hoc extension (to get gmk.gmk30.time) | |||
gmktime = getvar(varname ".time") | |||
// or do it all in the parameter (here to get all 6 values in the guidemark pose) | |||
gp = getvar("gmk.gmk" gmkId ".gmkPose") | |||
// if you need the z-position of the guidemark only | |||
z = getvar("gmk.gmk" gmkId ".gmkPose[2]") | |||
// change the value of a named variable | |||
setvar("gmk.gmk" gmkId ".crcOK", 0) | |||
print("Found guidemark ID " gmkid " in image " gmk.imageSerial[1] " at " gmkTime) | |||
gmk.imageSerial[1] = 0 | |||
</rule> | |||
====Enable/Disable==== | ====Enable/Disable==== | ||
Line 232: | Line 283: | ||
</init> | </init> | ||
... | ... | ||
if (not defined(global.emergency)) | if (not defined("global.emergency")) | ||
break | break | ||
control statement, e.g. | control statement, e.g. | ||
Line 269: | Line 320: | ||
</block> | </block> | ||
</rule> | </rule> | ||
====Case statement==== | |||
A case statement has the following syntax | |||
<rule name="foo" if="true"> | |||
<init> | |||
a = 3 | |||
</init> | |||
switch (a + global.a) | |||
<block> | |||
case 3 | |||
print("Global.a has a low value") | |||
case 4,5,-6, -7 ,8 # a remark | |||
print("a range of values, a is " a " and global A is " global.a) | |||
global.a = 9 | |||
print("a set to 9") | |||
case 9 # no statement is executed if switch value is 9 | |||
default | |||
print("default statement if outside range") | |||
</block> | |||
</rule> | |||
Case statements may be nested. | |||
====Function calls==== | ====Function calls==== | ||
Line 303: | Line 378: | ||
min(dd) | min(dd) | ||
defined(s) | defined(s) | ||
now() | |||
poseToMap(dddddd) | |||
mapToPose(dddddd) | |||
if() | |||
The function parameters are of type "d" for double or "dd" for two doubles (comma separated). The defined function uses a string as parameter, e.g. defined("false") returns true. | The function parameters are of type "d" for double or "dd" for two doubles (comma separated). The defined function uses a string as parameter, e.g. defined("false") returns true. | ||
Angle values are in radians. | Angle values are in radians. | ||
The if() expression evaluetes the expression in the rule if attribute, i.e: | |||
<rule name="foo" if="alfa > 100 and beta > 100 and gamma > 100"> | |||
print("all three is above limit") | |||
wait() : not if() | |||
print("At least one is no longer above limit") | |||
</rule> | |||
See on-line help | |||
>> var math | |||
for any other implemented standard functions (and function description) | |||
====Control statement==== | ====Control statement==== | ||
Line 330: | Line 421: | ||
will evaluate to true when the user event is executed by the MRC. | will evaluate to true when the user event is executed by the MRC. | ||
NB! the smr.event(1) itself will return true if the function is defined (i.e. plugin loaded) and false if it is not. | NB! the smr.event(1) itself will return 1 (true) if the function is defined (i.e. plugin loaded) and 0 (false) if it is not. | ||
===Language definition=== | ===Language definition=== | ||
Line 356: | Line 447: | ||
parameters ::= '<parameters ' [symbol '="' [paramDefaultValue] '"']* '/>\n' | parameters ::= '<parameters ' [symbol '="' [paramDefaultValue] '"']* '/>\n' | ||
paramDefaultValue ::= | paramDefaultValue ::= constantNumericValue | constantStringValue | ||
description ::= '<description>\n' xmlText '</description>\n' | description ::= '<description>\n' xmlText '</description>\n' | ||
Line 369: | Line 460: | ||
rulePostBlock ::= '<post>\n' [statement]* '</post>\n' | rulePostBlock ::= '<post>\n' [statement]* '</post>\n' | ||
constantStringValue ::= ' [xmlText] ' | " [xmlText " | |||
====Statements==== | ====Statements==== | ||
Line 375: | Line 468: | ||
executableStatement ::= assignment | procedureCall | blockStatement | | executableStatement ::= assignment | procedureCall | blockStatement | | ||
breakStatement | enableStatement | ifStatement | loopStatement | emptyStatement | breakStatement | enableStatement | ifStatement | loopStatement | caseStatement | emptyStatement | ||
controlStatement ::= proceureCall ':' expression [remark] '\n' | controlStatement ::= proceureCall ':' expression [remark] '\n' | ||
Line 398: | Line 491: | ||
whileStatement ::= 'while (' expression ')' [remark] '\n' statement [remark] '\n' | whileStatement ::= 'while (' expression ')' [remark] '\n' statement [remark] '\n' | ||
case statement ::= 'switch (' expression ')' [remark] '\n' blockCaseStatement | |||
blockCaseStatement ::= '<block>\n' [caseStatement]* [caseDefaultStatement] '</block>\n' | |||
caseStatement ::= 'case ' int [, int]* [remark] '\n' [statement]* | |||
caseDefaultStatement ::= 'default' [remark] \n [statement]* | |||
emptyStatement ::= [remark] '\n' | emptyStatement ::= [remark] '\n' | ||
remark ::= ('#' | ';' | '//') xmlText except '\n' | remark ::= ('#' | ';' | '//') xmlText except '\n' |
Latest revision as of 12:57, 6 October 2015
Introduction
This plug-in implements a language that is a mixture of a rule-based and a sequential based language. The idea is that a number of situations need permanent - or semi permanent - monitoring to get a good situation awareness, and at the same time accept that a large number of the robot tasks are better described by a sequential language.
This implementation attempts to cover this gab.
Language
An example rule definition file could look like this:
<?xml version="1.0" ?> <rule name="CrossRoad"> <init> odoPose.tripB = 0 nearRoad = false // define local variable <rule name="maxOdoDist" if="odoPose.tripB > 250"> // this is a rule to moditor distance traveled since trip-counter were reset print("Driven too far on odo " odoPose.tripB "m") // print message break CrossRoad // failed to cross road - could trigger a relocalization </rule> <rule name="closeToRoad" if="hypot(utmPose.poseY - 6174307, utmPose.poseX - 707873) < 15" > // this is a rule that monitors the distance to an UTM point print("Cose to road slowing down) // print message smr.speed=0.5 // set the desired maximum speed in mrc interface module nearRoad = true // set flag disable // disable this rule </rule> <rule name="turn"> <parameter angle="pi" dist=1.0/> // this is a plan <commands to="smr.send"> # construct commands to MRC using the smr.send command 'drive @v ' smr.speed ' : ($drivendist > ' dist ')' 'turn ' angle 'drive : ($drivendist > ' dist ')' </commands> </rule> </init> print("started") roaddrive.right(0.75) : nearRoad // follow road 75cm from edge until near road // more stuff missing here to detect traffic etc. ... turn() // turn back - using a call to a plan success=true <post> print("finsihed crossRoad - success=" success) </post> </rule>
A number of these rules can be active simultaneously, and can be total independent. I.e.one can control the robot arm while another controls the navigation.
Language keywords
Rule
A rule statement must be formed as a XML block structure, as shown below
<rule name="foo" run="true"> statements </rule> <rule name="bar" if="rule_condition"> statements </rule>
Rules comes in two flavors, rules and inactive rules. A (active) rule must have a rule condition in an if="" attribute like the plan "bar" above. A rule without the if="" attribute is inactive until called, or made active by a "enable" statement. If it is a top-level rule, then it can be made active when loaded by a run="true" attribute. The run="true" attribute will make it run once
The rule may have a description, like
<rule name="foo" run="true"> <description> This is the descriptive text of "foo", it may have any length, and is intended as an on-line available description of the rule. It is optional and recommended only for top-level rules. </description> statements </rule>
Rule as a function
A rule may have optional parameters, that can be used if the rule is called from another rules (as a procedure or function, that do not return a value), i.e.:
<rule name="foo"> <parameters x="0.28" y="0" th="pi" b="'empty'"/> statements </rule> <rule name="bar"> foo() foo(1.5, 0.0, pi/2, "pose") </rule>
Here the rule "bar" uses the "foo" rule as function, first by using the default values, and after this with new values replacing the default values in "foo". The parameters in "foo" works like local variables in the "foo" plan. The last parameter is a string, that must be initialized to a string constant, here shown as the string empty, this value is in the last call replaced with the string pose. A rule may be called recursively, and each call will have its own set of local variables. There is a limit to the number nested calls.
Local variables
A rule may define local variables and sub-rules, like
<rule name="foo"> <init> x = 88; x2 = sqr(x) <rule name="bar" if="x < 22") print("x is now " x) </rule> </init> statements </rule>
Here x and x2 are newly established local variables The rule "bar" is active as long as the plan "foo" is active.
String variables are partially implemented, and may be used in some situations, like:
<init> b = 5 aa = 'the value of b is ' b 'meters' </init>
This should result in the string "the value of b is 5meters" to be assigned to the aa variable.
A variable may have more than one value, i.e.:
<init> b[2] = 5 # allocates space for 3 elements b[1] = 22.7 # assigns value to second element b[0] = 0.5 # assigns value to first element a = b # copies all elements in b to a c = atan2(a[2] - 3.3, a[1] - 1) </init>
Some calls may support parameters that take such arrays, i.e. coordinate conversion
All non-string variables are implemented as doubles.
Init and Post blocks
A rule may have an init block, to define local varaibles and sub-rules and to do some initial processing. The init block is executed once only, when the rule is first activated (the rule condition is fulfilled or the rule is a top level rule activated by a run=true attribute).
If the rule is called from another rule, then the init-block is run at the start of the call, every time such a call is performed.
<rule name="foo"> <init> global.fooCnt = 0 global.fooActive = 0 </init> global.fooActive = true main statements <post> global.fooCnt = global.fooCnt + 1 global.fooActive = false </post> </rule>
A rule may further have a Post block. The post block is executed as the last set of statements even if the rule is disabled by another rule or the rule has a break in its main statements.
Global variables
A rule may create and use global variables, e.g.
<rule name="foo" if="utmpose.posey < 1000"> <init> global.equatorWarning = false </init> global.equatorWarning = true </rule>
This rule is triggered by the Northing in the utmPose module, "utmpose.posey" is here a global variable. The rule defined a new global variable "global.equatorWarning", this must start with the keyword "global", and is then available for all other rules (in all scopes).
Strings
Strings may be used as parameters and can be concatenated by substrings or values, e.g.
print("The value of a is " a "meter")
is evaluated to "The value of a is 1.47meter" (if a has the value 1.47)
A string parameter must start with a quote (") or apostrophe ('), if the first part of the string should be a number, then start with an empty string.
A string may hold another string, but the quotes (") or apostrophes (') must come in matched pairs, e.g.
print('an eval "$odox" may return ' 0.0 ", but an "a" may be a value")
will evaluate as
print('an eval "$odox" may return 0, but an 1.47 may be a value')
Numbers will be inserted as a string using the %g 'printf(...)' format specifier
Variable name in string
Some plugins generate global variables with value-dependent names, e.g. guidemark details, where a gmk.gmk30 structure when a guidemark with ID=30 is found. To access this structure, you must construct a string with the variable name and use a getvar(s) or setvar(sc) call, like:
<rule name="foo" if="gmk.imageSerial[1] != 0"> <init> gmkId = 0 gmkTime = 0 gp = 0 z = 0 </init> // get ID of first found guidemark gmkId = gmk.IDs[0] // construct base name in string variable varname = "gmk.gmk" gmkId // get value with ad-hoc extension (to get gmk.gmk30.time) gmktime = getvar(varname ".time") // or do it all in the parameter (here to get all 6 values in the guidemark pose) gp = getvar("gmk.gmk" gmkId ".gmkPose") // if you need the z-position of the guidemark only z = getvar("gmk.gmk" gmkId ".gmkPose[2]") // change the value of a named variable setvar("gmk.gmk" gmkId ".crcOK", 0) print("Found guidemark ID " gmkid " in image " gmk.imageSerial[1] " at " gmkTime) gmk.imageSerial[1] = 0 </rule>
Enable/Disable
This will make a rule active - if not already, or stop or prohibit a plan from getting active, e.g.
enable foo
Will make the rule "foo" active, that is start running the code as if it was a rule. If there is a rule condition, then the code will not run until the condition is satisfied. if the rule has no rule condition, it will run once only.
If the rule is active already, either it is an active rule or the plan is waiting at a control statement, the statement will have no effect.
The disable command:
disable foo
Will disable the plan "foo", if the plan is a rule, then the rule condition will no longer be evaluated. If the rule is waiting at a control statement, then this will be interrupted, and any post-lines will be executed, before the rule is disabled. If the rule is not a rule and is not waiting at a control statement, then the statement will have no effect. A called rule will not be affected by the 'disable rulename' statement
A rule can disable itself with the statement:
disable
(a rule can not enable itself)
Loop statements
Loop statements resembles the C syntax, but is line oriented. A sample loop could look like this
<rule name="foo"> <init> cnt = 1 </init> while (cnt < 70) <block> cnt = cnt * 2 print("Loop cnt is now " cnt) if (cnt >= 16) break </block> for (i = 0; i < 5; i = i + 1) print("For loop, i=" i); </rule>
This defines the local variable "cnt" and later implicitly the variable "i". The first while loop doubles cnt until it reaches 16, where the break terminates the loop. The for loop defines a local variable "i", which after the first use is available as a local variable.
Break statement
Breaks the first loop (see loop statement), or breaks a rule or a named rule, as in
<rule name="foo"> <init> ... <rule name="exception" if="global.emergency"> break foo </rule> </init> ... if (not defined("global.emergency")) break control statement, e.g. wait() : false ... <post> stopRobot() </post> </rule>
Here the break in the rule exception has a break foo statement, that will break the foo rule (and run the post block). The break in the main statements of the foo rule has no rule name, and will thus break the current rule (if not inside a loop statement - if so the rule can be stopped by the named version of the break command.
If statement
Nested if and if-else statements are supported in up to 10 levels
The if condition is evaluated as either false or true. False is any value in the interval [-0.5 to 0.5], all other values are regarded as true. Boolean operators evaluate to 0 (false) or 1 (true).
<rule name="foo" if="true"> <init> a = false b = 1 c = 2 </init> if (c == 0) a = true else if (not a) <block> a = true if (b == 1) b = 77 else b = b + 1 c = 0 </block> </rule>
Case statement
A case statement has the following syntax
<rule name="foo" if="true"> <init> a = 3 </init> switch (a + global.a) <block> case 3 print("Global.a has a low value") case 4,5,-6, -7 ,8 # a remark print("a range of values, a is " a " and global A is " global.a) global.a = 9 print("a set to 9") case 9 # no statement is executed if switch value is 9 default print("default statement if outside range") </block> </rule>
Case statements may be nested.
Function calls
Three call types are supported:
- call to another rule - as described above
- call to a method in another plugin - e.g.:
smr.send('followline "bl" : ($crossingblackline)')
- call to a standard function, e.g:
a = atan2(4,3) d = sin(pi/4)
Standard functions
The following values and functions are implemented
false = 0.0 true = 1.0 pi = M_PI
limitToPi(d) sin(d) cos(d) hypot(dd) acos(d) asin(d) tan(d) atan(d) atan2(dd) sqrt(d) sqr(d) abs(d) max(dd) min(dd) defined(s) now() poseToMap(dddddd) mapToPose(dddddd) if()
The function parameters are of type "d" for double or "dd" for two doubles (comma separated). The defined function uses a string as parameter, e.g. defined("false") returns true. Angle values are in radians.
The if() expression evaluetes the expression in the rule if attribute, i.e:
<rule name="foo" if="alfa > 100 and beta > 100 and gamma > 100"> print("all three is above limit") wait() : not if() print("At least one is no longer above limit") </rule>
See on-line help
>> var math
for any other implemented standard functions (and function description)
Control statement
A control statement is constructed of a call to a plug-in method and a condition for continuation, like
smr.event() : abs(odoPose.vel) < 0.05
The rule will stay at this line until either the "smr.event()" returns 2, or the expression after the ":" evaluates to true.
The control statement call must accept one "d" parameter more than shown in the control statement, i.e. the smr.event(d) call must be defined with a "d" parameter. This extra parameter is called an iteration parameter. When the rule gets to this line the iteration parameter is zero, the subsequent times the rule is evaluated, the iteration parameter is incremented by one. This is done to allow the plug-in method to initialize the control, when the parameter is 0, and just test for completion at the subsequent calls. If the control statement is interrupted by a break or disable statement, then the control function is called with the iteration parameter set to -1.
If the control calls are used outside a control statement the iteration parameter must be specified explicitly, e.g.
smr.event(0)
will initialize a user event on the MRC command queue, and
if (smr.event(1) == 2)
will evaluate to true when the user event is executed by the MRC. NB! the smr.event(1) itself will return 1 (true) if the function is defined (i.e. plugin loaded) and 0 (false) if it is not.
Language definition
The language definition is:
Rules
ruleBlock ::= ruleOpenTag ruleBody ruleCloseTag
ruleOpenTag ::= '<rule' 'name=' ruleName [runAttribute] [ruleCondition] [evaluateOrder]'>\n'
ruleName ::= '"' symbol '"'
runAttribute ::= 'run' ['="true"' | '="false"']
ruleCondition ::= 'if="' expression '"' (this expression may be split into more lines)
evaluateOrder ::= 'order="' number '"' (number in range 1..100, default is 50, lower number is run first)
ruleCloseTag ::= '</rule>\n'
ruleBody ::= [parameters] [description] [ruleInitBlock] ruleMainBlock [rulePostBlock]
parameters ::= '<parameters ' [symbol '="' [paramDefaultValue] '"']* '/>\n'
paramDefaultValue ::= constantNumericValue | constantStringValue
description ::= '<description>\n' xmlText '</description>\n'
xmlText ::= any 7-bit characters except '\0', '&' and '<', the last two should be coded as & and <
ruleInitBlock ::= '<init>\n' [declarationStatement]* '</init>\n'
declarationStatement ::= ruleBlock | executableStatement
ruleMainBlock ::= [statement]*
rulePostBlock ::= '<post>\n' [statement]* '</post>\n'
constantStringValue ::= ' [xmlText] ' | " [xmlText "
Statements
statement ::= executableStatement | controlStatement
executableStatement ::= assignment | procedureCall | blockStatement | breakStatement | enableStatement | ifStatement | loopStatement | caseStatement | emptyStatement
controlStatement ::= proceureCall ':' expression [remark] '\n'
assignment ::= [symbol.]* symbol '=' expression [remark] '\n'
procedureCall ::= [symbol.]* symbol '(' [expression [',' expression]* ')' [remark] '\n'
blockStatement ::= '<block>\n' [statement]* '</block>\n'
breakStatement ::= ('break' [symbol] | 'continue' ) [remark] '\n'
enableStatement ::= ('enable' | 'disable') [symbol] [remark] '\n'
ifStatement ::= 'if (' expression ')' [remark] '\n' statement [remark] '\n' [elseStatement]
elseStatement ::= 'else' [remark] '\n' statement [remark] '\n'
loopStatement ::= forStatement | whileStatement
forStatement ::= 'for (' assignment ';' expression ';' assignment ')' [remark] '\n' statement [remark] '\n'
whileStatement ::= 'while (' expression ')' [remark] '\n' statement [remark] '\n'
case statement ::= 'switch (' expression ')' [remark] '\n' blockCaseStatement
blockCaseStatement ::= '<block>\n' [caseStatement]* [caseDefaultStatement] '</block>\n'
caseStatement ::= 'case ' int [, int]* [remark] '\n' [statement]*
caseDefaultStatement ::= 'default' [remark] \n [statement]*
emptyStatement ::= [remark] '\n'
remark ::= ('#' | ';' | '//') xmlText except '\n'