From 0b5b48ede4651e21c58d337dec9466d5ea2387bb Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Thu, 6 May 2021 16:24:55 +0200
Subject: [PATCH] integrate protobuf messages, add auto-scroll log.

---
 README.md                             |   9 +
 cgv_connector.proto                   |  75 ++++
 cgv_connector_pb2.py                  | 601 ++++++++++++++++++++++++++
 config/config-scene-a.json            |  20 +
 config/config-scene-b.json            |  21 +
 config/dummy-reachability-b-arm1.json |  11 +
 config/dummy-reachability-b-arm2.json |  11 +
 main.py                               | 235 ++++++++--
 requirements.txt                      |   2 +
 9 files changed, 956 insertions(+), 29 deletions(-)
 create mode 100644 README.md
 create mode 100644 cgv_connector.proto
 create mode 100644 cgv_connector_pb2.py
 create mode 100644 config/config-scene-a.json
 create mode 100644 config/config-scene-b.json
 create mode 100644 config/dummy-reachability-b-arm1.json
 create mode 100644 config/dummy-reachability-b-arm2.json

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6e8e463
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+# Dashboard for ros3rag use case
+
+[Java impl repo](https://git-st.inf.tu-dresden.de/jastadd/ros3rag) | [Paper repo](https://git-st.inf.tu-dresden.de/stgroup/publications/2021/ragconnect-followup) | [Paper issue](https://git-st.inf.tu-dresden.de/stgroup/publications/kanban/2021/-/issues/3)
+
+## Running
+
+- Install requirements, best using virtual environment
+- Run `python main.py`
+- Open <http://127.0.0.1:8050/>
diff --git a/cgv_connector.proto b/cgv_connector.proto
new file mode 100644
index 0000000..be68b97
--- /dev/null
+++ b/cgv_connector.proto
@@ -0,0 +1,75 @@
+// cgv_connector.proto
+// this file contains the messages that are exchanged between the cgv framework and the st ROS interface
+
+syntax = "proto3";
+
+option java_package = "de.tudresden.inf.st.ceti";
+option java_multiple_files = true;
+
+message Object {
+
+    // Position is object-center related
+    message Position {
+        float x = 1; // in m
+        float y = 2; // in m
+        float z = 3; // height in m
+    }
+
+    // 3D description of the object
+    message Size {
+        float length = 1; // in m
+        float width = 2;  // in m
+        float height = 3; // in m
+    }
+    message Orientation {
+        float x = 1; // normalized quaternion
+        float y = 2;
+        float z = 3;
+        float w = 4;
+    }
+    message Color {
+        float r = 1; // 0..1
+        float g = 2; // 0..1
+        float b = 3; // 0..1
+    }
+    enum Type {
+        UNKNOWN = 0;
+        BOX = 1;
+        BIN = 2;
+        ARM = 3;
+    }
+
+    string id = 1;
+    Type type = 2;
+    Position pos = 3;
+    Size size = 4;
+    Orientation orientation = 5;
+    Color color = 6;
+}
+
+// the scene is stored within the ROS side and sent to the CGV framework
+message Scene {
+    repeated Object objects = 1;
+}
+
+// the selection is done by the CGV framework and sent to ROS
+message Selection {
+    string id = 1; // the id corresponds to an id of an Object in a Scene
+}
+
+// vvv from rs vvv.
+// Merged message to contain both pick and place in one
+message MergedSelection {
+    string idRobot = 1; // the id corresponds to and id of the robot Object that should execute this operation
+    string idPick = 2; // the id corresponds to an id of the Object in a Scene to be picked
+    string idPlace = 3; // the id corresponds to an id of the Object in a Scene where the picked object shall be placed
+}
+// Reachability of objects, as reported by MoveIt
+message Reachability {
+    message ObjectReachability {
+        string idObject = 1; // the id of the object to reach
+        bool reachable = 2; // whether the object can be reached
+    }
+    string idRobot = 1; // the id of the robot arm
+    repeated ObjectReachability objects = 2; // all objects reachable
+}
diff --git a/cgv_connector_pb2.py b/cgv_connector_pb2.py
new file mode 100644
index 0000000..9323422
--- /dev/null
+++ b/cgv_connector_pb2.py
@@ -0,0 +1,601 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: cgv_connector.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='cgv_connector.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=b'\n\030de.tudresden.inf.st.cetiP\001',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x13\x63gv_connector.proto\"\xac\x03\n\x06Object\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1a\n\x04type\x18\x02 \x01(\x0e\x32\x0c.Object.Type\x12\x1d\n\x03pos\x18\x03 \x01(\x0b\x32\x10.Object.Position\x12\x1a\n\x04size\x18\x04 \x01(\x0b\x32\x0c.Object.Size\x12(\n\x0borientation\x18\x05 \x01(\x0b\x32\x13.Object.Orientation\x12\x1c\n\x05\x63olor\x18\x06 \x01(\x0b\x32\r.Object.Color\x1a+\n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x1a\x35\n\x04Size\x12\x0e\n\x06length\x18\x01 \x01(\x02\x12\r\n\x05width\x18\x02 \x01(\x02\x12\x0e\n\x06height\x18\x03 \x01(\x02\x1a\x39\n\x0bOrientation\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\t\n\x01w\x18\x04 \x01(\x02\x1a(\n\x05\x43olor\x12\t\n\x01r\x18\x01 \x01(\x02\x12\t\n\x01g\x18\x02 \x01(\x02\x12\t\n\x01\x62\x18\x03 \x01(\x02\".\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03\x42OX\x10\x01\x12\x07\n\x03\x42IN\x10\x02\x12\x07\n\x03\x41RM\x10\x03\"!\n\x05Scene\x12\x18\n\x07objects\x18\x01 \x03(\x0b\x32\x07.Object\"\x17\n\tSelection\x12\n\n\x02id\x18\x01 \x01(\t\"C\n\x0fMergedSelection\x12\x0f\n\x07idRobot\x18\x01 \x01(\t\x12\x0e\n\x06idPick\x18\x02 \x01(\t\x12\x0f\n\x07idPlace\x18\x03 \x01(\t\"\x8d\x01\n\x0cReachability\x12\x0f\n\x07idRobot\x18\x01 \x01(\t\x12\x31\n\x07objects\x18\x02 \x03(\x0b\x32 .Reachability.ObjectReachability\x1a\x39\n\x12ObjectReachability\x12\x10\n\x08idObject\x18\x01 \x01(\t\x12\x11\n\treachable\x18\x02 \x01(\x08\x42\x1c\n\x18\x64\x65.tudresden.inf.st.cetiP\x01\x62\x06proto3'
+)
+
+
+
+_OBJECT_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='Object.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BOX', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BIN', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ARM', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=406,
+  serialized_end=452,
+)
+_sym_db.RegisterEnumDescriptor(_OBJECT_TYPE)
+
+
+_OBJECT_POSITION = _descriptor.Descriptor(
+  name='Position',
+  full_name='Object.Position',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='x', full_name='Object.Position.x', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='y', full_name='Object.Position.y', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='z', full_name='Object.Position.z', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=205,
+  serialized_end=248,
+)
+
+_OBJECT_SIZE = _descriptor.Descriptor(
+  name='Size',
+  full_name='Object.Size',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='length', full_name='Object.Size.length', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='width', full_name='Object.Size.width', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='height', full_name='Object.Size.height', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=250,
+  serialized_end=303,
+)
+
+_OBJECT_ORIENTATION = _descriptor.Descriptor(
+  name='Orientation',
+  full_name='Object.Orientation',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='x', full_name='Object.Orientation.x', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='y', full_name='Object.Orientation.y', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='z', full_name='Object.Orientation.z', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='w', full_name='Object.Orientation.w', index=3,
+      number=4, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=305,
+  serialized_end=362,
+)
+
+_OBJECT_COLOR = _descriptor.Descriptor(
+  name='Color',
+  full_name='Object.Color',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='r', full_name='Object.Color.r', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='g', full_name='Object.Color.g', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='b', full_name='Object.Color.b', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=364,
+  serialized_end=404,
+)
+
+_OBJECT = _descriptor.Descriptor(
+  name='Object',
+  full_name='Object',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='Object.id', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='Object.type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pos', full_name='Object.pos', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='size', full_name='Object.size', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='orientation', full_name='Object.orientation', index=4,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='color', full_name='Object.color', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_OBJECT_POSITION, _OBJECT_SIZE, _OBJECT_ORIENTATION, _OBJECT_COLOR, ],
+  enum_types=[
+    _OBJECT_TYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=24,
+  serialized_end=452,
+)
+
+
+_SCENE = _descriptor.Descriptor(
+  name='Scene',
+  full_name='Scene',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='objects', full_name='Scene.objects', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=454,
+  serialized_end=487,
+)
+
+
+_SELECTION = _descriptor.Descriptor(
+  name='Selection',
+  full_name='Selection',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='Selection.id', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=489,
+  serialized_end=512,
+)
+
+
+_MERGEDSELECTION = _descriptor.Descriptor(
+  name='MergedSelection',
+  full_name='MergedSelection',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='idRobot', full_name='MergedSelection.idRobot', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='idPick', full_name='MergedSelection.idPick', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='idPlace', full_name='MergedSelection.idPlace', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=514,
+  serialized_end=581,
+)
+
+
+_REACHABILITY_OBJECTREACHABILITY = _descriptor.Descriptor(
+  name='ObjectReachability',
+  full_name='Reachability.ObjectReachability',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='idObject', full_name='Reachability.ObjectReachability.idObject', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='reachable', full_name='Reachability.ObjectReachability.reachable', index=1,
+      number=2, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=668,
+  serialized_end=725,
+)
+
+_REACHABILITY = _descriptor.Descriptor(
+  name='Reachability',
+  full_name='Reachability',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='idRobot', full_name='Reachability.idRobot', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='objects', full_name='Reachability.objects', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_REACHABILITY_OBJECTREACHABILITY, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=584,
+  serialized_end=725,
+)
+
+_OBJECT_POSITION.containing_type = _OBJECT
+_OBJECT_SIZE.containing_type = _OBJECT
+_OBJECT_ORIENTATION.containing_type = _OBJECT
+_OBJECT_COLOR.containing_type = _OBJECT
+_OBJECT.fields_by_name['type'].enum_type = _OBJECT_TYPE
+_OBJECT.fields_by_name['pos'].message_type = _OBJECT_POSITION
+_OBJECT.fields_by_name['size'].message_type = _OBJECT_SIZE
+_OBJECT.fields_by_name['orientation'].message_type = _OBJECT_ORIENTATION
+_OBJECT.fields_by_name['color'].message_type = _OBJECT_COLOR
+_OBJECT_TYPE.containing_type = _OBJECT
+_SCENE.fields_by_name['objects'].message_type = _OBJECT
+_REACHABILITY_OBJECTREACHABILITY.containing_type = _REACHABILITY
+_REACHABILITY.fields_by_name['objects'].message_type = _REACHABILITY_OBJECTREACHABILITY
+DESCRIPTOR.message_types_by_name['Object'] = _OBJECT
+DESCRIPTOR.message_types_by_name['Scene'] = _SCENE
+DESCRIPTOR.message_types_by_name['Selection'] = _SELECTION
+DESCRIPTOR.message_types_by_name['MergedSelection'] = _MERGEDSELECTION
+DESCRIPTOR.message_types_by_name['Reachability'] = _REACHABILITY
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Object = _reflection.GeneratedProtocolMessageType('Object', (_message.Message,), {
+
+  'Position' : _reflection.GeneratedProtocolMessageType('Position', (_message.Message,), {
+    'DESCRIPTOR' : _OBJECT_POSITION,
+    '__module__' : 'cgv_connector_pb2'
+    # @@protoc_insertion_point(class_scope:Object.Position)
+    })
+  ,
+
+  'Size' : _reflection.GeneratedProtocolMessageType('Size', (_message.Message,), {
+    'DESCRIPTOR' : _OBJECT_SIZE,
+    '__module__' : 'cgv_connector_pb2'
+    # @@protoc_insertion_point(class_scope:Object.Size)
+    })
+  ,
+
+  'Orientation' : _reflection.GeneratedProtocolMessageType('Orientation', (_message.Message,), {
+    'DESCRIPTOR' : _OBJECT_ORIENTATION,
+    '__module__' : 'cgv_connector_pb2'
+    # @@protoc_insertion_point(class_scope:Object.Orientation)
+    })
+  ,
+
+  'Color' : _reflection.GeneratedProtocolMessageType('Color', (_message.Message,), {
+    'DESCRIPTOR' : _OBJECT_COLOR,
+    '__module__' : 'cgv_connector_pb2'
+    # @@protoc_insertion_point(class_scope:Object.Color)
+    })
+  ,
+  'DESCRIPTOR' : _OBJECT,
+  '__module__' : 'cgv_connector_pb2'
+  # @@protoc_insertion_point(class_scope:Object)
+  })
+_sym_db.RegisterMessage(Object)
+_sym_db.RegisterMessage(Object.Position)
+_sym_db.RegisterMessage(Object.Size)
+_sym_db.RegisterMessage(Object.Orientation)
+_sym_db.RegisterMessage(Object.Color)
+
+Scene = _reflection.GeneratedProtocolMessageType('Scene', (_message.Message,), {
+  'DESCRIPTOR' : _SCENE,
+  '__module__' : 'cgv_connector_pb2'
+  # @@protoc_insertion_point(class_scope:Scene)
+  })
+_sym_db.RegisterMessage(Scene)
+
+Selection = _reflection.GeneratedProtocolMessageType('Selection', (_message.Message,), {
+  'DESCRIPTOR' : _SELECTION,
+  '__module__' : 'cgv_connector_pb2'
+  # @@protoc_insertion_point(class_scope:Selection)
+  })
+_sym_db.RegisterMessage(Selection)
+
+MergedSelection = _reflection.GeneratedProtocolMessageType('MergedSelection', (_message.Message,), {
+  'DESCRIPTOR' : _MERGEDSELECTION,
+  '__module__' : 'cgv_connector_pb2'
+  # @@protoc_insertion_point(class_scope:MergedSelection)
+  })
+_sym_db.RegisterMessage(MergedSelection)
+
+Reachability = _reflection.GeneratedProtocolMessageType('Reachability', (_message.Message,), {
+
+  'ObjectReachability' : _reflection.GeneratedProtocolMessageType('ObjectReachability', (_message.Message,), {
+    'DESCRIPTOR' : _REACHABILITY_OBJECTREACHABILITY,
+    '__module__' : 'cgv_connector_pb2'
+    # @@protoc_insertion_point(class_scope:Reachability.ObjectReachability)
+    })
+  ,
+  'DESCRIPTOR' : _REACHABILITY,
+  '__module__' : 'cgv_connector_pb2'
+  # @@protoc_insertion_point(class_scope:Reachability)
+  })
+_sym_db.RegisterMessage(Reachability)
+_sym_db.RegisterMessage(Reachability.ObjectReachability)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/config/config-scene-a.json b/config/config-scene-a.json
new file mode 100644
index 0000000..9f09d08
--- /dev/null
+++ b/config/config-scene-a.json
@@ -0,0 +1,20 @@
+{ "objects": [
+  { "id": "tablePillar1","pos": { "x": 0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar2","pos": { "x": -0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar3","pos": { "x": -0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar4","pos": { "x": 0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "table","pos": { "z": 0.7 },"size": { "length": 1.6,"width": 1.6,"height": 0.1 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "binBlue","type": "BIN","pos": { "x": -0.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } },
+  { "id": "binRed","type": "BIN","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } },
+  { "id": "binGreen","type": "BIN","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } },
+  { "id": "objectRed1","type": "BOX","pos": { "x": 0.5,"y": -0.1,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } },
+  { "id": "objectRed2","type": "BOX","pos": { "x": 0.25,"y": -0.2,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "w": 1 },"color": { "r": 1 } },
+  { "id": "objectRed3","type": "BOX","pos": { "x": -0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } },
+  { "id": "objectGreen1","type": "BOX","pos": { "x": 0.45,"y": -0.3,"z": 0.8105 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 1,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectGreen3","type": "BOX","pos": { "x": 0.1,"y": -0.3,"z": 0.819 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectBlue1","type": "BOX","pos": { "x": 0.25,"y": -0.4,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "objectBlue2","type": "BOX","pos": { "x": -0.3,"y": -0.3,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "objectBlue3","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "arm","type": "ARM","pos": { "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } }
+] }
diff --git a/config/config-scene-b.json b/config/config-scene-b.json
new file mode 100644
index 0000000..f14e17f
--- /dev/null
+++ b/config/config-scene-b.json
@@ -0,0 +1,21 @@
+{ "objects": [
+  { "id": "tablePillar1","pos": { "x": 0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar2","pos": { "x": -0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar3","pos": { "x": -0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "tablePillar4","pos": { "x": 0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "table","pos": { "z": 0.7 },"size": { "length": 1.6,"width": 1.6,"height": 0.1 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } },
+  { "id": "binBlue","type": "BIN","pos": { "x": -0.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } },
+  { "id": "binRed","type": "BIN","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } },
+  { "id": "binGreen","type": "BIN","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } },
+  { "id": "objectRed1","type": "BOX","pos": { "x": 0.5,"y": -0.1,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } },
+  { "id": "objectRed2","type": "BOX","pos": { "x": 0.25,"y": -0.2,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "w": 1 },"color": { "r": 1 } },
+  { "id": "objectRed3","type": "BOX","pos": { "x": -0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } },
+  { "id": "objectGreen1","type": "BOX","pos": { "x": 0.45,"y": -0.3,"z": 0.8105 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 1,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectGreen3","type": "BOX","pos": { "x": 0.1,"y": -0.3,"z": 0.819 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } },
+  { "id": "objectBlue1","type": "BOX","pos": { "x": 0.25,"y": -0.4,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "objectBlue2","type": "BOX","pos": { "x": -0.3,"y": -0.3,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "objectBlue3","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } },
+  { "id": "arm1","type": "ARM","pos": { "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } },
+  { "id": "arm2","type": "ARM","pos": { "x": 1, "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } }
+] }
diff --git a/config/dummy-reachability-b-arm1.json b/config/dummy-reachability-b-arm1.json
new file mode 100644
index 0000000..d238077
--- /dev/null
+++ b/config/dummy-reachability-b-arm1.json
@@ -0,0 +1,11 @@
+{
+  "idRobot": "ARM1",
+  "objects": [
+    { "idObject": "objectRed1", "reachable": true },
+    { "idObject": "objectRed2", "reachable": true },
+    { "idObject": "objectRed3", "reachable": false },
+    { "idObject": "binRed", "reachable": true },
+    { "idObject": "binBlue", "reachable": true },
+    { "idObject": "binGreen", "reachable": false }
+  ]
+}
diff --git a/config/dummy-reachability-b-arm2.json b/config/dummy-reachability-b-arm2.json
new file mode 100644
index 0000000..c058487
--- /dev/null
+++ b/config/dummy-reachability-b-arm2.json
@@ -0,0 +1,11 @@
+{
+  "idRobot": "ARM2",
+  "objects": [
+    { "idObject": "objectRed1", "reachable": false },
+    { "idObject": "objectRed2", "reachable": false },
+    { "idObject": "objectRed3", "reachable": false },
+    { "idObject": "binRed", "reachable": false },
+    { "idObject": "binBlue", "reachable": true },
+    { "idObject": "binGreen", "reachable": true }
+  ]
+}
diff --git a/main.py b/main.py
index 4bcbf54..bbedf27 100644
--- a/main.py
+++ b/main.py
@@ -8,8 +8,11 @@ import dash
 import dash_core_components as dcc
 import dash_html_components as html
 import paho.mqtt.client as mqtt
+import visdcc
 from dash.dependencies import Input, Output, State
 
+from google.protobuf import json_format
+import cgv_connector_pb2
 import utils
 
 external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
@@ -39,34 +42,77 @@ commands = {
     'send-place-b-demo-objRed-red': ('place-b/demo/move/objectRed1/red', '1'),
 }
 
+# button-id: (topic, textarea-content-id, protobuf-object)
+complex_commands = {
+    'send-place-a-update':
+        ('place-a/scene/update', 'place-a-input-json', cgv_connector_pb2.Scene()),
+    'send-place-b-update':
+        ('place-b/scene/update', 'place-b-input-json', cgv_connector_pb2.Scene()),
+    'send-place-b-reachability-1-update':
+        ('place-b/reachability/arm1', 'place-b-reachability-1-json', cgv_connector_pb2.Reachability()),
+    'send-place-b-reachability-2-update':
+        ('place-b/reachability/arm2', 'place-b-reachability-2-json', cgv_connector_pb2.Reachability()),
+}
+
+bytes_topics = [
+    'place-a/scene/update',
+    'place-b/scene/update',
+    'place-b/reachability/arm1',
+    'place-b/reachability/arm2',
+    'place-b/commands',
+]
+
 button_style_normal = {"marginRight": "15px"}
 button_style_exit = {**button_style_normal, "backgroundColor": "red", "color": "white"}
+textarea_style_normal = {'width': '100%', 'height': '200px', 'resize': 'vertical'}
 
 app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
 app.layout = html.Div([
-    html.Div([
-        html.Div([
+    html.Div([  # First Row
+        html.Div([  # Column for place a
             dcc.Textarea(
                 id='place-a-input-json',
                 placeholder='place-a-input-json',
                 value='{}',
-                style={'width': '100%'}
+                style=textarea_style_normal
             ),
-            html.Button('Send to place-a/scene/update', id='send-place-a-update', disabled=True),
+            html.Button('Send to place-a/scene/update', id='send-place-a-update'),
+            html.Div(id='hidden-div-scene-a', style={'display': 'none'})
         ], className="six columns"),
-        html.Div([
+        html.Div([  # Column for place b
             dcc.Textarea(
                 id='place-b-input-json',
                 placeholder='place-b-input-json',
                 value='{}',
-                style={'width': '100%'}
+                style=textarea_style_normal
             ),
-            html.Button('Send to place-b/scene/update', id='send-place-b-update', disabled=True),
+            html.Button('Send to place-b/scene/update', id='send-place-b-update'),
+            html.Div(id='hidden-div-scene-b', style={'display': 'none'}),
+            html.Div([  # Row for reachability
+                html.Div([  # Column for reachability arm 1
+                    dcc.Textarea(
+                        id='place-b-reachability-1-json',
+                        placeholder='place-b-reachability-1-json',
+                        value='{}',
+                        style=textarea_style_normal
+                    ),
+                    html.Button('Send to place-b/reachability/arm1', id='send-place-b-reachability-1-update'),
+                ], className="six columns"),
+                html.Div([  # Column for reachability arm 2
+                    dcc.Textarea(
+                        id='place-b-reachability-2-json',
+                        placeholder='place-b-reachability-2-json',
+                        value='{}',
+                        style=textarea_style_normal
+                    ),
+                    html.Button('Send to place-b/reachability/arm2', id='send-place-b-reachability-2-update'),
+                ], className="six columns"),
+            ], className='row', style={'marginTop': '15px'}),
         ], className="six columns"),
     ], className='row'),
-    dcc.Markdown("---"),
-    html.Div([
-        html.Div([
+    # dcc.Markdown("---"),
+    html.Div([  # Row for commands
+        html.Div([  # Column for commands of place a
             html.H3("Commands Place A"),
             html.Button('Model', id='send-place-a-model', style=button_style_normal),
             html.Button('Model (Details)', id='send-place-a-model-details', style=button_style_normal),
@@ -74,7 +120,7 @@ app.layout = html.Div([
             html.Button('obj-Red -> Red', id='send-place-a-demo-objRed-red', style=button_style_normal),
             html.Button('obj-Red -> Blue', id='send-place-a-demo-objRed-blue', style=button_style_normal),
         ], className="six columns"),
-        html.Div([
+        html.Div([  # Column for commands of place b
             html.H3("Commands Place B"),
             html.Button('Model', id='send-place-b-model', style=button_style_normal),
             html.Button('Model (Details)', id='send-place-b-model-details', style=button_style_normal),
@@ -82,42 +128,146 @@ app.layout = html.Div([
             html.Button('obj-Red -> Red', id='send-place-b-demo-objRed-red', style=button_style_normal),
         ], className="six columns"),
     ], className='row'),
-    dcc.Markdown("---"),
+    # dcc.Markdown("---"),
     html.H3("MQTT Log"),
     dcc.Textarea(
         id='mqtt-log',
         value="",
         readOnly=True,
-        style={'width': '100%', 'height': '200px', 'fontFamily': 'Consolas, monospace'}
+        style={**textarea_style_normal, 'fontFamily': 'Consolas, monospace'}
+    ),
+    dcc.Checklist(
+        id="should-scroll-mqtt-log",
+        options=[{"label": "Auto-Scroll", "value": "Auto-Scroll"}],
+        value=["Auto-Scroll"],
+        labelStyle={"display": "inline-block"},
     ),
     dcc.Markdown("---"),
     html.Div([
-        # html.Div([
-            html.P("Topic"),
-            dcc.Input(id='manual-mqtt-topic'),
-            html.P("Message"),
-            dcc.Input(id='manual-mqtt-message'),
-            # dcc.Textarea(id='manual-mqtt-message', style={'width': '50%', 'font-family': 'Consolas, monospace'}),
-            html.Button('Send', id='manual-mqtt-send')
-        # ], className="six columns"),
-    ], className='row'),
+        html.P("Topic"),
+        dcc.Input(id='manual-mqtt-topic'),
+        html.P("Message"),
+        dcc.Input(id='manual-mqtt-message'),
+        # dcc.Textarea(id='manual-mqtt-message', style={'width': '50%', 'font-family': 'Consolas, monospace'}),
+        html.Button('Send', id='manual-mqtt-send')
+    ], className='row', style=dict(display='flex')),
 
     # -- Invisible elements --
+    dcc.Location(id='url', refresh=False),
     dcc.Interval(
         id='every-1-second',
         interval=1000,  # in milliseconds
         n_intervals=0
     ),
+    visdcc.Run_js(id='javascriptLog', run=""),
     html.Div(id='hidden-div', style={'display': 'none'})
 ])
 
 
+@app.callback(
+    Output('place-a-input-json', 'value'),
+    Output('place-b-input-json', 'value'),
+    Output('place-b-reachability-1-json', 'value'),
+    Output('place-b-reachability-2-json', 'value'),
+    Input('url', 'pathname')
+)
+def set_initial_json_content(_pathname):
+    """
+    Set initial JSON content from files in config/ directory
+    :param _pathname: Unused value of page URL
+    :return: list of contents
+    """
+    with open('config/config-scene-a.json') as fdr:
+        json_content_a = fdr.read()
+    with open('config/config-scene-b.json') as fdr:
+        json_content_b = fdr.read()
+    with open('config/dummy-reachability-b-arm1.json') as fdr:
+        json_content_b_reachability_1 = fdr.read()
+    with open('config/dummy-reachability-b-arm2.json') as fdr:
+        json_content_b_reachability_2 = fdr.read()
+    return json_content_a, json_content_b, json_content_b_reachability_1, json_content_b_reachability_2
+
+
+def send_json_for_object_to_topic(json_content: str, obj, topic: str):
+    """
+    Use JSON to populate protobuf object and send its serialization to the given topic
+    :param json_content: Content to initialize the object represented in JSON
+    :param obj: The object to send
+    :param topic: The MQTT topic to send the serialization
+    :return: None
+    """
+    json_format.Parse(json_content, obj)
+    mqttc.publish(topic=topic, payload=obj.SerializeToString())
+
+
+# @app.callback(
+#     Output('hidden-div-scene-a', 'children'),
+#     Input('send-place-a-update', 'n_clicks'),
+#     State('place-a-input-json', 'value')
+# )
+# def send_scene_a(n_clicks, json_content):
+#     """
+#     Send scene in place a
+#     :param n_clicks: button.clicks
+#     :param json_content: state of place-a-input-json
+#     :return: no_update
+#     """
+#     if n_clicks:
+#         scene = cgv_connector_pb2.Scene()
+#         send_json_for_object_to_topic(json_content, scene, 'place-a/scene/update')
+#     return dash.no_update
+#
+#
+# @app.callback(
+#     Output('hidden-div-scene-b', 'children'),
+#     Input('send-place-b-update', 'n_clicks'),
+#     State('place-b-input-json', 'value')
+# )
+# def send_scene_a(n_clicks, json_content):
+#     """
+#     Send scene in place b
+#     :param n_clicks: button.clicks
+#     :param json_content:state of place-b-input-json
+#     :return:no_update
+#     """
+#     if n_clicks:
+#         scene = cgv_connector_pb2.Scene()
+#         send_json_for_object_to_topic(json_content, scene, 'place-b/scene/update')
+#     return dash.no_update
+@app.callback(
+    Output('hidden-div-scene-b', 'children'),
+    [Input(button_id, 'n_clicks') for button_id in complex_commands],
+    [State(value[1], 'value') for value in complex_commands.values()]
+)
+def send_complex(*_):
+    ctx = dash.callback_context
+
+    if not ctx.triggered:
+        button_id = None
+    else:
+        button_id = ctx.triggered[0]['prop_id'].split('.')[0]
+    if button_id:
+        topic, state_id, protobuf_obj = complex_commands[button_id]
+        json_content = ctx.states[state_id + ".value"]
+        send_json_for_object_to_topic(json_content, protobuf_obj, topic)
+    return dash.no_update
+
+
 @app.callback(
     Output('mqtt-log', 'value'),
+    Output('javascriptLog', 'run'),
     Input('every-1-second', 'n_intervals'),
     State('mqtt-log', 'value'),
+    State('should-scroll-mqtt-log', 'value')
 )
-def append_to_mqtt_log(_, value):
+def append_to_mqtt_log(_n_intervals, value, should_scroll):
+    """
+    Periodically update mqtt log
+    :param _n_intervals: Unused value of intervals
+    :param value: current content of mqtt log
+    :param should_scroll: checkbox value whether to scroll to the end after update
+    :return: new content of mqtt log
+    """
     local_messages = []
     while not message_queue.empty():
         local_messages.append(message_queue.get_nowait())
@@ -136,7 +286,13 @@ def append_to_mqtt_log(_, value):
     #         reformatted_lines.append(utils.format_log_msg(topic, max_topic.max_mqtt_topic_length,
     #                                                       message, timestamp=timestamp))
     #         value = '\n'.join(reformatted_lines)
-    return value
+    else:
+        return dash.no_update
+    log_cmd = '''
+             var textarea = document.getElementById('mqtt-log');
+             textarea.scrollTop = textarea.scrollHeight;
+             ''' if should_scroll else ""
+    return value, log_cmd
 
 
 @app.callback(
@@ -144,6 +300,11 @@ def append_to_mqtt_log(_, value):
     [Input(button_id, 'n_clicks') for button_id in commands]
 )
 def button_clicked_to_add_to_mqtt_log(*_):
+    """
+    Based on dict "commands", send message to topic
+    :param _: Unused "catch-all" variable for n_clicks of all buttons
+    :return: no_update
+    """
     ctx = dash.callback_context
 
     if not ctx.triggered:
@@ -163,28 +324,44 @@ def button_clicked_to_add_to_mqtt_log(*_):
     State('manual-mqtt-message', 'value'),
 )
 def send_manual(n_clicks, topic, message):
+    """
+    Manual sending of mqtt message
+    :param n_clicks: button.n_clicks
+    :param topic: value of input field manual-mqtt-topic
+    :param message: value of input field manual-mqtt-message
+    :return: no_update
+    """
     if n_clicks:
         mqttc.publish(topic=topic, payload=message)
     return dash.no_update
 
 
 def on_mqtt_connect(_client, _userdata, _flags, _rc, _properties=None):
+    # Callback for mqtt client when connected
     print('Connected at ' + datetime.datetime.now().isoformat())
     ready_event.set()
 
 
 def on_mqtt_message(_client, _userdata, message):
+    # Callback for mqtt client when message was received
     max_mqtt_topic_length = max_topic.process_topic(message.topic)
-    try:
-        payload = message.payload.decode("utf-8")
-    except UnicodeDecodeError:
-        payload = "(unreadable bytes)"
+    if message.topic in bytes_topics:
+        payload = "(ignored bytes)"
+    else:
+        try:
+            payload = message.payload.decode("utf-8")
+        except UnicodeDecodeError:
+            payload = "(unreadable bytes)"
     message_queue.put_nowait(utils.format_log_msg(message.topic,
                                                   max_mqtt_topic_length,
-                                                  payload))  #.replace("\n", " ~ "))
+                                                  payload))  # .replace("\n", " ~ "))
 
 
 def publish_test_message():
+    """
+    Publish test message to see that app has started and mqtt client has connected (both successfully)
+    :return: None
+    """
     time.sleep(2)
     mqttc.publish(topic="init", payload=datetime.datetime.now().isoformat())
 
diff --git a/requirements.txt b/requirements.txt
index cb73df6..5cefdc0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,4 @@
 paho-mqtt~=1.5.1
 dash~=1.20.0
+visdcc~=0.0.40
+protobuf~=3.15.8
-- 
GitLab