From 5aa98072d064f2b06c4ba6575b2be1a6bbde406a Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Tue, 21 Apr 2020 17:43:17 +0200 Subject: [PATCH] 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 --- .../src/main/jastadd/Computation.jrag | 8 -- ros2rag.example/src/main/jastadd/Example.jadd | 5 + .../src/main/jastadd/Example.relast | 3 +- .../src/main/jastadd/Example.ros2rag | 1 + .../src/main/jastadd/Generated.jrag | 84 +++++++++------- .../src/main/jastadd/Generated.relast | 5 + .../st/ros2rag/example/GeneratedJoint.java | 73 -------------- .../st/ros2rag/example/GeneratedRobotArm.java | 85 ---------------- .../inf/st/ros2rag/example/Main.java | 97 ++++++++++++++++--- 9 files changed, 146 insertions(+), 215 deletions(-) delete mode 100644 ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java delete mode 100644 ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java diff --git a/ros2rag.example/src/main/jastadd/Computation.jrag b/ros2rag.example/src/main/jastadd/Computation.jrag index 029d55c..4d72197 100644 --- a/ros2rag.example/src/main/jastadd/Computation.jrag +++ b/ros2rag.example/src/main/jastadd/Computation.jrag @@ -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(); - } - } diff --git a/ros2rag.example/src/main/jastadd/Example.jadd b/ros2rag.example/src/main/jastadd/Example.jadd index 1b862a6..1fd2549 100644 --- a/ros2rag.example/src/main/jastadd/Example.jadd +++ b/ros2rag.example/src/main/jastadd/Example.jadd @@ -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 + ")"; + } } } diff --git a/ros2rag.example/src/main/jastadd/Example.relast b/ros2rag.example/src/main/jastadd/Example.relast index 8fc963c..f4b2aa0 100644 --- a/ros2rag.example/src/main/jastadd/Example.relast +++ b/ros2rag.example/src/main/jastadd/Example.relast @@ -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; diff --git a/ros2rag.example/src/main/jastadd/Example.ros2rag b/ros2rag.example/src/main/jastadd/Example.ros2rag index 4dab39a..f6b49f2 100644 --- a/ros2rag.example/src/main/jastadd/Example.ros2rag +++ b/ros2rag.example/src/main/jastadd/Example.ros2rag @@ -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 { diff --git a/ros2rag.example/src/main/jastadd/Generated.jrag b/ros2rag.example/src/main/jastadd/Generated.jrag index 62677f9..2dfef1f 100644 --- a/ros2rag.example/src/main/jastadd/Generated.jrag +++ b/ros2rag.example/src/main/jastadd/Generated.jrag @@ -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); } } diff --git a/ros2rag.example/src/main/jastadd/Generated.relast b/ros2rag.example/src/main/jastadd/Generated.relast index e69de29..af903c2 100644 --- a/ros2rag.example/src/main/jastadd/Generated.relast +++ b/ros2rag.example/src/main/jastadd/Generated.relast @@ -0,0 +1,5 @@ +// RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition ; +rel RobotArm._dependency1Source* <-> Joint._dependency1Target* ; + +// RobotArm._AppropriateSpeed canDependOn RobotArm._AttributeTestSource as dependency2 ; +rel RobotArm._dependency2Source* <-> RobotArm._dependency2Target* ; diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java deleted file mode 100644 index 9dd585e..0000000 --- a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java +++ /dev/null @@ -1,73 +0,0 @@ -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(); - } - -} diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java deleted file mode 100644 index 55a50d9..0000000 --- a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java +++ /dev/null @@ -1,85 +0,0 @@ -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); - } -} diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java index bd7628e..8529fa2 100644 --- a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java +++ b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java @@ -1,6 +1,9 @@ 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); } } -- GitLab