diff --git a/ros2rag.base/src/main/jastadd/backend/Mappings.jrag b/ros2rag.base/src/main/jastadd/backend/Mappings.jrag index fd40385d9e8879dd188362f6a0acd802652f511e..600ce92ee064f45fe0f41860dd3ab0bc5e334bd3 100644 --- a/ros2rag.base/src/main/jastadd/backend/Mappings.jrag +++ b/ros2rag.base/src/main/jastadd/backend/Mappings.jrag @@ -111,7 +111,7 @@ aspect Mappings { // or if no mappings are specified. // then prepend the suitable default mapping java.util.List<MappingDefinition> result; - if (getMappingList().size() == 0 || !getMappingList().get(0).getFromType().isByteArray()) { + if (getMappingList().isEmpty() || !getMappingList().get(0).getFromType().isByteArray()) { result = new java.util.ArrayList(); result.add(suitableDefaultMapping()); result.addAll(getMappingList()); @@ -161,7 +161,9 @@ aspect Mappings { // --- suitableDefaultMapping --- syn DefaultMappingDefinition UpdateDefinition.suitableDefaultMapping(); eq ReadFromMqttDefinition.suitableDefaultMapping() { - String typeName = getToken().getJavaTypeUse().getName(); + String typeName = getMappingList().isEmpty() ? + getToken().getJavaTypeUse().getName() : + getMappingList().get(0).getFromType().prettyPrint(); switch(typeName) { case "int": case "Integer": return ros2rag().defaultBytesToIntMapping(); @@ -180,7 +182,9 @@ aspect Mappings { } } eq WriteToMqttDefinition.suitableDefaultMapping() { - String typeName = getToken().getJavaTypeUse().getName(); + String typeName = getMappingList().isEmpty() ? + getToken().getJavaTypeUse().getName() : + getMappingList().get(getMappingList().size() - 1).getFromType().prettyPrint(); switch(typeName) { case "int": case "Integer": return ros2rag().defaultIntToBytesMapping(); diff --git a/ros2rag.base/src/main/resources/MqttHandler.jadd b/ros2rag.base/src/main/resources/MqttHandler.jadd index 47005270a0cae49229d70760be5be694ea3ca20f..c3e98dc89377e6217946fafa54a9eb7ab7594c61 100644 --- a/ros2rag.base/src/main/resources/MqttHandler.jadd +++ b/ros2rag.base/src/main/resources/MqttHandler.jadd @@ -21,7 +21,7 @@ public class MqttHandler { private boolean sendWelcomeMessage = true; private org.fusesource.mqtt.client.QoS qos; /** Dispatch knowledge */ - private final java.util.Map<String, java.util.function.Consumer<byte[]>> callbacks; + private final java.util.Map<String, java.util.List<java.util.function.Consumer<byte[]>>> callbacks; public MqttHandler() { this("Ros2Rag"); @@ -81,13 +81,15 @@ public class MqttHandler { @Override public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic, org.fusesource.hawtbuf.Buffer body, org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) { String topicString = topic.toString(); - java.util.function.Consumer<byte[]> callback = callbacks.get(topicString); - if (callback == null) { + java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topicString); + if (callbackList == null || callbackList.isEmpty()) { logger.debug("Got a message, but no callback to call. Forgot to unsubscribe?"); } else { byte[] message = body.toByteArray(); // System.out.println("message = " + Arrays.toString(message)); - callback.accept(message); + for (java.util.function.Consumer<byte[]> callback : callbackList) { + callback.accept(message); + } } ack.onSuccess(null); // always acknowledge message } @@ -163,27 +165,30 @@ public class MqttHandler { public void newConnection(String topic, java.util.function.Consumer<byte[]> callback) { if (!ready) { - // TODO should maybe be something more kind than throwing an exception here + // should maybe be something more kind than throwing an exception here throw new IllegalStateException("Updater not ready"); } // register callback - callbacks.put(topic, callback); + if (callbacks.get(topic) == null) { + callbacks.put(topic, new java.util.ArrayList<>()); - // subscribe at broker - org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) }; - connection.getDispatchQueue().execute(() -> { - connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() { - @Override - public void onSuccess(byte[] qoses) { - logger.debug("Subscribed to {}, qoses: {}", topic, qoses); - } + // subscribe at broker + org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) }; + connection.getDispatchQueue().execute(() -> { + connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() { + @Override + public void onSuccess(byte[] qoses) { + logger.debug("Subscribed to {}, qoses: {}", topic, qoses); + } - @Override - public void onFailure(Throwable cause) { - logger.error("Could not subscribe to {}", topic, cause); - } + @Override + public void onFailure(Throwable cause) { + logger.error("Could not subscribe to {}", topic, cause); + } + }); }); - }); + } + callbacks.get(topic).add(callback); } /** diff --git a/ros2rag.base/src/main/resources/mqtt.mustache b/ros2rag.base/src/main/resources/mqtt.mustache index fb2b173c35cc802c5c5c661cf22ce331c20fbbae..d65ca944a9dc7e881ff75d89ea696d4bb1fe09fc 100644 --- a/ros2rag.base/src/main/resources/mqtt.mustache +++ b/ros2rag.base/src/main/resources/mqtt.mustache @@ -1,5 +1,6 @@ aspect MQTT { - private MqttHandler {{rootNodeName}}.{{mqttHandlerField}} = new MqttHandler(); + private String {{rootNodeName}}.MqttName() { return "Ros2Rag"; } + private MqttHandler {{rootNodeName}}.{{mqttHandlerField}} = new MqttHandler(MqttName()); public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host) throws java.io.IOException { {{mqttHandlerField}}.setHost(host); } diff --git a/ros2rag.common/config.yaml b/ros2rag.common/config.yaml index 8f49855352242a04efc633af2d30a8b0efa029dc..687a32c95842c1b498bdb2f5f8c15bcb9a97a818 100644 --- a/ros2rag.common/config.yaml +++ b/ros2rag.common/config.yaml @@ -24,7 +24,9 @@ panda_mqtt_connector: panda: EndEffector: "panda::panda_link7" goal_poses: - - position: "0 0 0" - wait: "3" - position: "1 1 1" - wait: "2" + wait: "5000" + - position: "1 0 1" + wait: "3000" + - position: "0 0 1" + wait: "4000" diff --git a/ros2rag.goal/src/main/jastadd/Computation.jrag b/ros2rag.goal/src/main/jastadd/Computation.jrag index c64146e8de760553aaf7aef779c80d94306137b2..ffa46d6323e2552f36e1da7442991f1fc8c5da61 100644 --- a/ros2rag.goal/src/main/jastadd/Computation.jrag +++ b/ros2rag.goal/src/main/jastadd/Computation.jrag @@ -1,51 +1,26 @@ aspect Computation { -// syn boolean RobotArm.isInSafetyZone() { -// System.out.println("isInSafetyZone()"); -// for (Link link : getLinkList()) { -// if (model().getZoneModel().isInSafetyZone(link.getCurrentPosition())) { -// return true; -// } -// } -// return model().getZoneModel().isInSafetyZone(getEndEffector().getCurrentPosition()); -// } -// -// cache ZoneModel.isInSafetyZone(IntPosition pos); -// syn boolean ZoneModel.isInSafetyZone(IntPosition pos) { -// System.out.println("isInSafetyZone(" + pos + ")"); -// for (Zone sz : getSafetyZoneList()) { -// for (Coordinate coordinate : sz.getCoordinateList()) { -// if (coordinate.getPosition().equals(pos)) { -// return true; -// } -// } -// } -// return false; -// } -// -// syn double RobotArm.getAppropriateSpeed() { -// return isInSafetyZone() ? 0.4d : 1.0d; -// } syn Workflow GoalModel.getWorkflow() { Workflow result = new Workflow(); - Step lastStep = null; int index = 0; + Step startStep = new StartStep(); + startStep.setId(index++); + result.addStep(startStep); + result.setCurrentStep(LastUpdatedInteger.of(startStep.getId())); + Step lastStep = startStep; for (WorkPose workPose : getWorkPoseList()) { MoveToStep moveToStep = new MoveToStep(); moveToStep.setId(index++); moveToStep.setPosition(workPose.getPosition()); - if (lastStep != null) { - lastStep.setSuccessor(moveToStep); - } else { - // this is the first move-to-step - result.setCurrentStep(moveToStep.getId()); - } + lastStep.setSuccessor(moveToStep); result.addStep(moveToStep); + WorkStep workStep = new WorkStep(); workStep.setId(index++); workStep.setDuration(workPose.getDuration()); moveToStep.setSuccessor(workStep); result.addStep(workStep); - lastStep = moveToStep; + + lastStep = workStep; } FinishedStep finish = new FinishedStep(); finish.setId(index); @@ -61,6 +36,7 @@ aspect Computation { } syn int Step.readyForThisStep(); + eq StartStep.readyForThisStep() = getSuccessor().getId(); eq MoveToStep.readyForThisStep() { // check if we have reached the destination yet if (isNear(model().getRobotState().getCurrentPosition(), getPosition())) { @@ -71,7 +47,8 @@ aspect Computation { } eq WorkStep.readyForThisStep() { // check if we have "worked" long enough - if (lastStarted() + getDuration() >= model().getRobotState().getLastUpdate()) { + System.out.println("lastStarted: " + lastStarted() + ", duration: " + getDuration() + ", lastUpdate: " + model().getRobotState().getLastUpdate()); + if (model().getRobotState().getLastUpdate() >= lastStarted() + getDuration()) { // proceed to next step return getSuccessor().getId(); } @@ -87,6 +64,7 @@ aspect Computation { return currentStep().nextPosition(); } syn DoublePosition Step.nextPosition(); + eq StartStep.nextPosition() = null; // next position should not be called before step is advanced eq MoveToStep.nextPosition() = getPosition(); eq WorkStep.nextPosition() = getPredecessor().nextPosition(); eq FinishedStep.nextPosition() = getPredecessor().nextPosition(); diff --git a/ros2rag.goal/src/main/jastadd/GoalModel.jadd b/ros2rag.goal/src/main/jastadd/GoalModel.jadd index f114361784e4a9db8f8ac0674408220999108399..7dd67fbc46fb3abca9715fbffae3ceb29846aa53 100644 --- a/ros2rag.goal/src/main/jastadd/GoalModel.jadd +++ b/ros2rag.goal/src/main/jastadd/GoalModel.jadd @@ -44,4 +44,49 @@ aspect GrammarTypes { return "(" + x + ", " + y + ", " + z + ")"; } } + + public class LastUpdatedInteger { + private final int value; + private final long timestampUpdated; + + private LastUpdatedInteger(int value, long timestampUpdated) { + this.value = value; + this.timestampUpdated = timestampUpdated; + } + + public static LastUpdatedInteger of(int value) { + return new LastUpdatedInteger(value, System.currentTimeMillis()); + } + + public int getValue() { + return value; + } + + public long getTimestampUpdated() { + return timestampUpdated; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LastUpdatedInteger that = (LastUpdatedInteger) o; + return value == that.value && + timestampUpdated == that.timestampUpdated; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(value, timestampUpdated); + } + + @Override + public String toString() { + return "(" + value + " @ " + timestampUpdated + ")"; + } + } + + refine MQTT String GoalModel.MqttName() { + return "GoalModel"; + } } diff --git a/ros2rag.goal/src/main/jastadd/GoalModel.relast b/ros2rag.goal/src/main/jastadd/GoalModel.relast index 8c1b2c33425c4e0fe27e100063fadf886d24fee1..ada05c63e58672128740f3a1616f2bf5ed20cf59 100644 --- a/ros2rag.goal/src/main/jastadd/GoalModel.relast +++ b/ros2rag.goal/src/main/jastadd/GoalModel.relast @@ -4,11 +4,12 @@ RobotState ::= <CurrentPosition:DoublePosition> <LastUpdate:long> ; WorkPose ::= <Position:DoublePosition> <Duration:long> ; // Position in [m,m,m], Duration in ms -Workflow ::= Step* /<ReadyForThisStep:int>/ /<NextPosition:DoublePosition>/ <CurrentStep:int> <CurrentStepStart:long> ; // NextPosition in [m,m,m]; CurrentStepStart in ms; ReadyForThisStep and CurrentStep are step ids +Workflow ::= Step* /<ReadyForThisStep:int>/ /<NextPosition:DoublePosition>/ <CurrentStep:LastUpdatedInteger> ; // NextPosition in [m,m,m]; CurrentStepStart in ms; ReadyForThisStep and CurrentStep are step ids abstract Step ::= <Id:int> ; // Started in ms MoveToStep : Step ::= <Position:DoublePosition> ; // Position in [m,m,m] WorkStep : Step ::= <Duration:long> ; // Duration in ms +StartStep : Step ; FinishedStep : Step ; rel Step.successor <-> Step.predecessor; diff --git a/ros2rag.goal/src/main/jastadd/GoalModel.ros2rag b/ros2rag.goal/src/main/jastadd/GoalModel.ros2rag index 1e99b20fc0f176e60e7621931180a53a19ca1515..188b6bfc7c757c3e1553f260ab6062c65bf026df 100644 --- a/ros2rag.goal/src/main/jastadd/GoalModel.ros2rag +++ b/ros2rag.goal/src/main/jastadd/GoalModel.ros2rag @@ -4,8 +4,11 @@ // --- update definitions --- read RobotState.CurrentPosition using ParseLinkState, LinkStateToDoublePosition ; read RobotState.LastUpdate using TickWhenLinkState ; -write Workflow.ReadyForThisStep ; // |_ Those two roles encode are attribute-driven rewrite (via mqtt) -read Workflow.CurrentStep ; // | + +// Those two roles together encode an attribute-driven rewrite (via mqtt) +write Workflow.ReadyForThisStep ; +read Workflow.CurrentStep using ParseLastUpdatedInteger ; + write Workflow.NextPosition using CreateTrajectoryMessage, SerializeTrajectory ; // --- dependency definitions --- @@ -21,7 +24,7 @@ ParseLinkState maps byte[] bytes to panda.Linkstate.PandaLinkState {: :} TickWhenLinkState maps byte[] bytes to long {: -// System.out.println("TickWhenLinkState"); + System.out.println("TickWhenLinkState"); return System.currentTimeMillis(); :} @@ -31,7 +34,7 @@ SerializeTrajectory maps plan.TrajectoryOuterClass.Trajectory t to byte[] {: :} LinkStateToDoublePosition maps panda.Linkstate.PandaLinkState pls to DoublePosition {: -// System.out.println("LinkStateToDoublePosition"); + System.out.println("LinkStateToDoublePosition"); panda.Linkstate.PandaLinkState.Position p = pls.getPos(); return DoublePosition.of(p.getPositionX(), p.getPositionY(), p.getPositionZ()); :} @@ -46,3 +49,7 @@ CreateTrajectoryMessage maps DoublePosition dp to plan.TrajectoryOuterClass.Traj .build()) .build(); :} + +ParseLastUpdatedInteger maps int value to LastUpdatedInteger {: + return LastUpdatedInteger.of(value); +:} diff --git a/ros2rag.goal/src/main/jastadd/Navigation.jrag b/ros2rag.goal/src/main/jastadd/Navigation.jrag index 56d596c2a33dcb6d33bb4028fee173b4a61db01a..90630eec8de02f2a4bdb6f8f821becfb8ef18ad1 100644 --- a/ros2rag.goal/src/main/jastadd/Navigation.jrag +++ b/ros2rag.goal/src/main/jastadd/Navigation.jrag @@ -6,7 +6,7 @@ aspect Navigation { // eq RobotArm.getLink().containingRobotArm() = this; // eq RobotArm.getEndEffector().containingRobotArm() = this; syn Step Workflow.currentStep() { - return resolveStep(getCurrentStep()); + return resolveStep(getCurrentStep().getValue()); } syn Step Workflow.resolveStep(int id) { @@ -23,5 +23,5 @@ aspect Navigation { eq GoalModel.getChild().model() = this; inh long Step.lastStarted(); - eq Workflow.getStep().lastStarted() = getCurrentStepStart(); + eq Workflow.getStep().lastStarted() = getCurrentStep().getTimestampUpdated(); } diff --git a/ros2rag.goal/src/main/jastadd/Printing.jrag b/ros2rag.goal/src/main/jastadd/Printing.jrag index cc2c91bbbb17d3bd885d1bb41765b4fad7f362cf..b82655bd949e3d45efa6fdf229ef3d840ef8c628 100644 --- a/ros2rag.goal/src/main/jastadd/Printing.jrag +++ b/ros2rag.goal/src/main/jastadd/Printing.jrag @@ -1,6 +1,8 @@ aspect Printing { - syn String Step.prettyPrint(); - eq MoveToStep.prettyPrint() = getId() + "Move to " + getPosition(); - eq WorkStep.prettyPrint() = getId() + "Work " + getDuration(); - eq FinishedStep.prettyPrint() = getId() + "Finish"; + syn String Step.prettyPrint() = getId() + ": " + prettyPrint0() + " ->[" + (getSuccessor() == null ? "/" : getSuccessor().getId()) + "]"; + syn String Step.prettyPrint0(); + eq StartStep.prettyPrint0() = "Start"; + eq MoveToStep.prettyPrint0() = "Move to " + getPosition(); + eq WorkStep.prettyPrint0() = "Work " + getDuration(); + eq FinishedStep.prettyPrint0() = "Finish"; } diff --git a/ros2rag.goal/src/main/java/de/tudresden/inf/st/ros2rag/goal/GoalMain.java b/ros2rag.goal/src/main/java/de/tudresden/inf/st/ros2rag/goal/GoalMain.java index 39e94bbe1043e6d0c2aab981d1d1a1b07ba7d7d8..75db6261c62fb5464484a99baf1f032906f5e3af 100644 --- a/ros2rag.goal/src/main/java/de/tudresden/inf/st/ros2rag/goal/GoalMain.java +++ b/ros2rag.goal/src/main/java/de/tudresden/inf/st/ros2rag/goal/GoalMain.java @@ -81,9 +81,11 @@ public class GoalMain { robotState.connectLastUpdate(topic); } }, config); - model.getWorkflow().connectCurrentStep(config.topics.nextStep); - model.getWorkflow().connectReadyForThisStep(config.topics.nextStep, false); + // next position is not initialized, so don't send it model.getWorkflow().connectNextPosition(config.topics.trajectory, false); + model.getWorkflow().connectCurrentStep(config.topics.nextStep); + // initial next step is sent, as soon as this is received, the workflow starts + model.getWorkflow().connectReadyForThisStep(config.topics.nextStep, true); logStatus("Start"); CountDownLatch exitCondition = new CountDownLatch(1); @@ -123,7 +125,8 @@ public class GoalMain { sb.append(" ").append(step.prettyPrint()).append("\n"); } sb.append("CurrentStep: ").append(model.getWorkflow().getCurrentStep()) - .append(", lastStart: ").append(model.getWorkflow().getCurrentStepStart()).append("\n"); +// .append(", lastStart: ").append(model.getWorkflow().getCurrentStepStart()) + .append(", readyForThisStep: ").append(model.getWorkflow().getReadyForThisStep()).append("\n"); sb.append("CurrentPosition: ").append(model.getRobotState().getCurrentPosition()) .append(", lastUpdate: ").append(model.getRobotState().getLastUpdate()).append("\n"); logger.info(sb.toString());