From d314ec04e5bae2ab139e731523991fc2f6ef3c0a Mon Sep 17 00:00:00 2001
From: Johannes Mey <johannes.mey@tu-dresden.de>
Date: Wed, 5 Jan 2022 17:08:17 +0100
Subject: [PATCH] move files, apply formatting, work on demo

---
 CMakeLists.txt            |   4 +-
 include/action.h          | 124 ++++++++++
 include/cell.h            |  30 +++
 include/cell_controller.h | 126 ++++++++++
 include/task.h            |  23 ++
 src/action.cpp            |   1 +
 src/cell.cpp              |   2 +
 src/cell_controller.cpp   |   1 +
 src/main_controller.cpp   | 478 ++++++++++++--------------------------
 src/task.cpp              |   1 +
 10 files changed, 456 insertions(+), 334 deletions(-)
 create mode 100644 include/action.h
 create mode 100644 include/cell.h
 create mode 100644 include/cell_controller.h
 create mode 100644 include/task.h
 create mode 100644 src/action.cpp
 create mode 100644 src/cell.cpp
 create mode 100644 src/cell_controller.cpp
 create mode 100644 src/task.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c2491a8..66e85de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -135,8 +135,8 @@ include_directories(
 ## With catkin_make all packages are built within a single CMake context
 ## The recommended prefix ensures that target names across packages don't collide
 add_executable(${PROJECT_NAME}_dummy_selection_provider src/dummy_selection_provider.cpp ${PROTO_SRCS} ${PROTO_HDRS})
-add_executable(${PROJECT_NAME}_dummy_sorting_controller src/dummy_sorting_controller.cpp ${PROTO_SRCS} ${PROTO_HDRS} )
-add_executable(${PROJECT_NAME}_main_controller src/main_controller.cpp ${PROTO_SRCS} ${PROTO_HDRS} )
+add_executable(${PROJECT_NAME}_dummy_sorting_controller src/dummy_sorting_controller.cpp ${PROTO_SRCS} ${PROTO_HDRS})
+add_executable(${PROJECT_NAME}_main_controller src/main_controller.cpp ${PROTO_SRCS} ${PROTO_HDRS} src/cell.cpp src/action.cpp src/task.cpp src/cell_controller.cpp include/cell_controller.h)
 add_executable(${PROJECT_NAME}_moveit_sorting_controller src/moveit_sorting_controller.cpp ${PROTO_SRCS} ${PROTO_HDRS})
 
 
diff --git a/include/action.h b/include/action.h
new file mode 100644
index 0000000..0e60abc
--- /dev/null
+++ b/include/action.h
@@ -0,0 +1,124 @@
+#ifndef CCF_IMMERSIVE_SORTING_ACTION_H_
+#define CCF_IMMERSIVE_SORTING_ACTION_H_
+
+#include <string>
+
+#include "connector.pb.h"
+
+class Action
+{
+public:
+  enum Type
+  {
+    UNDEFINED,
+    PICK_DROP
+  };
+
+private:
+  const Type type_;
+  const std::string source_;
+  const std::string target_;
+
+public:
+  bool operator==(const Action& rhs) const
+  {
+    return type_ == rhs.type_ && source_ == rhs.source_ && target_ == rhs.target_;
+  }
+  bool operator!=(const Action& rhs) const
+  {
+    return !(rhs == *this);
+  }
+
+private:
+  static constexpr Object::Type requiredSourceType(Type type)
+  {
+    switch (type)
+    {
+      case PICK_DROP:
+        return Object::BOX;
+      case UNDEFINED:
+      default:
+        return Object::UNKNOWN;
+    }
+  }
+  static constexpr Object::Type requiredTargetType(Type type)
+  {
+    switch (type)
+    {
+      case PICK_DROP:
+        return Object::BIN;
+      case UNDEFINED:
+      default:
+        return Object::UNKNOWN;
+    }
+  }
+
+  [[nodiscard]] static std::optional<Object> lookupObject(const Scene& scene, const std::string& id)
+  {
+    for (Object object : scene.objects())
+    {
+      if (object.id() == id)
+      {
+        return object;
+      }
+    }
+    return std::nullopt;
+  }
+
+public:
+  [[nodiscard]] Type getType() const
+  {
+    return type_;
+  }
+
+  [[nodiscard]] const std::string& getSource() const
+  {
+    return source_;
+  }
+
+  [[nodiscard]] const std::string& getTarget() const
+  {
+    return target_;
+  }
+
+public:
+  /**
+   * Constructor
+   * @param type
+   * @param source
+   * @param target
+   */
+  Action(Type type, std::string source, std::string target)
+    : type_(type), source_(std::move(source)), target_(std::move(target))
+  {
+  }
+
+  /**
+   * an action can be performed in a scene if source and target exist in the scene and have the correct type for the
+   * action
+   * @param scene
+   * @return true if the action can be performed
+   */
+  bool canBePerformed(const Scene& scene)
+  {
+    auto s = lookupObject(scene, source_);
+    auto t = lookupObject(scene, target_);
+    return s && t && s->type() == requiredSourceType(type_) && t->type() == requiredTargetType(type_);
+  }
+
+  [[nodiscard]] bool isCompleted(const Scene& scene) const
+  {
+    switch (type_)
+    {
+      case Action::Type::PICK_DROP:
+        // pick and drop is completed if the dropped element no longer exists
+        return !lookupObject(scene, source_).has_value();
+      case Action::Type::UNDEFINED:
+        // undefined actions are defined to be completed
+      default:
+        return true;
+    }
+  }
+};
+
+#endif  // CCF_IMMERSIVE_SORTING_ACTION_H_
diff --git a/include/cell.h b/include/cell.h
new file mode 100644
index 0000000..7ac7677
--- /dev/null
+++ b/include/cell.h
@@ -0,0 +1,30 @@
+#ifndef CCF_IMMERSIVE_SORTING_CELL_H_
+#define CCF_IMMERSIVE_SORTING_CELL_H_
+
+#include <connector.pb.h>
+#include <optional>
+#include <string>
+
+struct Cell
+{
+  enum State
+  {
+    UNDEFINED,
+    DISCONNECTED,
+    IDLE,
+    WORKING
+  };
+
+  std::string id;
+  Scene scene;
+  State state = UNDEFINED;
+  std::optional<std::string> processed_object;
+
+  Cell() = default;
+
+  // one does not copy cells
+  Cell(const Cell&) = delete;
+  Cell& operator=(const Cell&) = delete;
+};
+
+#endif  // CCF_IMMERSIVE_SORTING_CELL_H_
diff --git a/include/cell_controller.h b/include/cell_controller.h
new file mode 100644
index 0000000..13c41bd
--- /dev/null
+++ b/include/cell_controller.h
@@ -0,0 +1,126 @@
+#ifndef CCF_IMMERSIVE_SORTING_CELL_CONTROLLER_H_
+#define CCF_IMMERSIVE_SORTING_CELL_CONTROLLER_H_
+
+#include <utility>
+
+#include "cell.h"
+#include "action.h"
+#include "task.h"
+
+#include "ros/ros.h"
+
+class CellController
+{
+  Cell cell_;
+  std::shared_ptr<Action> current_action_;
+  std::shared_ptr<Task> current_task_;
+
+public:
+  CellController()
+  {
+    cell_.state = Cell::DISCONNECTED;
+  }
+
+  void setCellId(const std::string& id)
+  {
+    cell_.id = id;
+  }
+
+  const Cell& cell() const
+  {
+    return cell_;
+  }
+
+  bool hasCurrentAction() const
+  {
+    return current_action_ == nullptr;
+  }
+
+  const Action& getCurrentAction() const
+  {
+    return *current_action_;
+  }
+
+  const std::shared_ptr<Task>& getCurrentTask() const
+  {
+    return current_task_;
+  }
+
+  void proceedWithTask()
+  {
+    if (cell_.state == Cell::IDLE)
+    {
+      if (currentActionCompleted())
+      {
+        auto action = triggerNextAction();
+      }
+    }
+  }
+
+  void updateModel(const std::string& scene_string)
+  {
+    ROS_INFO_STREAM("Getting a scene update for cell " << cell_.id);
+    if (cell_.state == Cell::DISCONNECTED)
+    {
+      cell_.state = Cell::IDLE;
+    }
+
+    cell_.scene.ParseFromString(scene_string);
+    proceedWithTask();
+  }
+
+  // TODO do we need this method?
+  void updateModel(const Scene& scene)
+  {
+    ROS_INFO_STREAM("Getting a scene update for cell " << cell_.id);
+    if (cell_.state == Cell::DISCONNECTED)
+    {
+      cell_.state = Cell::IDLE;
+    }
+
+    cell_.scene.CopyFrom(scene);
+    proceedWithTask();
+  }
+
+  /**
+   * A current action is completed if there is no current action or the current action can no longer be performed
+   * @return
+   */
+  bool currentActionCompleted()
+  {
+    if (current_action_ == nullptr)
+    {
+      return true;
+    }
+    else if (current_action_->isCompleted(cell_.scene))
+    {
+      // the current action is completed, but this was not registered before, so we have to set the cell state
+      cell_.state = Cell::IDLE;
+      current_action_ = nullptr;
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  std::optional<Action> triggerNextAction()
+  {
+    // TODO this assumes each action can be performed only once, which holds for pick-and-drop tasks but not in general
+    for (Action action : current_task_->GetActions())
+    {
+      if (!action.isCompleted(cell_.scene) && action.canBePerformed(cell_.scene))
+      {
+        cell_.state = Cell::WORKING;
+        // TODO send selections to controller
+        return action;
+      }
+    }
+    return std::nullopt;
+  }
+
+  friend class CellController;
+};
+
+#endif  // CCF_IMMERSIVE_SORTING_CELL_CONTROLLER_H_
diff --git a/include/task.h b/include/task.h
new file mode 100644
index 0000000..9ffcd42
--- /dev/null
+++ b/include/task.h
@@ -0,0 +1,23 @@
+#ifndef CCF_IMMERSIVE_SORTING_TASK_H_
+#define CCF_IMMERSIVE_SORTING_TASK_H_
+
+#include <vector>
+#include "action.h"
+
+class Task
+{
+  std::vector<Action> actions_;
+
+public:
+  void AddAction(const Action& action)
+  {
+    actions_.emplace_back(action);
+  }
+
+  [[nodiscard]] const std::vector<Action>& GetActions() const
+  {
+    return actions_;
+  }
+};
+
+#endif  // CCF_IMMERSIVE_SORTING_TASK_H_
diff --git a/src/action.cpp b/src/action.cpp
new file mode 100644
index 0000000..1eba944
--- /dev/null
+++ b/src/action.cpp
@@ -0,0 +1 @@
+#include "action.h"
diff --git a/src/cell.cpp b/src/cell.cpp
new file mode 100644
index 0000000..f5f0a4f
--- /dev/null
+++ b/src/cell.cpp
@@ -0,0 +1,2 @@
+#include "cell.h"
+
diff --git a/src/cell_controller.cpp b/src/cell_controller.cpp
new file mode 100644
index 0000000..b6b7799
--- /dev/null
+++ b/src/cell_controller.cpp
@@ -0,0 +1 @@
+#include "cell_controller.h"
diff --git a/src/main_controller.cpp b/src/main_controller.cpp
index 892fafd..eae8c3a 100644
--- a/src/main_controller.cpp
+++ b/src/main_controller.cpp
@@ -1,17 +1,9 @@
-/*! \file dummy_sorting_controller.cpp
-
-    A ROS node that simulates a node connected to a model-based cobotic application
-
-    \author Johannes Mey
-    \author Sebastian Ebert
-    \date 07.07.20
-*/
-
 #include <ros/ros.h>
 #include <ros/package.h>
 #include <std_msgs/Empty.h>
 
 #include <utility>
+#include <cell_controller.h>
 
 #include "connector.pb.h"
 
@@ -20,7 +12,8 @@
 #include "ccf/connection/MqttConnection.h"
 #include "ccf/util/NodeUtil.h"
 
-std::string NODE_NAME = "main_controller";
+const char* DEFAULT_NODE_NAME = "main_controller";
+std::string NODE_NAME;
 
 using CetiRosToolbox::getParameter;
 using CetiRosToolbox::getPrivateParameter;
@@ -29,341 +22,162 @@ const float SELECT = 2.0;
 const float UNSELECT = 0.5;
 const float DELETING = 0.08;
 
-
-
-struct Cell {
-  enum CELL_STATE {
-    UNDEFINED,
-    DISCONNECTED,
-    IDLE,
-    WORKING
-  };
-
-  Scene scene;
-  CELL_STATE state = UNDEFINED;
-  std::optional<std::string> processedObject;
-};
-
-class Action {
- public:
-  enum Type {
-    UNDEFINED,
-    PICK_DROP
-  };
-
- private:
-  const Type type;
-  const std::string source;
-  const std::string target;
-
- public:
-  bool operator==(const Action &rhs) const {
-      return type == rhs.type &&
-              source == rhs.source &&
-              target == rhs.target;
-  }
-  bool operator!=(const Action &rhs) const {
-      return !(rhs == *this);
-  }
- private:
-
-  static constexpr Object::Type requiredSourceType(Type type) {
-      switch (type) {
-          case UNDEFINED:return Object::UNKNOWN;
-          case PICK_DROP:return Object::BOX;
-      }
-  }
-  static constexpr Object::Type requiredTargetType(Type type) {
-      switch (type) {
-          case UNDEFINED:return Object::UNKNOWN;
-          case PICK_DROP:return Object::BIN;
-      }
-  }
-
-  std::optional<Object> lookupObject(const Scene &scene, const std::string &id) const {
-      for (Object object: scene.objects()) {
-          if (object.id() == id) {
-              return object;
-          }
-      }
-      return std::nullopt;
-  }
-
- public:
-  Type GetType() const {
-      return type;
-  }
-
-  const std::string &GetSource() const {
-      return source;
-  }
-
-  const std::string &GetTarget() const {
-      return target;
-  }
-
- public:
-  /**
-   * Constructor
-   * @param type
-   * @param source
-   * @param target
-   */
-  Action(Type type, std::string source, std::string target)
-          : type(type), source(std::move(source)), target(std::move(target)) {}
-
-  /**
-   * an action can be performed in a scene if source and target exist in the scene and have the correct type for the action
-   * @param scene
-   * @return true if the action can be performed
-   */
-  bool canBePerformed(const Scene &scene) {
-      auto s = lookupObject(scene, source);
-      auto t = lookupObject(scene, target);
-      return s && t && s->type() == requiredSourceType(type) && t->type() == requiredTargetType(type);
-  }
-
-  bool isCompleted(const Scene &scene) const {
-      switch (type) {
-          case Action::Type::PICK_DROP:
-              // pick and drop is completed if the dropped element no longer exists
-              return !lookupObject(scene, source).has_value();
-          case Action::Type::UNDEFINED:
-              // undefined actions are defined to be completed
-              return true;
-      }
-  }
-
-};
-
-class Task {
-
-  std::vector<Action> actions;
-
- public:
-
-  void addAction(const Action &action) {
-      actions.emplace_back(action);
-  }
-
-  const std::vector<Action> &GetActions() const {
-      return actions;
-  }
-
-};
-
-class CellController {
-  Cell cell;
-  std::shared_ptr<Action> currentAction;
-  std::shared_ptr<Task> currentTask;
-
- public:
-  const Cell &GetCell() const {
-      return cell;
-  }
-  void SetCell(const Cell &cell) {
-      CellController::cell = cell;
-  }
-
-  bool HasCurrentAction() const {
-      return currentAction == nullptr;
-  }
-
-  const Action &GetCurrentAction() const {
-      return *currentAction;
-  }
-  void SetCurrentAction(Action current_action) {
-      currentAction.reset(&current_action);
-  }
-  const std::shared_ptr<Task> &GetCurrentTask() const {
-      return currentTask;
-  }
-  void SetCurrentTask(const std::shared_ptr<Task> &current_task) {
-      currentTask = current_task;
+void highlight(Object* selectedBin, float factor)
+{
+  if (selectedBin)
+  {
+    selectedBin->mutable_color()->set_r(selectedBin->color().r() * factor);
+    selectedBin->mutable_color()->set_g(selectedBin->color().g() * factor);
+    selectedBin->mutable_color()->set_b(selectedBin->color().b() * factor);
   }
+}
 
-  void proceedWithTask() {
-      if (cell.state == Cell::IDLE) {
+int main(int argc, char** argv)
+{
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  ros::init(argc, argv, DEFAULT_NODE_NAME);
+  NODE_NAME = ros::this_node::getName();
+
+  ros::NodeHandle n("connector_node_ros_ccf");  // namespace where the connection_address is
+
+  auto connectionAddress = getPrivateParameter<std::string>("connection_address", "tcp://*:6576");
+
+  DummyRobotArmController connector{ n, NODE_NAME };
+
+  std::map<std::string, CellController> clients;
+  std::vector<std::pair<std::string, std::string>> actions;
+
+  // add an NNG connection
+  std::unique_ptr<NngConnection> connection = std::make_unique<NngConnection>(connectionAddress);
+  connection->setReceiveTopic(getParameter<std::string>(n, "topics/selection", "selection"));
+  connection->setSendTopic(getParameter(n, "topics/scene", NODE_NAME + "/scene/update"));
+  connector.addConnection(std::move(connection));
+
+  auto clientControllers = getPrivateParameter<std::vector<std::string>>("client_controllers");
+  auto mqttServer = getPrivateParameter<std::string>("mqtt_server", "localhost:1883");
+
+  ROS_INFO_STREAM("Connecting to " << clientControllers.size() << " client controllers.");
+  std::shared_ptr<MqttConnection> client_connection = std::make_shared<MqttConnection>(NODE_NAME, mqttServer);
+  connector.addConnection(client_connection);
+  for (const auto& client : clientControllers)
+  {
+    ROS_INFO_STREAM("Connecting to client at " << client << ".");
+    clients[client].setCellId(client);
+    auto scene_update_topic = "/" + client + getParameter(n, "topics/scene", NODE_NAME + "/scene/update");
+    client_connection->listen(scene_update_topic);
+    connector.addCallback(scene_update_topic, [client, &clients](auto msg) { clients[client].updateModel(msg); });
+  }
+  auto fallback_path = ros::package::getPath("ccf_immersive_sorting") + "/config/config_scene_virtual-table.json";
+  connector.loadScene(getPrivateParameter<std::string>("scene", fallback_path));
+
+  Object* robot = nullptr;
+  Object* selected_box = nullptr;
+  Object* selectedBin = nullptr;
+  std::optional<std::string> currentlyPickedBox{};
+
+  ros::Subscriber sub = n.subscribe<std_msgs::Empty>(
+      "send_scene", 1000, [&connector](const std_msgs::EmptyConstPtr& msg) { connector.sendScene(); });
+  ros::Timer timer = n.createTimer(ros::Duration(10), [&connector](const ros::TimerEvent& event) {
+    connector.sendScene();
+  });  // send a scene every ten seconds
+
+  //
+  ros::Timer timer_scene_log = n.createTimer(ros::Duration(10), [&clients](const ros::TimerEvent& event) {
+    for (const auto& [name, cell_controller] : clients)
+    {
+      ROS_WARN_STREAM("CLIENT " << name << " has a cell_controller with " << cell_controller.cell().scene.objects_size()
+                                << "objects");
+    }
+  });
+
+  auto selectionMessageCallback = [&currentlyPickedBox, &connector, &robot, &selected_box,
+                                   &selectedBin](const Selection& selection) {
+    if (currentlyPickedBox.has_value())
+    {
+      ROS_WARN_STREAM("Unable to accept selections while picking is in progress.");
+      return;
+    }
 
+    Object* object = connector.resolveObject(selection.id());
+    if (!object)
+    {
+      ROS_ERROR_STREAM("Selected unknown object '" << selection.id() << "'");
+      return;
+    }
+    auto type = Object::Type_Name(object->type());
+    if (object->type() == Object::BOX)
+    {
+      highlight(selected_box, UNSELECT);
+      if (selected_box == object)
+      {
+        selected_box = nullptr;
       }
-  }
-
-  void updateModel(Scene scene) {
-
-      if (cell.state == Cell::DISCONNECTED) {
-          cell.state == Cell::IDLE;
+      else
+      {
+        selected_box = object;
+        highlight(selected_box, SELECT);
       }
-
-      cell.scene = std::move(scene);
-      if (currentActionCompleted()) {
-          auto action = triggerNextAction();
-          // TODO send selections to controller
+      connector.sendScene();
+      ROS_INFO_STREAM("Selected " << type << " '" << selection.id() << "'");
+    }
+    else if (object->type() == Object::BIN)
+    {
+      highlight(selectedBin, UNSELECT);
+      if (selectedBin == object)
+      {
+        selectedBin = nullptr;
       }
-  }
-
-  /**
-   * A current action is completed if there is no current action or the current action can no longer be performed
-   * @return
-   */
-  bool currentActionCompleted() {
-      return (currentAction == nullptr) || !currentAction->isCompleted(cell.scene);
-  }
-
-
-
-  std::optional<Action> triggerNextAction() {
-      for (Action action : currentTask->GetActions()) {
-          if (!action.isCompleted(cell.scene) && action.canBePerformed(cell.scene)) {
-              return action;
-          }
+      else
+      {
+        selectedBin = object;
+        highlight(selectedBin, SELECT);
       }
-      return std::nullopt;
-  }
-
-};
-
-void highlight(Object *selectedBin, float factor) {
-    if (selectedBin) {
-        selectedBin->mutable_color()->set_r(selectedBin->color().r() * factor);
-        selectedBin->mutable_color()->set_g(selectedBin->color().g() * factor);
-        selectedBin->mutable_color()->set_b(selectedBin->color().b() * factor);
+      connector.sendScene();
+      ROS_INFO_STREAM("Selected " << type << " '" << selection.id() << "'");
     }
-}
-
-int main(int argc, char **argv) {
-
-    GOOGLE_PROTOBUF_VERIFY_VERSION;
-
-    ros::init(argc, argv, NODE_NAME);
-    NODE_NAME = ros::this_node::getName();
-
-    ros::NodeHandle n("connector_node_ros_ccf"); // namespace where the connection_address is
-
-    auto connectionAddress = getPrivateParameter<std::string>("connection_address", "tcp://*:6576");
-
-    DummyRobotArmController connector{n, NODE_NAME};
-
-    std::map<std::string, Cell> clients;
-    std::vector<std::pair<std::string, std::string>> actions;
-
-    // add an NNG connection
-    std::unique_ptr<NngConnection> connection = std::make_unique<NngConnection>(connectionAddress);
-    connection->setReceiveTopic(getParameter<std::string>(n, "topics/selection", "selection"));
-    connection->setSendTopic(getParameter(n, "topics/scene", NODE_NAME + "/scene/update"));
-    connector.addConnection(std::move(connection));
-
-    auto clientControllers = getPrivateParameter<std::vector<std::string>>("client_controllers");
-    auto mqttServer = getPrivateParameter<std::string>("mqtt_server", "localhost:1883");
-
-    ROS_INFO_STREAM("Connecting to " << clientControllers.size() << " client controllers.");
-    std::shared_ptr<MqttConnection> client_connection = std::make_shared<MqttConnection>(NODE_NAME, mqttServer);
-    connector.addConnection(client_connection);
-    for (const auto &client: clientControllers) {
-        ROS_INFO_STREAM("Connecting to client at " << client << ".");
-        auto scene_update_topic = "/" + client + getParameter(n, "topics/scene", NODE_NAME + "/scene/update");
-        client_connection->listen(scene_update_topic);
-        connector.addCallback(scene_update_topic,
-                              [client, &clients](auto msg) { clients[client].scene.ParseFromString(msg); });
+    else
+    {
+      ROS_WARN_STREAM("Selected unsupported " << type << " '" << selection.id() << "'");
     }
 
-    connector.loadScene(getPrivateParameter<std::string>("scene", ros::package::getPath("ccf_immersive_sorting") +
-            "/config/config_scene_virtual-table.json"));
-
-    Object *robot = nullptr;
-    Object *selectedBox = nullptr;
-    Object *selectedBin = nullptr;
-    std::optional<std::string> currentlyPickedBox{};
-
-    ros::Subscriber sub = n.subscribe<std_msgs::Empty>("send_scene", 1000, [&connector](
-            const std_msgs::EmptyConstPtr &msg) { connector.sendScene(); });
-    ros::Timer timer = n.createTimer(ros::Duration(10), [&connector](
-            const ros::TimerEvent &event) { connector.sendScene(); }); // send a scene every ten seconds
-
-
-    ros::Timer timer_scene_log = n.createTimer(ros::Duration(10), [&clients](
-            const ros::TimerEvent &event) {
-
-      for (const auto &[name, scene]: clients) {
-          ROS_WARN_STREAM("CLIENT " << name << " has a scene with " << scene.objects_size() << "objects");
+    if (selectedBin && selected_box)
+    {
+      auto boxId = selected_box->id();
+      ROS_INFO_STREAM("Got task: Put " << boxId << " into " << selectedBin->id());
+      currentlyPickedBox = boxId;
+      highlight(selectedBin, UNSELECT);
+      highlight(selected_box, DELETING);
+      connector.sendScene();
+      if (!connector.pickAndDrop(*robot, *selected_box, *selectedBin, false))
+      {
+        ROS_WARN_STREAM("Unable to remove box '" << boxId << "'!");
       }
-
-    });
-
-    auto selectionMessageCallback = [&currentlyPickedBox, &connector, &robot, &selectedBox, &selectedBin, &n](
-            const Selection &selection) {
-
-      // forward the selection to the clients
-      connector.sendToAll(getParameter<std::string>(n, "topics/selection", "selection"),
-                          selection.SerializeAsString());
-
-      if (currentlyPickedBox.has_value()) {
-          ROS_WARN_STREAM("Unable to accept selections while picking is in progress.");
-          return;
-      }
-
-      Object *object = connector.resolveObject(selection.id());
-      if (!object) {
-          ROS_ERROR_STREAM("Selected unknown object '" << selection.id() << "'");
-          return;
-      }
-      auto type = Object::Type_Name(object->type());
-      if (object->type() == Object::BOX) {
-          highlight(selectedBox, UNSELECT);
-          if (selectedBox == object) {
-              selectedBox = nullptr;
-          } else {
-              selectedBox = object;
-              highlight(selectedBox, SELECT);
-          }
-          connector.sendScene();
-          ROS_INFO_STREAM("Selected " << type << " '" << selection.id() << "'");
-      } else if (object->type() == Object::BIN) {
-          highlight(selectedBin, UNSELECT);
-          if (selectedBin == object) {
-              selectedBin = nullptr;
-          } else {
-              selectedBin = object;
-              highlight(selectedBin, SELECT);
-          }
-          connector.sendScene();
-          ROS_INFO_STREAM("Selected " << type << " '" << selection.id() << "'");
-      } else {
-          ROS_WARN_STREAM("Selected unsupported " << type << " '" << selection.id() << "'");
-      }
-
-      if (selectedBin && selectedBox) {
-          auto boxId = selectedBox->id();
-          ROS_INFO_STREAM("Got task: Put " << boxId << " into " << selectedBin->id());
-          currentlyPickedBox = boxId;
-          highlight(selectedBin, UNSELECT);
-          highlight(selectedBox, DELETING);
-          connector.sendScene();
-          if (!connector.pickAndDrop(*robot, *selectedBox, *selectedBin, false)) {
-              ROS_WARN_STREAM("Unable to remove box '" << boxId << "'!");
-          } else {
-              ROS_INFO_STREAM("Job is done! '" << boxId << "' is no more.");
-          }
-          selectedBox = nullptr;
-          selectedBin = nullptr;
-          connector.sendScene();
-      } else {
-
+      else
+      {
+        ROS_INFO_STREAM("Job is done! '" << boxId << "' is no more.");
       }
-    };
-    connector.reactToSelectionMessage(selectionMessageCallback);
-
-    auto sceneUpdateCallback = [&currentlyPickedBox, &connector]() {
-      if (currentlyPickedBox.has_value()) {
-          auto resolved = connector.resolveObject(currentlyPickedBox.value());
-          if (!resolved) {
-              ROS_INFO_STREAM("box " << currentlyPickedBox.value() << " has been removed from the scene!");
-              currentlyPickedBox.reset();
-          }
+      selected_box = nullptr;
+      selectedBin = nullptr;
+      connector.sendScene();
+    }
+  };
+  connector.reactToSelectionMessage(selectionMessageCallback);
+
+  auto sceneUpdateCallback = [&currentlyPickedBox, &connector]() {
+    if (currentlyPickedBox.has_value())
+    {
+      auto resolved = connector.resolveObject(currentlyPickedBox.value());
+      if (!resolved)
+      {
+        ROS_INFO_STREAM("box " << currentlyPickedBox.value() << " has been removed from the scene!");
+        currentlyPickedBox.reset();
       }
-    };
-    connector.reactToSceneUpdateMessage(sceneUpdateCallback);
+    }
+  };
+  connector.reactToSceneUpdateMessage(sceneUpdateCallback);
 
-    ros::spin();
+  ros::spin();
 
-    return 0;
+  return 0;
 }
\ No newline at end of file
diff --git a/src/task.cpp b/src/task.cpp
new file mode 100644
index 0000000..204c27c
--- /dev/null
+++ b/src/task.cpp
@@ -0,0 +1 @@
+#include "task.h"
-- 
GitLab