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