Commit 5aa98072 authored by René Schöne's avatar René Schöne
Browse files

Example: Working state for dependency realization using relations.

- added relations that would be generated
- added methods for convenience dependency specification
- added parameter "writeCurrentValue" to connect methods of write definitions (not always wanted, thus optional)
- split method to update token-NTA in update (which now flushes) and write (using saved last value)
- cleanup of unused code
parent 874da761
Pipeline #6379 passed with stage
in 1 minute and 24 seconds
......@@ -29,12 +29,4 @@ aspect Computation {
syn double RobotArm.get_AppropriateSpeed() {
return isInSafetyZone() ? 0.4d : 1.0d;
}
// this should be a NTA-terminal "getAppropriateSpeed()"
syn config.Robotconfig.RobotConfig RobotArm.createSlowSpeedMessage() {
return config.Robotconfig.RobotConfig.newBuilder()
.setSpeed(isInSafetyZone() ? 0.4d : 1.0d)
.build();
}
}
......@@ -38,5 +38,10 @@ aspect GrammarTypes {
public int hashCode() {
return java.util.Objects.hash(x, y, z);
}
@Override
public String toString() {
return "(" + x + ", " + y + ", " + z + ")";
}
}
}
......@@ -4,10 +4,9 @@ ZoneModel ::= <Size:IntPosition> SafetyZone:Zone* ;
Zone ::= Coordinate* ;
// Do not use terminal-NTA's for now, as relast has problems with it "/<ShouldUseLowSpeed:Boolean>/" ;
RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ;
Joint ::= <Name> <_CurrentPosition:IntPosition> ;
Joint ::= <Name> <_CurrentPosition:IntPosition> ; // normally this would be: <CurrentPosition:IntPosition>
EndEffector : Joint;
......
......@@ -7,6 +7,7 @@ write RobotArm._AppropriateSpeed using CreateSpeedMessage ;
// --- dependency definitions ---
RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;
RobotArm._AppropriateSpeed canDependOn RobotArm._AttributeTestSource as dependency2 ;
// --- mapping definitions ---
LinkStateToIntPosition: map protobuf panda.Linkstate.PandaLinkState x to IntPosition y {
......
......@@ -35,8 +35,6 @@ aspect GrammarExtension {
_mqttUpdater().newConnection(topic, message -> {
// Parse message into a PandaLinkState
try {
// TODO the line with parseFrom should be generated, but needs a hint, that the type is protobuf
// maybe: "map protobuf panda.Linkstate.PandaLinkState [...]"?
System.out.println("begin update current position");
panda.Linkstate.PandaLinkState x = panda.Linkstate.PandaLinkState.parseFrom(message);
panda.Linkstate.PandaLinkState.Position p = x.getPos();
......@@ -54,11 +52,12 @@ aspect GrammarExtension {
public Joint Joint.setCurrentPosition(IntPosition value) {
set_CurrentPosition(value);
try {
containingRobotArm().update_AppropriateSpeed();
} catch (Exception e) {
// not advisable, but suits for now
e.printStackTrace();
for (RobotArm target : get_dependency1Targets()) {
System.out.println("checking robotarm " + target);
if (target._update_AppropriateSpeed()) {
System.out.println("writing " + target + "_AppropriateSpeed");
target._write_AppropriateSpeed_lastValue();
}
}
return this;
}
......@@ -68,52 +67,71 @@ aspect GrammarExtension {
}
// --- RobotArm ---
private String RobotArm.write_isInSafetyZone_topic = null;
private String RobotArm.write_AppropriateSpeed_topic = null;
private config.Robotconfig.RobotConfig RobotArm.write_AppropriateSpeed_lastValue = null;
/*
this is one way to store the topic, i.e., close to the producing nonterminal.
another way would be a map (or multiple) in the MqttUpdater mapping nonterminal and operation to a topic
*/
private String RobotArm._write_AppropriateSpeed_topic = null;
private config.Robotconfig.RobotConfig RobotArm._write_AppropriateSpeed_lastValue = null;
public RobotArm RobotArm.setAttributeTestSource(int value) {
int old_value = get_AttributeTestSource();
RobotArm result = set_AttributeTestSource(value);
// this is a primitive type, so compare with "!=" instead of !equals()
if (old_value != value && write_isInSafetyZone_topic != null) {
// TODO the method-call "toByteArray" can be generated, if known, that RobotConfig is a protobuf type
// maybe "write [...] using protobuf createSlowSpeedMessage() ;"
config.Robotconfig.RobotConfig config = createSlowSpeedMessage();
_mqttUpdater().publish(write_isInSafetyZone_topic, config.toByteArray());
set_AttributeTestSource(value);
for (RobotArm target : get_dependency2Targets()) {
if (target._update_AppropriateSpeed()) {
target._write_AppropriateSpeed_lastValue();
}
}
return result;
return this;
}
public int RobotArm.getAttributeTestSource() {
return get_AttributeTestSource();
}
public void RobotArm.connect_isInSafetyZone(String topic) {
write_isInSafetyZone_topic = topic;
}
public void RobotArm.connect_AppropriateSpeed(String topic) {
write_AppropriateSpeed_topic = topic;
public void RobotArm.connect_AppropriateSpeed(String topic, boolean writeCurrentValue) {
_write_AppropriateSpeed_topic = topic;
_update_AppropriateSpeed();
if (writeCurrentValue) {
_write_AppropriateSpeed_lastValue();
}
}
public void RobotArm.update_AppropriateSpeed() {
// todo here: 1) read _AppropriateSpeed (which triggers computation, right?)
// 2) maybe: check if value has changed since last publish
// 3) publish
/**
* @apilevel internal
* @return true, if CreateSpeedMessage changed for _AppropriateSpeed
*/
public boolean RobotArm._update_AppropriateSpeed() {
// 1) read _AppropriateSpeed (which triggers computation, right?)
// 2) maybe: check if value has changed since last publish
get_AppropriateSpeed_reset();
double x = get_AppropriateSpeed();
// begin transformation "CreateSpeedMessage"
config.Robotconfig.RobotConfig y = config.Robotconfig.RobotConfig.newBuilder()
.setSpeed(x)
.build();
// end transformation "CreateSpeedMessage"
if (y.equals(write_AppropriateSpeed_lastValue)) {
if (y.equals(_write_AppropriateSpeed_lastValue)) {
// if always is absent, then return here
return;
return false;
}
// remember last value
_write_AppropriateSpeed_lastValue = y;
return true;
}
public void RobotArm._write_AppropriateSpeed_lastValue() {
// 3) publish
// we know, it is a protobuf type, so call "toByteArray"
byte[] bytes = y.toByteArray();
_mqttUpdater().publish(write_AppropriateSpeed_topic, bytes);
byte[] bytes = _write_AppropriateSpeed_lastValue.toByteArray();
_mqttUpdater().publish(_write_AppropriateSpeed_topic, bytes);
}
public void RobotArm.addDependency1(Joint source) {
this.add_dependency1Source(source);
}
public void RobotArm.addDependency2(RobotArm source) {
this.add_dependency2Source(source);
}
}
// RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition ;
rel RobotArm._dependency1Source* <-> Joint._dependency1Target* ;
// RobotArm._AppropriateSpeed canDependOn RobotArm._AttributeTestSource as dependency2 ;
rel RobotArm._dependency2Source* <-> RobotArm._dependency2Target* ;
package de.tudresden.inf.st.ros2rag.example;
import com.google.protobuf.InvalidProtocolBufferException;
import de.tudresden.inf.st.ros2rag.ast.IntPosition;
import de.tudresden.inf.st.ros2rag.ast.Joint;
import panda.Linkstate.PandaLinkState;
import panda.Linkstate.PandaLinkState.Position;
/**
* Manually written code for Joint to be actually generated later.
*
* @author rschoene - Initial contribution
*/
public class GeneratedJoint extends Joint {
/*
Input for this to be generated:
// when an update of pose is read via mqtt, then update current position
[always] read Joint.CurrentPosition using LinkStateToIntPosition;
// panda.LinkState is a datatype defined in protobuf
LinkStateToIntPosition: map panda.Linkstate.PandaLinkState x to IntPosition y using {
panda.Linkstate.PandaLinkState.Position p = x.getPos();
y = IntPosition.of((int) p.getPositionX(), (int) p.getPositionY(), (int) p.getPositionZ());
}
*/
public void connectCurrentPosition(String topic) {
_mqttUpdater().newConnection(topic, message -> {
// Parse message into a PandaLinkState
try {
// TODO the line with parseFrom should be generated, but needs a hint, that the type is protobuf
// maybe: "map protobuf panda.Linkstate.PandaLinkState [...]"?
PandaLinkState x = PandaLinkState.parseFrom(message);
panda.Linkstate.PandaLinkState.Position p = x.getPos();
IntPosition y = IntPosition.of((int) p.getPositionX(), (int) p.getPositionY(), (int) p.getPositionZ());
// >> when "always" is absent, generate this
if (getCurrentPosition().equals(y)) { return; }
// >> until here
setCurrentPosition(y);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
});
}
/*
next try with token-attribute "_AppropriateSpeed" (maybe the underscore is not needed)
(A) [always] write RobotArm._AppropriateSpeed using CreateSpeedMessage ; // _AppropriateSpeed is a token-attribute, and CreateSpeedMessage is a mapping (use)
(B1) RobotArm._AppropriateSpeed dependsOn RobotArm.getJoint().CurrentPosition ; // dependency from token of subtree of robot-arm to token-attribute of robot-arm
(B2) Joint.CurrentPosition changes containingRobotArm()._AppropriateSpeed ; // switched dependency-edge, now from source (left) to target (right), enables attributes for navigation
CreateSpeedMessage: map double x to protobuf config.Robotconfig.RobotConfig y {
y = config.Robotconfig.RobotConfig.newBuilder()
.setSpeed(x)
.build();
}
details to remember: we probably need to distinguish the target. JM claims to only set token-attribute, but could also be normal attributes we want to send away (no difference, both need to be re-computed)
*/
public GeneratedJoint setCurrentPosition(IntPosition value) {
set_CurrentPosition(value);
// (the cast won't be necessary if generated into RobotArm)
((GeneratedRobotArm) containingRobotArm()).update_AppropriateSpeed();
return this;
}
public IntPosition getCurrentPosition() {
return get_CurrentPosition();
}
}
package de.tudresden.inf.st.ros2rag.example;
import config.Robotconfig;
import de.tudresden.inf.st.ros2rag.ast.RobotArm;
/**
* Manually written code for RobotArm to be actually generated later.
*
* @author rschoene - Initial contribution
*/
public class GeneratedRobotArm extends RobotArm {
/*
// as soon as the cache of isInSafetyZone is invalidated, update the value of Robot.ShouldUseLowSpeed with its value
[always] update Robot.ShouldUseLowSpeed with isInSafetyZone() using transformation();
// when a (new?) value for ShouldUseLowSpeed is set, send it over via mqtt
[always] write Robot.ShouldUseLowSpeed;
---
actually, it should look like:
(A) [always] write RobotArm.createSpeedMessage() when isInSafetyZone() ; // write attribute value, when the second attribute changes. maybe we should not allow the "when" clause, if we allow attribute dependencies
(B) RobotArm.isInSafetyZone() dependsOn RobotArm.AttributeTestSource ; // explicit dependency for implicit instance of the same RobotArm
(C) RobotArm.createSpeedMessage() dependsOn RobotArm.isInSafetyZone() ; // explicit attribute dependency
interesting detail: an attribute has no object, and can not be connected, but a generated method like
"connect_isInSafetyZone(String topic)" can :)
- the line "TYPE.ATTRIBUTE() dependsOn TYPE.TOKEN" implies: 1) change TOKEN to _TOKEN, 2) generate "virtual" setter/getter for TOKEN
- the implications of line "TYPE.ATTRIBUTE_1() dependsOn TYPE.ATTRIBUTE_2()" are currently unclear
- the line "write TYPE.ATTRIBUTE_1() when ATTRIBUTE_2()" implies: 1) add a private field for the topic to publish, 2) add a "connect_ATTRIBUTE_1(String topic)" method to set the topic, 3) inject a trigger in every setter the ATTRIBUTE_2 depends on, to check for a change (unless "always" was specified), store the result of ATTRIBUTE_1 in a variable, and publish this variable for the stored topic
*/
/*
this is one way to store the topic, i.e., close to the producing nonterminal.
another way would be a map (or multiple) in the MqttUpdater mapping nonterminal and operation to a topic
*/
private String /*GeneratedRobotArm.*/write_isInSafetyZone_topic = null;
private String /*GeneratedRobotArm.*/write_AppropriateSpeed_topic = null;
private Robotconfig.RobotConfig /*GeneratedRobotArm.*/write_AppropriateSpeed_lastValue = null;
public RobotArm setAttributeTestSource(int value) {
int old_value = super.get_AttributeTestSource();
RobotArm result = super.set_AttributeTestSource(value);
// this is a primitive type, so compare with "!=" instead of !equals()
if (old_value != value && write_isInSafetyZone_topic != null) {
// TODO the method-call "toByteArray" can be generated, if known, that RobotConfig is a protobuf type
// maybe "write [...] using protobuf createSlowSpeedMessage() ;"
Robotconfig.RobotConfig config = createSlowSpeedMessage();
_mqttUpdater().publish(write_isInSafetyZone_topic, config.toByteArray());
}
return result;
}
public int getAttributeTestSource() {
return super.get_AttributeTestSource();
}
public void connect_isInSafetyZone(String topic) {
write_isInSafetyZone_topic = topic;
}
public void connect_AppropriateSpeed(String topic) {
write_AppropriateSpeed_topic = topic;
}
public void update_AppropriateSpeed() {
// todo here: 1) read _AppropriateSpeed (which triggers computation, right?)
// 2) maybe: check if value has changed since last publish
// 3) publish
double x = get_AppropriateSpeed();
// begin transformation "CreateSpeedMessage"
Robotconfig.RobotConfig y = Robotconfig.RobotConfig.newBuilder()
.setSpeed(x)
.build();
// end transformation "CreateSpeedMessage"
if (y.equals(write_AppropriateSpeed_lastValue)) {
// if always is absent, then return here
return;
}
// we know, it is a protobuf type, so call "toByteArray"
byte[] bytes = y.toByteArray();
_mqttUpdater().publish(write_AppropriateSpeed_topic, bytes);
}
}
package de.tudresden.inf.st.ros2rag.example;
import de.tudresden.inf.st.ros2rag.ast.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import panda.Linkstate;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
......@@ -11,6 +14,12 @@ import java.util.concurrent.TimeUnit;
* @author rschoene - Initial contribution
*/
public class Main {
private static final String TOPIC_JOINT1 = "robot/joint1";
private static final String TOPIC_CONFIG = "robot/config";
private static final Logger logger = LogManager.getLogger(Main.class);
public static void main(String[] args) throws IOException, InterruptedException {
Model model = new Model();
model.MqttSetHost("localhost");
......@@ -39,36 +48,96 @@ public class Main {
EndEffector endEffector = new EndEffector();
endEffector.setName("gripper");
endEffector.setCurrentPosition(myPosition); // fixme: of course, this should be without "_" once generated
endEffector.setCurrentPosition(makePosition(2, 2, 3));
robotArm.addJoint(joint1);
robotArm.setEndEffector(endEffector);
model.setRobotArm(robotArm);
// add dependencies
robotArm.addDependency1(joint1);
robotArm.addDependency1(endEffector);
robotArm.addDependency2(robotArm);
model.MqttWaitUntilReady(2, TimeUnit.SECONDS);
joint1.connectCurrentPosition("robot/joint1");
robotArm.connect_isInSafetyZone("robot/config");
joint1.connectCurrentPosition(TOPIC_JOINT1);
robotArm.connect_AppropriateSpeed(TOPIC_CONFIG, true);
startDelayedSendOne();
logStatus("BEFORE", robotArm, joint1);
// logger.info("Now invoke ./send_one.sh");
// Thread.sleep(3000);
// robotArm.setAttributeTestSource(1);
// Thread.sleep(3000);
// robotArm.setAttributeTestSource(5);
Thread.sleep(6000);
System.out.println("BEFORE joint1.getCurrentPosition() = " + stringify(joint1.getCurrentPosition()));
System.out.println("Now invoke ./send_one.sh");
logStatus("AFTER", robotArm, joint1);
Thread.sleep(3000);
logger.info("Now changing AttributeTestSource - this should not emit any new mqtt message");
robotArm.setAttributeTestSource(1);
Thread.sleep(3000);
Thread.sleep(1000);
robotArm.setAttributeTestSource(5);
Thread.sleep(3000);
System.out.println("AFTER joint1.getCurrentPosition() = " + stringify(joint1.getCurrentPosition()));
model.MqttCloseConnections();
}
private static IntPosition makePosition(int x, int y, int z) {
return IntPosition.of(x, y, z);
private static void logStatus(String prefix, RobotArm robotArm, Joint joint1) {
logger.info("{} joint1.getCurrentPosition() = {}, " +
"robotArm.isInSafetyZone = {}, " +
"robotArm.get_AppropriateSpeed = {}",
prefix,
joint1.getCurrentPosition(),
robotArm.isInSafetyZone(),
robotArm.get_AppropriateSpeed());
}
private static String stringify(IntPosition position) {
return "(" + position.getX() + ", " + position.getY() + ", " + position.getZ() + ")";
private static void startDelayedSendOne() {
final int millis = 1500;
new Thread(() -> {
try {
Linkstate.PandaLinkState pls = Linkstate.PandaLinkState.newBuilder()
.setName("Joint1")
.setPos(Linkstate.PandaLinkState.Position.newBuilder()
.setPositionX(0.5f)
.setPositionY(0.5f)
.setPositionZ(0.5f)
.build())
.build();
byte[] bytes = pls.toByteArray();
MqttUpdater mqttUpdater = new MqttUpdater("sender").setHost("localhost", 1883);
// wait until connected, but in total millis
long before = System.currentTimeMillis();
mqttUpdater.waitUntilReady(millis, TimeUnit.MILLISECONDS);
long alreadyWaited = System.currentTimeMillis() - before;
Thread.sleep((long) millis - alreadyWaited);
mqttUpdater.publish(TOPIC_JOINT1, bytes);
Thread.sleep(1000);
pls = Linkstate.PandaLinkState.newBuilder()
.setName("Joint1")
.setPos(Linkstate.PandaLinkState.Position.newBuilder()
.setPositionX(2.5f)
.setPositionY(2.5f)
.setPositionZ(2.5f)
.build())
.build();
bytes = pls.toByteArray();
mqttUpdater.publish(TOPIC_JOINT1, bytes);
mqttUpdater.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
}
private static IntPosition makePosition(int x, int y, int z) {
return IntPosition.of(x, y, z);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment