diff --git a/.gitignore b/.gitignore
index 7f249738c56e97da80aaecaebb99c528eba78d3a..60c6519f7724a7ca08cac3263b595400dba9fdd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,8 @@
 **/.project
 **/.pydevproject
 **/.settings/org.eclipse.core.resources.prefs
+tangostationcontrol/dist
+tangostationcontrol/build
+**/.ipynb_checkpoints
+**/pending_log_messages.db
+**/.eggs
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 14ee77667f38df99badcb69c35a823b7ca62a283..27593ca877e544634c42b303a1dc3d756df4cb41 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,9 +4,6 @@ image: artefact.skao.int/ska-tango-images-tango-itango:9.3.5
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
-  # The PBR dependency requires a set version, not actually used
-  # Instead `util/lofar_git.py:get_version()` is used.
-  PBR_VERSION: "0.1"
 cache:
   paths:
     - .cache/pip
@@ -16,22 +13,29 @@ stages:
   - static-analysis
   - unit-tests
   - integration-tests
+  - packaging
 newline_at_eof:
   stage: linting
   before_script:
-    - pip3 install -r devices/test-requirements.txt
+    - pip3 install -r tangostationcontrol/test-requirements.txt
   script:
 #     TODO(Corne): Ignore shell files in submodules more cleanly
     - flake8 --filename *.sh,*.conf,*.md,*.yml --select=W292 --exclude docker-compose/tango-prometheus-exporter,.tox,.egg-info,docker
 python_linting:
   stage: linting
+  before_script:
+    - sudo apt-get update
+    - sudo apt-get install -y git
   script:
-    - cd devices
+    - cd tangostationcontrol
     - tox -e pep8
 bandit:
   stage: static-analysis
+  before_script:
+    - sudo apt-get update
+    - sudo apt-get install -y git
   script:
-    - cd devices
+    - cd tangostationcontrol
     - tox -e bandit
 shellcheck:
   stage: static-analysis
@@ -47,7 +51,7 @@ unit_test:
     - sudo apt-get update
     - sudo apt-get install -y git
   script:
-    - cd devices
+    - cd tangostationcontrol
     - tox -e py37
 integration_test_docker:
   stage: integration-tests
@@ -78,3 +82,16 @@ integration_test_docker:
     - chmod u+x $CI_PROJECT_DIR/sbin/run_integration_test.sh
 #    Do not remove 'bash' or statement will be ignored by primitive docker shell
     - bash $CI_PROJECT_DIR/sbin/run_integration_test.sh
+wheel_packaging:
+  stage: packaging
+  artifacts:
+    paths:
+      - tangostationcontrol/dist/*.whl
+  before_script:
+    - sudo apt-get update
+    - sudo apt-get install -y git
+    - pip3 install -r tangostationcontrol/test-requirements.txt
+    - pip3 install -r docker-compose/itango/lofar-requirements.txt
+  script:
+    - cd tangostationcontrol
+    - python setup.py bdist_wheel
diff --git a/bin/start-ds.sh b/bin/start-ds.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e7fab7e9331b4512f9caf13bb9096146605fd4d9
--- /dev/null
+++ b/bin/start-ds.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# Serves as entrypoint script for docker containers
+
+# Check required support file exists
+if [[ ! -f "/usr/local/bin/wait-for-it.sh" ]]; then
+    >&2 echo "/usr/local/bin/wait-for-it.sh file does not exist!"
+    exit 1
+fi
+
+# Check required environment variable is set
+if [[ ! $TANGO_HOST ]]; then
+  >&2 echo "TANGO_HOST environment variable unset!"
+  exit 1
+fi
+
+# Check if configured for specific version
+if [[ $TANGOSTATIONCONTROL ]]; then
+  # TODO (Corne): Download version from artifacts or pypi.
+  # Consider exit 2 an UnImplementedError
+  exit 2
+else
+  # Install the package, exit 1 if it fails
+  cd tangostationcontrol || exit 1
+  mkdir -p /tmp/tangostationcontrol
+  python3 setup.py build --build-base /tmp/tangostationcontrol egg_info --egg-base /tmp/tangostationcontrol bdist_wheel --dist-dir /tmp/tangostationcontrol || exit 1
+  # shellcheck disable=SC2012
+  sudo pip install "$(ls -Art /tmp/tangostationcontrol/*.whl | tail -n 1)"
+fi
+
+/usr/local/bin/wait-for-it.sh "$TANGO_HOST" --timeout=30 --strict -- "$@"
diff --git a/devices/.stestr.conf b/devices/.stestr.conf
deleted file mode 100644
index 07147c8697683f270e9388da8b914c20cb8e4c45..0000000000000000000000000000000000000000
--- a/devices/.stestr.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-[DEFAULT]
-test_path=${TESTS_DIR:-./test}
-top_dir=./
diff --git a/devices/LICENSE.txt b/devices/LICENSE.txt
deleted file mode 100644
index c9978b8eee263aebfdd8d0a016e447940682ba8b..0000000000000000000000000000000000000000
--- a/devices/LICENSE.txt
+++ /dev/null
@@ -1,201 +0,0 @@
-                              Apache License
-                        Version 2.0, January 2004
-                     http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-   "License" shall mean the terms and conditions for use, reproduction,
-   and distribution as defined by Sections 1 through 9 of this document.
-
-   "Licensor" shall mean the copyright owner or entity authorized by
-   the copyright owner that is granting the License.
-
-   "Legal Entity" shall mean the union of the acting entity and all
-   other entities that control, are controlled by, or are under common
-   control with that entity. For the purposes of this definition,
-   "control" means (i) the power, direct or indirect, to cause the
-   direction or management of such entity, whether by contract or
-   otherwise, or (ii) ownership of fifty percent (50%) or more of the
-   outstanding shares, or (iii) beneficial ownership of such entity.
-
-   "You" (or "Your") shall mean an individual or Legal Entity
-   exercising permissions granted by this License.
-
-   "Source" form shall mean the preferred form for making modifications,
-   including but not limited to software source code, documentation
-   source, and configuration files.
-
-   "Object" form shall mean any form resulting from mechanical
-   transformation or translation of a Source form, including but
-   not limited to compiled object code, generated documentation,
-   and conversions to other media types.
-
-   "Work" shall mean the work of authorship, whether in Source or
-   Object form, made available under the License, as indicated by a
-   copyright notice that is included in or attached to the work
-   (an example is provided in the Appendix below).
-
-   "Derivative Works" shall mean any work, whether in Source or Object
-   form, that is based on (or derived from) the Work and for which the
-   editorial revisions, annotations, elaborations, or other modifications
-   represent, as a whole, an original work of authorship. For the purposes
-   of this License, Derivative Works shall not include works that remain
-   separable from, or merely link (or bind by name) to the interfaces of,
-   the Work and Derivative Works thereof.
-
-   "Contribution" shall mean any work of authorship, including
-   the original version of the Work and any modifications or additions
-   to that Work or Derivative Works thereof, that is intentionally
-   submitted to Licensor for inclusion in the Work by the copyright owner
-   or by an individual or Legal Entity authorized to submit on behalf of
-   the copyright owner. For the purposes of this definition, "submitted"
-   means any form of electronic, verbal, or written communication sent
-   to the Licensor or its representatives, including but not limited to
-   communication on electronic mailing lists, source code control systems,
-   and issue tracking systems that are managed by, or on behalf of, the
-   Licensor for the purpose of discussing and improving the Work, but
-   excluding communication that is conspicuously marked or otherwise
-   designated in writing by the copyright owner as "Not a Contribution."
-
-   "Contributor" shall mean Licensor and any individual or Legal Entity
-   on behalf of whom a Contribution has been received by Licensor and
-   subsequently incorporated within the Work.
-
-2. Grant of Copyright License. Subject to the terms and conditions of
-   this License, each Contributor hereby grants to You a perpetual,
-   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-   copyright license to reproduce, prepare Derivative Works of,
-   publicly display, publicly perform, sublicense, and distribute the
-   Work and such Derivative Works in Source or Object form.
-
-3. Grant of Patent License. Subject to the terms and conditions of
-   this License, each Contributor hereby grants to You a perpetual,
-   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-   (except as stated in this section) patent license to make, have made,
-   use, offer to sell, sell, import, and otherwise transfer the Work,
-   where such license applies only to those patent claims licensable
-   by such Contributor that are necessarily infringed by their
-   Contribution(s) alone or by combination of their Contribution(s)
-   with the Work to which such Contribution(s) was submitted. If You
-   institute patent litigation against any entity (including a
-   cross-claim or counterclaim in a lawsuit) alleging that the Work
-   or a Contribution incorporated within the Work constitutes direct
-   or contributory patent infringement, then any patent licenses
-   granted to You under this License for that Work shall terminate
-   as of the date such litigation is filed.
-
-4. Redistribution. You may reproduce and distribute copies of the
-   Work or Derivative Works thereof in any medium, with or without
-   modifications, and in Source or Object form, provided that You
-   meet the following conditions:
-
-   (a) You must give any other recipients of the Work or
-       Derivative Works a copy of this License; and
-
-   (b) You must cause any modified files to carry prominent notices
-       stating that You changed the files; and
-
-   (c) You must retain, in the Source form of any Derivative Works
-       that You distribute, all copyright, patent, trademark, and
-       attribution notices from the Source form of the Work,
-       excluding those notices that do not pertain to any part of
-       the Derivative Works; and
-
-   (d) If the Work includes a "NOTICE" text file as part of its
-       distribution, then any Derivative Works that You distribute must
-       include a readable copy of the attribution notices contained
-       within such NOTICE file, excluding those notices that do not
-       pertain to any part of the Derivative Works, in at least one
-       of the following places: within a NOTICE text file distributed
-       as part of the Derivative Works; within the Source form or
-       documentation, if provided along with the Derivative Works; or,
-       within a display generated by the Derivative Works, if and
-       wherever such third-party notices normally appear. The contents
-       of the NOTICE file are for informational purposes only and
-       do not modify the License. You may add Your own attribution
-       notices within Derivative Works that You distribute, alongside
-       or as an addendum to the NOTICE text from the Work, provided
-       that such additional attribution notices cannot be construed
-       as modifying the License.
-
-   You may add Your own copyright statement to Your modifications and
-   may provide additional or different license terms and conditions
-   for use, reproduction, or distribution of Your modifications, or
-   for any such Derivative Works as a whole, provided Your use,
-   reproduction, and distribution of the Work otherwise complies with
-   the conditions stated in this License.
-
-5. Submission of Contributions. Unless You explicitly state otherwise,
-   any Contribution intentionally submitted for inclusion in the Work
-   by You to the Licensor shall be under the terms and conditions of
-   this License, without any additional terms or conditions.
-   Notwithstanding the above, nothing herein shall supersede or modify
-   the terms of any separate license agreement you may have executed
-   with Licensor regarding such Contributions.
-
-6. Trademarks. This License does not grant permission to use the trade
-   names, trademarks, service marks, or product names of the Licensor,
-   except as required for reasonable and customary use in describing the
-   origin of the Work and reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty. Unless required by applicable law or
-   agreed to in writing, Licensor provides the Work (and each
-   Contributor provides its Contributions) on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-   implied, including, without limitation, any warranties or conditions
-   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-   PARTICULAR PURPOSE. You are solely responsible for determining the
-   appropriateness of using or redistributing the Work and assume any
-   risks associated with Your exercise of permissions under this License.
-
-8. Limitation of Liability. In no event and under no legal theory,
-   whether in tort (including negligence), contract, or otherwise,
-   unless required by applicable law (such as deliberate and grossly
-   negligent acts) or agreed to in writing, shall any Contributor be
-   liable to You for damages, including any direct, indirect, special,
-   incidental, or consequential damages of any character arising as a
-   result of this License or out of the use or inability to use the
-   Work (including but not limited to damages for loss of goodwill,
-   work stoppage, computer failure or malfunction, or any and all
-   other commercial damages or losses), even if such Contributor
-   has been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability. While redistributing
-   the Work or Derivative Works thereof, You may choose to offer,
-   and charge a fee for, acceptance of support, warranty, indemnity,
-   or other liability obligations and/or rights consistent with this
-   License. However, in accepting such obligations, You may act only
-   on Your own behalf and on Your sole responsibility, not on behalf
-   of any other Contributor, and only if You agree to indemnify,
-   defend, and hold each Contributor harmless for any liability
-   incurred by, or claims asserted against, such Contributor by reason
-   of your accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
-   To apply the Apache License to your work, attach the following
-   boilerplate notice, with the fields enclosed by brackets "[]"
-   replaced with your own identifying information. (Don't include
-   the brackets!)  The text should be enclosed in the appropriate
-   comment syntax for the file format. We also recommend that a
-   file or class name and description of purpose be included on the
-   same "printed page" as the copyright notice for easier
-   identification within third-party archives.
-
-Copyright 2021 ASTRON Netherlands Institute for Radio Astronomy
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/devices/__init__.py b/devices/__init__.py
deleted file mode 100644
index 82b2af0e96f75105253e501e47f8861218132f63..0000000000000000000000000000000000000000
--- a/devices/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from util.lofar_git import get_version
-
-__version__ = get_version()
-
diff --git a/devices/common/lofar_git.py b/devices/common/lofar_git.py
deleted file mode 100644
index f4f6217280fe612fa2e9a7830c1f026c1e36a815..0000000000000000000000000000000000000000
--- a/devices/common/lofar_git.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import git # pip3 install gitpython
-import os
-from functools import lru_cache
-
-def get_repo(starting_directory: str = os.path.dirname(os.path.abspath(__file__))) -> git.Repo:
-    """ Try finding the repository by traversing up the tree.
-
-        By default, the repository containing this module is returned.
-    """
-
-    directory = starting_directory
-
-    try:
-        return git.Repo(directory)
-    except git.InvalidGitRepositoryError:
-        pass
-
-    # We now have to traverse up the tree
-    while directory != "/" and os.path.exists(directory):
-        # Go to parent
-        directory = os.path.abspath(directory + os.path.sep + "..")
-
-        try:
-            return git.Repo(directory)
-        except git.InvalidGitRepositoryError:
-            pass
-
-    raise git.InvalidGitRepositoryError("Could not find git repository root in {}".format(starting_directory))
-
-
-@lru_cache(maxsize=None)
-def get_version(repo: git.Repo = None) -> str:
-    """ Return a version string for the current commit.
-
-    There is a practical issue: the repository changes over time, f.e. switching branches with 'git checkout'. We want
-    to know the version that is running in memory, not the one that is on disk.
-
-    As a work-around, we cache the version information, in that it is at least consistent. It is up to the caller
-    to request the version early enough. 
-    
-    The version string is one of:
-       - <tag>
-       - <branch> [<commit>]
-
-    In both cases, a "*" prefix indicates this code is not production ready. Code is considered production ready if
-    it is a tag and there are no local modifications.
-       
-    """
-
-    if repo is None:
-        repo = get_repo()
-
-    commit = repo.commit()
-    tags = {tag.commit: tag for tag in repo.tags}
-
-    if commit in tags:
-        # a tag = production ready
-        commit_str = "{}".format(tags[commit])
-        production_ready = True
-    elif repo.head.is_detached:
-        # no active branch
-        commit_str = "<detached HEAD> [{}]".format(commit)
-        production_ready = False
-    else:
-        # HEAD of a branch
-        branch = repo.active_branch
-        commit_str = "{} [{}]".format(branch, commit)
-        production_ready = False
-
-    if repo.is_dirty():
-        production_ready = False
-
-    return "{}{}".format("*" if not production_ready else "", commit_str)
-
-
-# at least cache the current repo version immediately
-try:
-    _ = get_version()
-except:
-    pass
-
-
-if __name__ == "__main__":
-    print(get_version())
diff --git a/devices/requirements.txt b/devices/requirements.txt
deleted file mode 100644
index 8e11e2f537bf59f3602379c853976696df7524f0..0000000000000000000000000000000000000000
--- a/devices/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# the order of packages is of significance, because pip processes them in the
-# order of appearance. Changing the order has an impact on the overall
-# integration process, which may cause wedges in the gate later.
-
-pbr>=2.0 # Apache-2.0
diff --git a/devices/setup.cfg b/devices/setup.cfg
deleted file mode 100644
index 55b29032e6aefc1787179c054b701b7fc51323ac..0000000000000000000000000000000000000000
--- a/devices/setup.cfg
+++ /dev/null
@@ -1,30 +0,0 @@
-[metadata]
-name = TangoStationControl
-summary = LOFAR 2.0 Station Control
-description_file =
-    README.md
-description_content_type = text/x-rst; charset=UTF-8
-author = ASTRON
-home_page = https://astron.nl
-project_urls =
-    Bug Tracker = https://support.astron.nl/jira/projects/L2SS/issues/
-    Source Code = https://git.astron.nl/lofar2.0/tango
-license = Apache-2
-classifier =
-    Environment :: Console
-    License :: Apache Software License
-    Operating System :: POSIX :: Linux
-    Programming Language :: Python
-    Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.6
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-
-[files]
-package_dir=./
-
-[entry_points]
-console_scripts =
-    SDP = SDP:main
-   RECV = RECV:main
diff --git a/devices/setup.py b/devices/setup.py
deleted file mode 100644
index 4fa0ce44d0caa9b174fc65a699e63b31e43aee9b..0000000000000000000000000000000000000000
--- a/devices/setup.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import setuptools
-
-# Requires: setup.cfg
-setuptools.setup(setup_requires=['pbr>=2.0.0'], pbr=True)
diff --git a/devices/test/common/test_lofar_git.py b/devices/test/common/test_lofar_git.py
deleted file mode 100644
index 52a1c7d876fc2827757f082e0f44a0a64b1ffc78..0000000000000000000000000000000000000000
--- a/devices/test/common/test_lofar_git.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of the LOFAR 2.0 Station Software
-#
-#
-#
-# Distributed under the terms of the APACHE license.
-# See LICENSE.txt for more info.
-
-import git
-from unittest import mock
-
-from common import lofar_git
-
-from test import base
-
-
-class TestLofarGit(base.TestCase):
-
-    def setUp(self):
-        super(TestLofarGit, self).setUp()
-
-        # Clear the cache as this function of lofar_git uses LRU decorator
-        # This is a good demonstration of how unit tests in Python can have
-        # permanent effects, typically fixtures are needed to restore these.
-        lofar_git.get_version.cache_clear()
-
-    def test_get_version(self):
-        """Test if attributes of get_repo are correctly used by get_version"""
-
-        with mock.patch.object(lofar_git, 'get_repo') as m_get_repo:
-            m_commit = mock.Mock()
-            m_commit.return_value = "123456"
-
-            m_is_dirty = mock.Mock()
-            m_is_dirty.return_value = True
-
-            m_head = mock.Mock(is_detached=False)
-
-            m_get_repo.return_value = mock.Mock(
-                active_branch="main", commit=m_commit, tags=[],
-                is_dirty=m_is_dirty, head=m_head)
-
-            # No need for special string equal in Python
-            self.assertEqual("*main [123456]", lofar_git.get_version())
-
-    def test_get_version_tag(self):
-        """Test if get_version determines production_ready for tagged commit"""
-
-        with mock.patch.object(lofar_git, 'get_repo') as m_get_repo:
-            m_commit = mock.Mock()
-            m_commit.return_value = "123456"
-
-            m_is_dirty = mock.Mock()
-            m_is_dirty.return_value = False
-
-            m_head = mock.Mock(is_detached=False)
-
-            m_tag = mock.Mock(commit="123456")
-            m_tag.__str__ = mock.Mock(return_value= "version-1.2")
-
-            m_get_repo.return_value = mock.Mock(
-                active_branch="main", commit=m_commit,
-                tags=[m_tag], is_dirty=m_is_dirty, head=m_head)
-
-            self.assertEqual("version-1.2", lofar_git.get_version())
-
-    @mock.patch.object(lofar_git, 'get_repo')
-    def test_get_version_tag_dirty(self, m_get_repo):
-        """Test if get_version determines dirty tagged commit"""
-
-        m_commit = mock.Mock()
-        m_commit.return_value = "123456"
-
-        m_is_dirty = mock.Mock()
-        m_is_dirty.return_value = False
-
-        m_head = mock.Mock(is_detached=False)
-
-        m_tag = mock.Mock(commit="123456")
-        m_tag.__str__ = mock.Mock(return_value= "version-1.2")
-
-        # Now m_get_repo is mocked using a decorator
-        m_get_repo.return_value = mock.Mock(
-            active_branch="main", commit=m_commit,
-            tags=[m_tag], is_dirty=m_is_dirty, head=m_head)
-
-        self.assertEqual("version-1.2", lofar_git.get_version())
-
-    def test_catch_repo_error(self):
-        """Test if invalid git directories will raise error"""
-
-        with mock.patch.object(lofar_git, 'get_repo') as m_get_repo:
-
-            # Configure lofar_git.get_repo to raise InvalidGitRepositoryError
-            m_get_repo.side_effect = git.InvalidGitRepositoryError
-
-            # Test that error is raised by get_version
-            self.assertRaises(
-                git.InvalidGitRepositoryError, lofar_git.get_version)
diff --git a/docker-compose/device-boot.yml b/docker-compose/device-boot.yml
index d51f91614a483feb8c5956d3d35942843c4be3ce..131e5644399eeae7b0e043a7aca9829d45a9a167 100644
--- a/docker-compose/device-boot.yml
+++ b/docker-compose/device-boot.yml
@@ -32,12 +32,8 @@ services:
     environment:
       - TANGO_HOST=${TANGO_HOST}
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/boot.py LTS -v -ORBendPoint giop:tcp:0:5708 -ORBendPointPublish giop:tcp:${HOSTNAME}:5708
+      - l2ss-boot Boot LTS -v -ORBendPoint giop:tcp:0:5708 -ORBendPointPublish giop:tcp:${HOSTNAME}:5708
     restart: unless-stopped
diff --git a/docker-compose/device-docker.yml b/docker-compose/device-docker.yml
index 49144b7bb61ed1ca30c83b26e0c5443761411c54..93e7cd8eaca8ea2ad6a024fa6fab7a1902b693c1 100644
--- a/docker-compose/device-docker.yml
+++ b/docker-compose/device-docker.yml
@@ -34,13 +34,10 @@ services:
     user: 1000:${DOCKER_GID} # uid 1000 is the default "tango" user
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/docker_device.py LTS -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705
+      - l2ss-docker-device Docker LTS -v -ORBendPoint giop:tcp:0:5705 -ORBendPointPublish giop:tcp:${HOSTNAME}:5705
     restart: unless-stopped
diff --git a/docker-compose/device-observation_control.yml b/docker-compose/device-observation_control.yml
index b057df808d9f7c0fc3a2e5fb8bb9a3f0504ff388..d15fb8a8e708ce7e01e27a300348a5f47b09aa24 100644
--- a/docker-compose/device-observation_control.yml
+++ b/docker-compose/device-observation_control.yml
@@ -31,13 +31,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/observation_control.py LTS -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703
+      - l2ss-observation-control ObservationControl LTS -v -ORBendPoint giop:tcp:0:5703 -ORBendPointPublish giop:tcp:${HOSTNAME}:5703
     restart: unless-stopped
diff --git a/docker-compose/device-recv.yml b/docker-compose/device-recv.yml
index 0b860949bb44cd8301986f732df99a8a3d58d8aa..c1269ab89e34798b8766ced06d7df3394cbaf3b7 100644
--- a/docker-compose/device-recv.yml
+++ b/docker-compose/device-recv.yml
@@ -32,13 +32,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/recv.py LTS -v -ORBendPoint giop:tcp:0:5707 -ORBendPointPublish giop:tcp:${HOSTNAME}:5707
+      - l2ss-receiver RECV LTS -v -ORBendPoint giop:tcp:0:5707 -ORBendPointPublish giop:tcp:${HOSTNAME}:5707
     restart: unless-stopped
diff --git a/docker-compose/device-sdp.yml b/docker-compose/device-sdp.yml
index c1685235467277ac60f781918aa98fde6394877d..0768d39cd4a75d4d92abd59f42cbb436ac4c2f48 100644
--- a/docker-compose/device-sdp.yml
+++ b/docker-compose/device-sdp.yml
@@ -32,13 +32,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/sdp/sdp.py LTS -v -ORBendPoint giop:tcp:0:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701
+      - l2ss-sdp SDP LTS -v -ORBendPoint giop:tcp:0:5701 -ORBendPointPublish giop:tcp:${HOSTNAME}:5701
     restart: unless-stopped
diff --git a/docker-compose/device-sst.yml b/docker-compose/device-sst.yml
index 68164379ed1e7d3fa985cb9f3c5f620c09b71d50..3924b4bf18c1a221a8859a32a73f9ccccb4fdfc1 100644
--- a/docker-compose/device-sst.yml
+++ b/docker-compose/device-sst.yml
@@ -35,13 +35,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/sdp/sst.py LTS -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702
+      - l2ss-sst SST LTS -v -ORBendPoint giop:tcp:0:5702 -ORBendPointPublish giop:tcp:${HOSTNAME}:5702
     restart: unless-stopped
diff --git a/docker-compose/device-unb2.yml b/docker-compose/device-unb2.yml
index bd9b6496332994171959967ff358115ac91e5ca7..9e9f8797ce62f57c3fd3434dd41c36412df2150f 100644
--- a/docker-compose/device-unb2.yml
+++ b/docker-compose/device-unb2.yml
@@ -32,13 +32,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/unb2.py LTS -v -ORBendPoint giop:tcp:0:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704
+      - l2ss-unb2 UNB2 LTS -v -ORBendPoint giop:tcp:0:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704
     restart: unless-stopped
diff --git a/docker-compose/device-xst.yml b/docker-compose/device-xst.yml
index 619b532e694f502b93638c2427bb7d9464138e82..6d7a1036dfa965750bcf7212ae865a1b71e3de3a 100644
--- a/docker-compose/device-xst.yml
+++ b/docker-compose/device-xst.yml
@@ -35,13 +35,10 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
+    working_dir: /opt/lofar/tango
     entrypoint:
-      - /usr/local/bin/wait-for-it.sh
-      - ${TANGO_HOST}
-      - --timeout=30
-      - --strict
-      - --
+      - bin/start-ds.sh
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u /opt/lofar/tango/devices/devices/sdp/xst.py LTS -v -ORBendPoint giop:tcp:0:5706 -ORBendPointPublish giop:tcp:${HOSTNAME}:5706
+      - l2ss-xst XST LTS -v -ORBendPoint giop:tcp:0:5706 -ORBendPointPublish giop:tcp:${HOSTNAME}:5706
     restart: unless-stopped
diff --git a/docker-compose/integration-test.yml b/docker-compose/integration-test.yml
index 1811645b06f4d576a71a66d2f36a475c976a2308..defb45e3c3183516131795b283372ca784635d8c 100644
--- a/docker-compose/integration-test.yml
+++ b/docker-compose/integration-test.yml
@@ -21,7 +21,7 @@ services:
         - ..:/opt/lofar/tango:rw
     environment:
       - TANGO_HOST=${TANGO_HOST}
-    working_dir: /opt/lofar/tango/devices
+    working_dir: /opt/lofar/tango/tangostationcontrol
     entrypoint:
       - /usr/local/bin/wait-for-it.sh
       - ${TANGO_HOST}
diff --git a/docker-compose/itango/lofar-requirements.txt b/docker-compose/itango/lofar-requirements.txt
index 1349c50ca993b51bb866a7880e3e7fb185049de8..b193887dbfdb59a4bc143ce6dd48a720ec5ad00c 100644
--- a/docker-compose/itango/lofar-requirements.txt
+++ b/docker-compose/itango/lofar-requirements.txt
@@ -1,8 +1,4 @@
+# Do not put tangostationcontrol dependencies here
 parso == 0.7.1
 jedi == 0.17.2
-asyncua
-astropy 
-python-logstash-async
-gitpython
-PyMySQL[rsa]
-sqlalchemy
+astropy
diff --git a/docker-compose/lofar-device-base/lofar-requirements.txt b/docker-compose/lofar-device-base/lofar-requirements.txt
index 31b22c71689b481357cef56bf4940c1575a3b01d..10ad55d977c97793a352c13da323d84d3c826c0e 100644
--- a/docker-compose/lofar-device-base/lofar-requirements.txt
+++ b/docker-compose/lofar-device-base/lofar-requirements.txt
@@ -1,7 +1,5 @@
-asyncua
+# Do not put tangostationcontrol dependencies here
 astropy
-python-logstash-async
-gitpython
-PyMySQL[rsa]
-sqlalchemy
-docker
+
+# requirements to build tangocontrol 
+GitPython >= 3.1.24 # BSD
diff --git a/tangostationcontrol/.stestr.conf b/tangostationcontrol/.stestr.conf
new file mode 100644
index 0000000000000000000000000000000000000000..59b161cddad8a253059cf201b8872bc946f78c64
--- /dev/null
+++ b/tangostationcontrol/.stestr.conf
@@ -0,0 +1,3 @@
+[DEFAULT]
+test_path=${TESTS_DIR:-./tangostationcontrol/test}
+top_dir=./
diff --git a/devices/README.md b/tangostationcontrol/README.md
similarity index 100%
rename from devices/README.md
rename to tangostationcontrol/README.md
diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b1620255b5a45c9e3f653661e65de6732fd93a07
--- /dev/null
+++ b/tangostationcontrol/requirements.txt
@@ -0,0 +1,13 @@
+# the order of packages is of significance, because pip processes them in the
+# order of appearance. Changing the order has an impact on the overall
+# integration process, which may cause wedges in the gate later.
+
+asyncua >= 0.9.90 # LGPLv3
+PyMySQL[rsa] >= 1.0.2 # MIT
+sqlalchemy >= 1.4.26 #MIT
+GitPython >= 3.1.24 # BSD
+snmp >= 0.1.7 # GPL3
+h5py >= 3.5.0 # BSD
+psutil >= 5.8.0 # BSD
+docker >= 5.0.3 # Apache 2
+python-logstash-async >= 2.3.0 # MIT
\ No newline at end of file
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..daee0edbff26ee8f44613d75dd8a6bcd411906e0
--- /dev/null
+++ b/tangostationcontrol/setup.cfg
@@ -0,0 +1,56 @@
+[metadata]
+name = tangostationcontrol
+version = attr: tangostationcontrol.__version__
+summary = LOFAR 2.0 Station Control
+description_file =
+    README.md
+description_content_type = text/x-rst; charset=UTF-8
+author = ASTRON
+home_page = https://astron.nl
+project_urls =
+    Bug Tracker = https://support.astron.nl/jira/projects/L2SS/issues/
+    Source Code = https://git.astron.nl/lofar2.0/tango
+license = Apache-2
+classifier =
+    Environment :: Console
+    License :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+
+[options]
+package_dir=
+    =./
+packages=find:
+python_requires = >=3.6
+
+[options.packages.find]
+where=./
+
+[options.entry_points]
+console_scripts =
+    l2ss-boot = tangostationcontrol.devices.boot:main
+    l2ss-docker-device = tangostationcontrol.devices.docker_device:main
+    l2ss-observation = tangostationcontrol.devices.observation:main
+    l2ss-observation-control = tangostationcontrol.devices.observation_control:main
+    l2ss-receiver = tangostationcontrol.devices.recv:main
+    l2ss-sdp = tangostationcontrol.devices.sdp.sdp:main
+    l2ss-sst = tangostationcontrol.devices.sdp.sst:main
+    l2ss-unb2 = tangostationcontrol.devices.unb2:main
+    l2ss-xst = tangostationcontrol.devices.sdp.xst:main
+    l2ss-statistics-reader = tangostationcontrol.statistics_writer.statistics_reader:main
+    l2ss-statistics-writer = tangostationcontrol.statistics_writer.statistics_writer:main
+
+# The following entry points should eventually be removed / replaced
+    l2ss-cold-start = tangostationcontrol.toolkit.lts_cold_start:main
+    l2ss-hardware-device-template = tangostationcontrol.examples.HW_device_template:main
+    l2ss-ini-device = tangostationcontrol.examples.load_from_disk.ini_device:main
+    l2ss-parse-statistics-packet = tangostationcontrol.devices.sdp.statistics_packet:main
+    l2ss-random-data = tangostationcontrol.test.devices.random_data:main
+    l2ss-snmp = tangostationcontrol.examples.snmp.snmp:main
+    l2ss-version = tangostationcontrol.common.lofar_version:main
diff --git a/tangostationcontrol/setup.py b/tangostationcontrol/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..6356812fdc2951fff6af9659feca273df06efca3
--- /dev/null
+++ b/tangostationcontrol/setup.py
@@ -0,0 +1,7 @@
+import setuptools
+
+with open('requirements.txt') as f:
+    required = f.read().splitlines()
+
+# Requires: setup.cfg
+setuptools.setup(install_requires=required)
diff --git a/tangostationcontrol/tangostationcontrol/__init__.py b/tangostationcontrol/tangostationcontrol/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6e48f3e8c0b11146b60c5fb7b8b2285fd7124d0
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/__init__.py
@@ -0,0 +1,3 @@
+from tangostationcontrol.common.lofar_version import get_version
+
+__version__ = get_version()
diff --git a/devices/clients/README.md b/tangostationcontrol/tangostationcontrol/clients/README.md
similarity index 100%
rename from devices/clients/README.md
rename to tangostationcontrol/tangostationcontrol/clients/README.md
diff --git a/devices/devices/__init__.py b/tangostationcontrol/tangostationcontrol/clients/__init__.py
similarity index 100%
rename from devices/devices/__init__.py
rename to tangostationcontrol/tangostationcontrol/clients/__init__.py
diff --git a/devices/clients/attribute_wrapper.py b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py
similarity index 98%
rename from devices/clients/attribute_wrapper.py
rename to tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py
index e025d56d3421144b0dfeb1457f417fd4946a2671..f9cc13b3bfa8738414fa265c5a2154c0c471a493 100644
--- a/devices/clients/attribute_wrapper.py
+++ b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py
@@ -2,7 +2,7 @@ from tango.server import attribute
 from tango import AttrWriteType
 import numpy
 
-from devices.device_decorators import only_when_on, fault_on_error
+from tangostationcontrol.devices.device_decorators import only_when_on, fault_on_error
 import logging
 
 logger = logging.getLogger()
diff --git a/devices/clients/comms_client.py b/tangostationcontrol/tangostationcontrol/clients/comms_client.py
similarity index 100%
rename from devices/clients/comms_client.py
rename to tangostationcontrol/tangostationcontrol/clients/comms_client.py
diff --git a/devices/clients/docker_client.py b/tangostationcontrol/tangostationcontrol/clients/docker_client.py
similarity index 100%
rename from devices/clients/docker_client.py
rename to tangostationcontrol/tangostationcontrol/clients/docker_client.py
diff --git a/devices/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py
similarity index 99%
rename from devices/clients/opcua_client.py
rename to tangostationcontrol/tangostationcontrol/clients/opcua_client.py
index 1c6ee35621dcc1ca5671ffbb31bbc71ddfa75faf..145c0ebe68c3824d764dd4417a81afeae8ce0247 100644
--- a/devices/clients/opcua_client.py
+++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py
@@ -1,11 +1,10 @@
-from threading import Thread
 import socket
 import numpy
 import asyncua
 import asyncio
 from asyncua import Client
 
-from clients.comms_client import AsyncCommClient
+from tangostationcontrol.clients.comms_client import AsyncCommClient
 
 import logging
 logger = logging.getLogger()
diff --git a/devices/clients/statistics_client.py b/tangostationcontrol/tangostationcontrol/clients/statistics_client.py
similarity index 98%
rename from devices/clients/statistics_client.py
rename to tangostationcontrol/tangostationcontrol/clients/statistics_client.py
index 3fd470fbf0319e45242abbc3a79362584628f844..2790197bdb90f2483d872f0dfd5978fa088d4980 100644
--- a/devices/clients/statistics_client.py
+++ b/tangostationcontrol/tangostationcontrol/clients/statistics_client.py
@@ -6,7 +6,7 @@ from .comms_client import AsyncCommClient
 from .tcp_replicator import TCPReplicator
 from .udp_receiver import UDPReceiver
 
-from devices.sdp.statistics_collector import StatisticsConsumer
+from tangostationcontrol.devices.sdp.statistics_collector import StatisticsConsumer
 
 logger = logging.getLogger()
 
diff --git a/devices/clients/statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py
similarity index 100%
rename from devices/clients/statistics_client_thread.py
rename to tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py
diff --git a/devices/clients/tcp_replicator.py b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py
similarity index 99%
rename from devices/clients/tcp_replicator.py
rename to tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py
index 5ac6e492d977cf14452d4f97bd213c0d12af7cbb..8b820cc765daa193fe142f4a3f49ef7a3f643c76 100644
--- a/devices/clients/tcp_replicator.py
+++ b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py
@@ -6,7 +6,7 @@ from threading import Thread
 import asyncio
 import logging
 
-from clients.statistics_client_thread import StatisticsClientThread
+from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread
 
 logger = logging.getLogger()
 
diff --git a/devices/clients/udp_receiver.py b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py
similarity index 98%
rename from devices/clients/udp_receiver.py
rename to tangostationcontrol/tangostationcontrol/clients/udp_receiver.py
index 8a9d1429945cdd5c41c47bf45edc5034c1cafa0c..2bda038a1fb0646af2d2e14082e10ca3d7866816 100644
--- a/devices/clients/udp_receiver.py
+++ b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py
@@ -7,7 +7,7 @@ import socket
 import time
 from typing import List # not needed for python3.9+, where we can use the type "list[Queue]" directly
 
-from clients.statistics_client_thread import StatisticsClientThread
+from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread
 
 logger = logging.getLogger()
 
diff --git a/devices/devices/sdp/__init__.py b/tangostationcontrol/tangostationcontrol/common/__init__.py
similarity index 100%
rename from devices/devices/sdp/__init__.py
rename to tangostationcontrol/tangostationcontrol/common/__init__.py
diff --git a/devices/common/baselines.py b/tangostationcontrol/tangostationcontrol/common/baselines.py
similarity index 100%
rename from devices/common/baselines.py
rename to tangostationcontrol/tangostationcontrol/common/baselines.py
diff --git a/tangostationcontrol/tangostationcontrol/common/entrypoint.py b/tangostationcontrol/tangostationcontrol/common/entrypoint.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5b9d4952ee08180162b3bdca7417fff31f456f0
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/common/entrypoint.py
@@ -0,0 +1,21 @@
+import sys
+
+from tango.server import run
+
+from tangostationcontrol.common.lofar_logging import configure_logger
+
+
+def entry(Device, **kwargs):
+    """General device entrypoint"""
+
+    # Remove first argument which is filename
+    args = sys.argv[1:]
+
+    # Setup logging
+    configure_logger()
+
+    # Start the device server
+    if type(Device) is tuple:
+        return run(Device, args=args, **kwargs)
+    else:
+        return run((Device,), args=args, **kwargs)
diff --git a/devices/common/lofar_logging.py b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
similarity index 99%
rename from devices/common/lofar_logging.py
rename to tangostationcontrol/tangostationcontrol/common/lofar_logging.py
index 826e484e6e2bd321c343b814ccb734472e8bf73c..46d417c67987b1cec37897552ad35446ac7fdae1 100644
--- a/devices/common/lofar_logging.py
+++ b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
@@ -1,12 +1,11 @@
 import logging
 from functools import wraps
 from tango.server import Device
-import sys
 import traceback
 import socket
 import time
 
-from .lofar_git import get_version
+from .lofar_version import get_version
 
 class TangoLoggingHandler(logging.Handler):
     level_to_device_stream = {
diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_version.py b/tangostationcontrol/tangostationcontrol/common/lofar_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..89cb22f9fe6b76caf438984c673b21d00bc25645
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/common/lofar_version.py
@@ -0,0 +1,103 @@
+import git
+import os
+import functools
+import pkg_resources
+import re
+
+def get_repo(starting_directory: str = os.path.dirname(os.path.abspath(__file__)), limit = 10) -> git.Repo:
+    """ Try finding the repository by traversing up the tree.
+
+        By default, the repository containing this module is returned.
+    """
+
+    directory = starting_directory
+
+    try:
+        return git.Repo(directory)
+    except git.InvalidGitRepositoryError:
+        pass
+
+    # We now have to traverse up the tree up until limit diretories
+    for i in range(limit):
+        if directory == "/" or not os.path.exists(directory):
+            break
+
+        directory = os.path.abspath(directory + os.path.sep + "..")
+
+        try:
+            return git.Repo(directory)
+        except git.InvalidGitRepositoryError:
+            pass
+
+    # Could not find a repo within the limit so return None
+    return None
+
+
+@functools.lru_cache(maxsize=None)
+def get_version(repo: git.Repo = None) -> str:
+    """ Return a version string for the current commit.
+
+    There is a practical issue: the repository changes over time, f.e. switching branches with 'git checkout'. We want
+    to know the version that is running in memory, not the one that is on disk.
+
+    As a work-around, we cache the version information, in that it is at least consistent. It is up to the caller
+    to request the version early enough. 
+    
+    The version string is of the following pattern:
+       - ${MAJOR}.${MINOR}.${PATCH}[.${BRANCH}$.{COMMIT}][.dirty]
+
+    For releases only ${MAJOR}.${MINOR}.${PATCH} should be set. Versioning is
+    achieved by tagging commits using the `v${MAJOR}.${MINOR}.${PATCH}` pattern.
+    The leading `v` is none optional!
+       
+    """
+
+    if repo is None:
+        repo = get_repo()
+
+    # When we can't find a git repo anymore, we must be packaged. Extract the
+    # package version directly
+    if repo is None:
+        try:
+            return pkg_resources.require("tangostationcontrol")[0].version
+        except Exception:
+            pass
+
+    # Filter all tags so that they must match vMAJOR.MINOR.PATCH or
+    # vMAJOR.MINOR.PATCH.BRANCHCOMMIT
+    reg = re.compile(r'^v[0-9](\.[0-9]){2}(\.[a-z]*[0-9]*)?')
+
+    commit = repo.commit()
+    filtered_tags = [tag.name for tag in repo.tags if reg.search(tag.name)]
+    # Order tags from newest to oldest
+    tags = {tag.commit: tag for tag in reversed(repo.tags) if tag.name in filtered_tags}
+
+    # Find closest tag for commit
+    closest_tag = type('',(object,),{"name": 'v0.0.0'})()
+    for item in commit.iter_items(repo, commit):
+        if item.type == 'commit' and item in tags:
+            closest_tag = tags[item]
+            break
+
+    if commit in tags:
+        # a tag = production ready
+        commit_str = "{}".format(tags[commit].name[1:])
+    elif repo.head.is_detached:
+        # no active branch
+        commit_str = "{}.{}".format(closest_tag.name[1:], commit)
+    else:
+        # HEAD of a branch
+        branch = repo.active_branch
+        commit_str = "{}.{}.{}".format(closest_tag.name[1:], branch, commit)
+
+    return "{}{}".format(commit_str, ".dirty" if repo.is_dirty() else "")
+
+# at least cache the current repo version immediately
+try:
+    _ = get_version()
+except:
+    pass
+
+
+def main(args=None, **kwargs):
+    print(get_version())
diff --git a/devices/examples/__init__.py b/tangostationcontrol/tangostationcontrol/devices/__init__.py
similarity index 100%
rename from devices/examples/__init__.py
rename to tangostationcontrol/tangostationcontrol/devices/__init__.py
diff --git a/devices/devices/abstract_device.py b/tangostationcontrol/tangostationcontrol/devices/abstract_device.py
similarity index 100%
rename from devices/devices/abstract_device.py
rename to tangostationcontrol/tangostationcontrol/devices/abstract_device.py
diff --git a/devices/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py
similarity index 100%
rename from devices/devices/apsct.py
rename to tangostationcontrol/tangostationcontrol/devices/apsct.py
diff --git a/devices/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py
similarity index 100%
rename from devices/devices/apspu.py
rename to tangostationcontrol/tangostationcontrol/devices/apspu.py
diff --git a/devices/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py
similarity index 93%
rename from devices/devices/boot.py
rename to tangostationcontrol/tangostationcontrol/devices/boot.py
index 5d9540aa52ffa1f177b3cd615110676f73466343..40fb6f5f4fb6f8648adf8897340278fed2040f43 100644
--- a/devices/devices/boot.py
+++ b/tangostationcontrol/tangostationcontrol/devices/boot.py
@@ -13,12 +13,6 @@ Boots the rest of the station software.
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango import DebugIt
 from tango.server import run, command
@@ -29,10 +23,9 @@ import numpy
 
 from device_decorators import *
 
-from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
-from common.lofar_logging import device_logging_to_python, log_exceptions
-from common.lofar_git import get_version
+from tangostationcontrol.common.entry import entry
+from tangostationcontrol.devices.hardware_device import hardware_device
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 
 import logging
 logger = logging.getLogger()
@@ -294,14 +287,6 @@ class Boot(hardware_device):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
-    """Main function of the RECV module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((Boot,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+def main(**kwargs):
+    """Main function of the Boot module."""
+    return entry(Boot, **kwargs)
diff --git a/devices/devices/device_decorators.py b/tangostationcontrol/tangostationcontrol/devices/device_decorators.py
similarity index 100%
rename from devices/devices/device_decorators.py
rename to tangostationcontrol/tangostationcontrol/devices/device_decorators.py
diff --git a/devices/devices/docker_device.py b/tangostationcontrol/tangostationcontrol/devices/docker_device.py
similarity index 91%
rename from devices/devices/docker_device.py
rename to tangostationcontrol/tangostationcontrol/devices/docker_device.py
index f8a83dd41f9a81a217dcab9a2dc54201ecff1025..d00ce507d85fcb6333ab913c5c9678da47736108 100644
--- a/devices/devices/docker_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/docker_device.py
@@ -11,14 +11,7 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
-from tango import DebugIt
 from tango.server import run, command
 from tango.server import device_property, attribute
 from tango import AttrWriteType
@@ -28,10 +21,12 @@ import asyncio
 
 from device_decorators import *
 
-from clients.docker_client import DockerClient
-from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
-from common.lofar_logging import device_logging_to_python, log_exceptions
+# Additional import
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.clients.docker_client import DockerClient
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.hardware_device import hardware_device
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 
 __all__ = ["Docker", "main"]
 
@@ -131,14 +126,6 @@ class Docker(hardware_device):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
+def main(**kwargs):
     """Main function of the Docker module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((Docker,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(Docker, **kwargs)
diff --git a/devices/devices/hardware_device.py b/tangostationcontrol/tangostationcontrol/devices/hardware_device.py
similarity index 95%
rename from devices/devices/hardware_device.py
rename to tangostationcontrol/tangostationcontrol/devices/hardware_device.py
index 89cc9f58cbd592c5321302abfb184bfd4dc72067..9ebff00d7554040425e3fdd6630e0a833d48fa4f 100644
--- a/devices/devices/hardware_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/hardware_device.py
@@ -15,17 +15,19 @@ from abc import abstractmethod
 
 # PyTango imports
 from tango.server import Device, command, DeviceMeta, attribute
-from tango import DevState, DebugIt, Attribute, DeviceProxy, AttrWriteType
-# Additional import
+from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy
 
-from clients.attribute_wrapper import attribute_wrapper
-from common.lofar_logging import log_exceptions
-from common.lofar_git import get_version
-from devices.abstract_device import AbstractDeviceMetas
-from devices.device_decorators import only_in_states, fault_on_error
 import time
 import math
 
+# Additional import
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.common.lofar_logging import log_exceptions
+from tangostationcontrol.common.lofar_version import get_version
+from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas
+from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error
+
+
 __all__ = ["hardware_device"]
 
 import logging
@@ -279,4 +281,3 @@ class hardware_device(Device, metaclass=AbstractDeviceMetas):
             time.sleep(pollperiod)
 
         raise Exception(f"{attr} != {value} after f{timeout} seconds still.")
-
diff --git a/devices/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
similarity index 89%
rename from devices/devices/observation.py
rename to tangostationcontrol/tangostationcontrol/devices/observation.py
index fc69158c7215b29c3fd05c204032a15661ec3e15..0c38ed93c76f0e03aa71de0a0d880d8ede6d8251 100644
--- a/devices/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -5,22 +5,16 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango import server, Except, DevState, AttrWriteType, DevString, DebugIt
 from tango.server import Device, run, command, attribute
 import numpy
 from time import time
 
-from devices.device_decorators import *
-from common.lofar_logging import device_logging_to_python, log_exceptions
-from common.lofar_git import get_version
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.common.lofar_version import get_version
+from tangostationcontrol.devices.device_decorators import *
 
 from json import loads
 
@@ -117,10 +111,6 @@ class Observation(Device):
 # ----------
 # Run server
 # ----------
-def main(args = None, **kwargs):
+def main(**kwargs):
     """Main function of the ObservationControl module."""
-    return run((Observation,), args = args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(Observation, **kwargs)
diff --git a/devices/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
similarity index 97%
rename from devices/devices/observation_control.py
rename to tangostationcontrol/tangostationcontrol/devices/observation_control.py
index 42a8c5d8298df0ec215f862598fd8dc71fb9eaff..0383b9ab7e0b69e54feddd40af2bdc6eb4da3bb0 100644
--- a/devices/devices/observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
@@ -5,13 +5,6 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango import Except, DevFailed, DevState, AttrWriteType, DebugIt, DeviceProxy, Util, DevBoolean, DevString
 from tango.server import Device, run, command, device_property, attribute
@@ -21,11 +14,11 @@ import numpy
 import time
 from json import loads
 
-from devices.device_decorators import *
-from common.lofar_logging import device_logging_to_python, log_exceptions
-from common.lofar_git import get_version
-
-from observation import Observation
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.common.lofar_version import get_version
+from tangostationcontrol.devices.device_decorators import *
+from tangostationcontrol.devices.observation import Observation
 
 
 __all__ = ["ObservationControl", "main"]
@@ -445,10 +438,6 @@ class ObservationControl(Device):
 # ----------
 # Run server
 # ----------
-def main(args = None, **kwargs):
+def main(**kwargs):
     """Main function of the ObservationControl module."""
-    return run((ObservationControl, Observation), verbose = True, args = args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry((ObservationControl, Observation), verbose=True, **kwargs)
diff --git a/devices/devices/opcua_device.py b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py
similarity index 92%
rename from devices/devices/opcua_device.py
rename to tangostationcontrol/tangostationcontrol/devices/opcua_device.py
index fd49b90f122868a0741ebe91ba69d71a434143d6..801c71c09a96fd631db23a57ca123f3ceaebd843 100644
--- a/devices/devices/opcua_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py
@@ -25,11 +25,10 @@ import numpy
 import asyncio
 # Additional import
 
-from devices.device_decorators import *
-
-from clients.opcua_client import OPCUAConnection
-from devices.hardware_device import hardware_device
-from common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.clients.opcua_client import OPCUAConnection
+from tangostationcontrol.devices.device_decorators import *
+from tangostationcontrol.devices.hardware_device import hardware_device
 
 __all__ = ["opcua_device", "main"]
 
diff --git a/devices/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py
similarity index 94%
rename from devices/devices/recv.py
rename to tangostationcontrol/tangostationcontrol/devices/recv.py
index b5cc7f6e7cd6031d290f0d2509ec651dc181765a..a9eee4160f9b7a6f981119c58ad2e707df0cf717 100644
--- a/devices/devices/recv.py
+++ b/tangostationcontrol/tangostationcontrol/devices/recv.py
@@ -11,25 +11,19 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango import DebugIt
 from tango.server import run, command
 from tango.server import device_property, attribute
 from tango import AttrWriteType
 import numpy
-# Additional import
 
-from device_decorators import *
-
-from clients.attribute_wrapper import attribute_wrapper
-from devices.opcua_device import opcua_device
-from common.lofar_logging import device_logging_to_python, log_exceptions
+# Additional import
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.device_decorators import *
+from tangostationcontrol.devices.opcua_device import opcua_device
 
 import logging
 logger = logging.getLogger()
@@ -234,14 +228,6 @@ class RECV(opcua_device):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
+def main(**kwargs):
     """Main function of the RECV module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((RECV,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(RECV, **kwargs)
diff --git a/devices/integration_test/__init__.py b/tangostationcontrol/tangostationcontrol/devices/sdp/__init__.py
similarity index 100%
rename from devices/integration_test/__init__.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/__init__.py
diff --git a/devices/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
similarity index 94%
rename from devices/devices/sdp/sdp.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
index d41d50af989c47ec0ee2fac3f0349bf8f00a3d53..4c4b03f8a75d026f2f322ea9b301b9d2b537a6ba 100644
--- a/devices/devices/sdp/sdp.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
@@ -11,28 +11,22 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango.server import device_property, attribute
 from tango import AttrWriteType
-# Additional import
-
-from clients.attribute_wrapper import attribute_wrapper
-from devices.opcua_device import opcua_device
 
-from common.lofar_logging import device_logging_to_python, log_exceptions
+# Additional import
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.opcua_device import opcua_device
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 
 import numpy
 
 __all__ = ["SDP", "main"]
 
+
 @device_logging_to_python()
 class SDP(opcua_device):
     # -----------------
@@ -77,7 +71,7 @@ class SDP(opcua_device):
         mandatory=False,
         default_value=[[0.0] * 12] * 16
     )
-    
+
     FPGA_sdp_info_station_id_RW_default = device_property(
         dtype='DevVarULongArray',
         mandatory=False,
@@ -167,7 +161,6 @@ class SDP(opcua_device):
     FPGA_bsn_monitor_input_nof_valid_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_nof_valid_R"], datatype=numpy.int32, dims=(N_pn,))
     FPGA_bsn_monitor_input_nof_err_R = attribute_wrapper(comms_annotation=["FPGA_bsn_monitor_input_nof_err_R"], datatype=numpy.int32, dims=(N_pn,))
 
-
     # --------
     # overloaded functions
     # --------
@@ -179,14 +172,6 @@ class SDP(opcua_device):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
+def main(**kwargs):
     """Main function of the SDP module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((SDP,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(SDP, **kwargs)
diff --git a/devices/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
similarity index 88%
rename from devices/devices/sdp/sst.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
index d790170bb997f5e69044a2ef45754e04b9d62d1c..18f000697b5351487ce4c75c0796e2cc81c28740 100644
--- a/devices/devices/sdp/sst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
@@ -11,29 +11,24 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango.server import device_property, attribute
 from tango import AttrWriteType
 # Additional import
 
-from clients.attribute_wrapper import attribute_wrapper
-from clients.statistics_client import StatisticsClient
-from clients.opcua_client import OPCUAConnection
-from devices.sdp.statistics import Statistics
-from devices.sdp.statistics_collector import SSTCollector
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.clients.opcua_client import OPCUAConnection
+from tangostationcontrol.clients.statistics_client import StatisticsClient
+from tangostationcontrol.devices.sdp.statistics import Statistics
+from tangostationcontrol.devices.sdp.statistics_collector import SSTCollector
 
 import numpy
 
 __all__ = ["SST", "main"]
 
+
 class SST(Statistics):
 
     STATISTICS_COLLECTOR_CLASS = SSTCollector
@@ -120,14 +115,6 @@ class SST(Statistics):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
+def main(**kwargs):
     """Main function of the SST Device module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((SST,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(SST, **kwargs)
diff --git a/devices/devices/sdp/statistics.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
similarity index 92%
rename from devices/devices/sdp/statistics.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
index af1cf0201fd4dc244b8495730660b7c84398a518..aa56e71f047ebd8412deb77fa315dd6e2a7d8c16 100644
--- a/devices/devices/sdp/statistics.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
@@ -11,27 +11,20 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 from abc import ABCMeta, abstractmethod
 
 # PyTango imports
 from tango.server import device_property, attribute
 from tango import AttrWriteType
+
 # Additional import
 import asyncio
 
-from clients.statistics_client import StatisticsClient
-from clients.attribute_wrapper import attribute_wrapper
-
-from devices.opcua_device import opcua_device
+from tangostationcontrol.clients.statistics_client import StatisticsClient
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.opcua_device import opcua_device
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 
-from common.lofar_logging import device_logging_to_python, log_exceptions
 import logging
 
 logger = logging.getLogger()
diff --git a/devices/devices/sdp/statistics_collector.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py
similarity index 98%
rename from devices/devices/sdp/statistics_collector.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py
index d9e5668b7e9b3db288a4b2360f4fa298594bbc1c..29503ca58ef25fcd3cb0b4ced4ff09ddaa62ecc6 100644
--- a/devices/devices/sdp/statistics_collector.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py
@@ -4,8 +4,8 @@ import logging
 import numpy
 
 from .statistics_packet import SSTPacket, XSTPacket
-from common.baselines import nr_baselines, baseline_index, baseline_from_index
-from clients.statistics_client_thread import StatisticsClientThread
+from tangostationcontrol.common.baselines import nr_baselines, baseline_index, baseline_from_index
+from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread
 
 logger = logging.getLogger()
 
@@ -184,6 +184,7 @@ class XSTCollector(StatisticsCollector):
 
         # process the packet
         self.parameters["nof_valid_payloads"][fields.gn_index] += numpy.uint64(1)
+
         self.parameters["xst_blocks"][block_index][:fields.nof_statistics_per_packet] = fields.payload
         self.parameters["xst_timestamps"][block_index]        = numpy.float64(fields.timestamp().timestamp())
         self.parameters["xst_conjugated"][block_index]        = conjugated
diff --git a/devices/devices/sdp/statistics_packet.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py
similarity index 99%
rename from devices/devices/sdp/statistics_packet.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py
index 9bac227071dfbdec9ea0b0fd1fa63fa36176a8d9..c98ae9b5bdc604e8a55480cc5473e658b10cefa1 100644
--- a/devices/devices/sdp/statistics_packet.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_packet.py
@@ -1,6 +1,5 @@
 import struct
 from datetime import datetime, timezone
-from typing import Tuple
 import numpy
 
 __all__ = ["StatisticsPacket", "SSTPacket", "XSTPacket", "BSTPacket"]
@@ -331,7 +330,7 @@ class BSTPacket(StatisticsPacket):
         return header
 
 
-if __name__ == "__main__":
+def main(args=None, **kwargs):
     # parse one packet from stdin
     import sys
     import pprint
diff --git a/devices/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
similarity index 90%
rename from devices/devices/sdp/xst.py
rename to tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
index 1740563c916115373d435896c075815a477795ea..dcbda73c6570f6db966eaf5518e2129ecea41187 100644
--- a/devices/devices/sdp/xst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
@@ -11,27 +11,19 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango.server import device_property, attribute
 from tango import AttrWriteType
-# Additional import
 
-from clients.attribute_wrapper import attribute_wrapper
-from clients.statistics_client import StatisticsClient
-from clients.opcua_client import OPCUAConnection
-
-from common.lofar_logging import device_logging_to_python, log_exceptions
+# Additional import
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.clients.opcua_client import OPCUAConnection
+from tangostationcontrol.clients.statistics_client import StatisticsClient
 
-from devices.sdp.statistics import Statistics
-from devices.sdp.statistics_collector import XSTCollector
+from tangostationcontrol.devices.sdp.statistics import Statistics
+from tangostationcontrol.devices.sdp.statistics_collector import XSTCollector
 
 import numpy
 
@@ -160,14 +152,6 @@ class XST(Statistics):
 # ----------
 # Run server
 # ----------
-def main(args=None, **kwargs):
+def main(**kwargs):
     """Main function of the XST Device module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((XST,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
+    return entry(XST, **kwargs)
diff --git a/devices/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py
similarity index 94%
rename from devices/devices/unb2.py
rename to tangostationcontrol/tangostationcontrol/devices/unb2.py
index 0feb3e53d90e84a708aeaed9a511b86975801cfa..0076a37a25c0838f8324a409f08223fab79b92f1 100644
--- a/devices/devices/unb2.py
+++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py
@@ -11,22 +11,16 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango.server import device_property, attribute
 from tango import AttrWriteType
 # Additional import
 
-from clients.attribute_wrapper import attribute_wrapper
-from devices.opcua_device import opcua_device
-
-from common.lofar_logging import device_logging_to_python, log_exceptions
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.opcua_device import opcua_device
+from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
 
 import numpy
 
@@ -132,13 +126,4 @@ class UNB2(opcua_device):
 # ----------
 def main(args=None, **kwargs):
     """Main function of the UNB2 module."""
-
-    from common.lofar_logging import configure_logger
-    configure_logger()
-
-    return run((UNB2,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
-
+    return entry(UNB2, **kwargs)
diff --git a/devices/examples/HW_device_template.py b/tangostationcontrol/tangostationcontrol/examples/HW_device_template.py
similarity index 85%
rename from devices/examples/HW_device_template.py
rename to tangostationcontrol/tangostationcontrol/examples/HW_device_template.py
index 6059733023c5ee8448369a706ea2be5c8fa6b840..4645488405ac1dc23acde36a7659e3b9182c57a7 100644
--- a/devices/examples/HW_device_template.py
+++ b/tangostationcontrol/tangostationcontrol/examples/HW_device_template.py
@@ -9,19 +9,11 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
-from tango import AttrWriteType
 # Additional import
 
-from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
+from tangostationcontrol.devices.hardware_device import hardware_device
 
 
 __all__ = ["HW_dev"]
@@ -90,7 +82,3 @@ class HW_dev(hardware_device):
 def main(args=None, **kwargs):
     """Main function of the hardware device module."""
     return run((HW_dev,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/devices/integration_test/client/__init__.py b/tangostationcontrol/tangostationcontrol/examples/__init__.py
similarity index 100%
rename from devices/integration_test/client/__init__.py
rename to tangostationcontrol/tangostationcontrol/examples/__init__.py
diff --git a/devices/integration_test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/__init__.py
similarity index 100%
rename from devices/integration_test/devices/__init__.py
rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/__init__.py
diff --git a/devices/examples/load_from_disk/ini_client.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py
similarity index 98%
rename from devices/examples/load_from_disk/ini_client.py
rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py
index cd227f23458c672c08b3acf08ba65fa9a48b581d..82c57a685e105527625c7dd3c0acdb77e59e1c8c 100644
--- a/devices/examples/load_from_disk/ini_client.py
+++ b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_client.py
@@ -1,4 +1,4 @@
-from clients.comms_client import CommClient
+from tangostationcontrol.clients.comms_client import CommClient
 import configparser
 import numpy
 
diff --git a/devices/examples/load_from_disk/ini_device.py b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py
similarity index 93%
rename from devices/examples/load_from_disk/ini_device.py
rename to tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py
index 07b2f419ab6b4cd5d78eb84a66c3906e169da99d..1f65d3ccd5f0827305a2d1a91e958b6e6241bc85 100644
--- a/devices/examples/load_from_disk/ini_device.py
+++ b/tangostationcontrol/tangostationcontrol/examples/load_from_disk/ini_device.py
@@ -9,26 +9,17 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango import AttrWriteType
-# Additional import
-from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
-
 
 import configparser
 import numpy
 
-from ini_client import *
-
+# Additional import
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.hardware_device import hardware_device
+from tangostationcontrol.examples.load_from_disk.ini_client import *
 
 __all__ = ["ini_device"]
 
@@ -130,10 +121,5 @@ class ini_device(hardware_device):
 def main(args=None, **kwargs):
     write_ini_file("example.ini")
 
-
     """Main function of the hardware device module."""
     return run((ini_device,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/devices/test/__init__.py b/tangostationcontrol/tangostationcontrol/examples/snmp/__init__.py
similarity index 100%
rename from devices/test/__init__.py
rename to tangostationcontrol/tangostationcontrol/examples/snmp/__init__.py
diff --git a/devices/examples/snmp/snmp.py b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py
similarity index 85%
rename from devices/examples/snmp/snmp.py
rename to tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py
index 2a912ce1443bbd8e83b662d4ed9764627d947943..845ff0937ccaaf3ad120fe6c96acfce581417f0f 100644
--- a/devices/examples/snmp/snmp.py
+++ b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp.py
@@ -11,22 +11,15 @@
 
 """
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango.server import run
 from tango.server import device_property
 from tango import AttrWriteType
 
 # Additional import
-from examples.snmp.snmp_client import SNMP_client
-from clients.attribute_wrapper import attribute_wrapper
-from devices.hardware_device import hardware_device
+from tangostationcontrol.examples.snmp.snmp_client import SNMP_client
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+from tangostationcontrol.devices.hardware_device import hardware_device
 
 import numpy
 
@@ -118,12 +111,7 @@ class SNMP(hardware_device):
 def main(args=None, **kwargs):
     """Main function of the module."""
 
-    from common.lofar_logging import configure_logger
-    import logging
-    configure_logger(logging.getLogger())
+    from tangostationcontrol.common.lofar_logging import configure_logger
+    configure_logger()
 
     return run((SNMP,), args=args, **kwargs)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/devices/examples/snmp/snmp_client.py b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py
similarity index 98%
rename from devices/examples/snmp/snmp_client.py
rename to tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py
index 96ac67140b9bdbdba7ab4d4fb8651b5e9674c219..00464f37d92800207fec524279126e494886d01a 100644
--- a/devices/examples/snmp/snmp_client.py
+++ b/tangostationcontrol/tangostationcontrol/examples/snmp/snmp_client.py
@@ -1,5 +1,5 @@
 
-from clients.comms_client import CommClient
+from tangostationcontrol.clients.comms_client import CommClient
 
 import snmp
 
diff --git a/devices/integration_test/README.md b/tangostationcontrol/tangostationcontrol/integration_test/README.md
similarity index 100%
rename from devices/integration_test/README.md
rename to tangostationcontrol/tangostationcontrol/integration_test/README.md
diff --git a/devices/test/clients/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/__init__.py
similarity index 100%
rename from devices/test/clients/__init__.py
rename to tangostationcontrol/tangostationcontrol/integration_test/__init__.py
diff --git a/devices/integration_test/base.py b/tangostationcontrol/tangostationcontrol/integration_test/base.py
similarity index 92%
rename from devices/integration_test/base.py
rename to tangostationcontrol/tangostationcontrol/integration_test/base.py
index 241f0ecd409fd16484d81e31f1e1f83dc1b9d81b..ed1eb2239af74251e22b42adf8ea5e2596688076 100644
--- a/devices/integration_test/base.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/base.py
@@ -7,7 +7,7 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from common.lofar_logging import configure_logger
+from tangostationcontrol.common.lofar_logging import configure_logger
 
 import unittest
 import asynctest
diff --git a/devices/test/common/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/client/__init__.py
similarity index 100%
rename from devices/test/common/__init__.py
rename to tangostationcontrol/tangostationcontrol/integration_test/client/__init__.py
diff --git a/devices/integration_test/client/test_sdptr_sim.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py
similarity index 92%
rename from devices/integration_test/client/test_sdptr_sim.py
rename to tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py
index ab9288b727e515c19b07c99d1fe8a233d7032055..a09f407e2982d7b873021f03b1eb9e78fe336e44 100644
--- a/devices/integration_test/client/test_sdptr_sim.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_sdptr_sim.py
@@ -9,7 +9,7 @@
 
 from asyncua import Client
 
-from integration_test import base
+from tangostationcontrol.integration_test import base
 
 
 class TestSDPTRSim(base.IntegrationAsyncTestCase):
diff --git a/devices/integration_test/client/test_tcp_replicator.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py
similarity index 97%
rename from devices/integration_test/client/test_tcp_replicator.py
rename to tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py
index ca45c4c52ab7f5e379c484b964a05225950fc9e1..2467fd6dd8a7b24cc786c5b2cc10a25a610b88f1 100644
--- a/devices/integration_test/client/test_tcp_replicator.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_tcp_replicator.py
@@ -7,18 +7,16 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from asyncio import Queue
-
 import logging
 import time
 import socket
 import sys
 
-from clients.tcp_replicator import TCPReplicator
+import timeout_decorator
 
-from integration_test import base
+from tangostationcontrol.clients.tcp_replicator import TCPReplicator
 
-import timeout_decorator
+from tangostationcontrol.integration_test import base
 
 logger = logging.getLogger()
 
diff --git a/devices/integration_test/client/test_unb2_sim.py b/tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py
similarity index 92%
rename from devices/integration_test/client/test_unb2_sim.py
rename to tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py
index d934c06fb6dfb40dad1c8b54dc00a00715deedc8..261441901c589a26256b586dc571f7b063c08408 100644
--- a/devices/integration_test/client/test_unb2_sim.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/client/test_unb2_sim.py
@@ -9,7 +9,7 @@
 
 from asyncua import Client
 
-from integration_test import base
+from tangostationcontrol.integration_test import base
 
 
 class TestUNB2Sim(base.IntegrationAsyncTestCase):
diff --git a/devices/integration_test/device_proxy.py b/tangostationcontrol/tangostationcontrol/integration_test/device_proxy.py
similarity index 100%
rename from devices/integration_test/device_proxy.py
rename to tangostationcontrol/tangostationcontrol/integration_test/device_proxy.py
diff --git a/devices/test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/__init__.py
similarity index 100%
rename from devices/test/devices/__init__.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/__init__.py
diff --git a/devices/integration_test/devices/test_device_recv.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py
similarity index 90%
rename from devices/integration_test/devices/test_device_recv.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py
index 72199e292d9a8ce5ab90c69d764d03a342bb41c9..6e6e8602e0662750b7b097c4e60f7fef0ce056a2 100644
--- a/devices/integration_test/devices/test_device_recv.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_recv.py
@@ -9,8 +9,8 @@
 
 from tango._tango import DevState
 
-from integration_test.device_proxy import TestDeviceProxy
-from integration_test import base
+from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
+from tangostationcontrol.integration_test import base
 
 
 class TestDeviceRECV(base.IntegrationTestCase):
diff --git a/devices/integration_test/devices/test_device_sdp.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py
similarity index 91%
rename from devices/integration_test/devices/test_device_sdp.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py
index 3db8b744ffc6679cfd9389640a9d27e18c3d2ca1..faf965eb00c8c57785712b6bb581d9118c4632ba 100644
--- a/devices/integration_test/devices/test_device_sdp.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sdp.py
@@ -7,10 +7,11 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
+from tango import DeviceProxy
 from tango._tango import DevState
 
-from integration_test.device_proxy import TestDeviceProxy
-from integration_test import base
+from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
+from tangostationcontrol.integration_test import base
 
 
 class TestDeviceSDP(base.IntegrationTestCase):
diff --git a/devices/integration_test/devices/test_device_sst.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py
similarity index 96%
rename from devices/integration_test/devices/test_device_sst.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py
index 759028324d5c63dea55a930c2bd0557d69f7213d..194313fd6708d01ab70bda04563ce038bb55086c 100644
--- a/devices/integration_test/devices/test_device_sst.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_sst.py
@@ -12,8 +12,8 @@ import time
 
 from tango._tango import DevState
 
-from integration_test.device_proxy import TestDeviceProxy
-from integration_test import base
+from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
+from tangostationcontrol.integration_test import base
 
 
 class TestDeviceSST(base.IntegrationTestCase):
diff --git a/devices/integration_test/devices/test_device_unb2.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py
similarity index 91%
rename from devices/integration_test/devices/test_device_unb2.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py
index 00702a36a1b480ad583d29d80da69874cc1e98af..c699e225c05861f61786f332a70ccb3191f2dc5f 100644
--- a/devices/integration_test/devices/test_device_unb2.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_unb2.py
@@ -9,8 +9,8 @@
 
 from tango._tango import DevState
 
-from integration_test.device_proxy import TestDeviceProxy
-from integration_test import base
+from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
+from tangostationcontrol.integration_test import base
 
 
 class TestDeviceUNB2(base.IntegrationTestCase):
diff --git a/devices/integration_test/devices/test_tango_database.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py
similarity index 93%
rename from devices/integration_test/devices/test_tango_database.py
rename to tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py
index 0ae65d5c51a98898e4016bbb231baa18b025dba9..b14cc363f24b3ea8e77ecd59e1184500fe40e0d4 100644
--- a/devices/integration_test/devices/test_tango_database.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_tango_database.py
@@ -12,7 +12,7 @@ import time
 from tango import Database
 from tango._tango import DevState
 
-from integration_test import base
+from tangostationcontrol.integration_test import base
 
 
 class TestTangoDatabase(base.IntegrationTestCase):
diff --git a/devices/statistics_writer/README.md b/tangostationcontrol/tangostationcontrol/statistics_writer/README.md
similarity index 100%
rename from devices/statistics_writer/README.md
rename to tangostationcontrol/tangostationcontrol/statistics_writer/README.md
diff --git a/devices/statistics_writer/SST_2021-10-04-07-36-52.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5
similarity index 100%
rename from devices/statistics_writer/SST_2021-10-04-07-36-52.h5
rename to tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/statistics_writer/hdf5_writer.py b/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py
similarity index 98%
rename from devices/statistics_writer/hdf5_writer.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py
index 6715dd870608a0202610ea52c417695844f0d1c9..eb7fa643bd9c8d74b1e946f468532c4852ecaba5 100644
--- a/devices/statistics_writer/hdf5_writer.py
+++ b/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py
@@ -11,8 +11,8 @@ import logging
 # import statistics classes with workaround
 import sys
 sys.path.append("..")
-from devices.sdp.statistics_packet import SSTPacket, XSTPacket, BSTPacket, StatisticsPacket
-import devices.sdp.statistics_collector as statistics_collector
+from tangostationcontrol.devices.sdp.statistics_packet import SSTPacket, XSTPacket, BSTPacket, StatisticsPacket
+import tangostationcontrol.devices.sdp.statistics_collector as statistics_collector
 
 
 logger = logging.getLogger("statistics_writer")
diff --git a/devices/statistics_writer/receiver.py b/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py
similarity index 96%
rename from devices/statistics_writer/receiver.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py
index 92d0d6d34bfc69f8f89f306c86b906c68956e47b..cd6c0af4cd5d4a1f8cdc3c2e37d86f6bd655db53 100644
--- a/devices/statistics_writer/receiver.py
+++ b/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py
@@ -2,7 +2,7 @@ import socket
 
 import sys
 sys.path.append("..")
-from devices.sdp.statistics_packet import StatisticsPacket
+from tangostationcontrol.devices.sdp.statistics_packet import StatisticsPacket
 import os
 
 class receiver:
diff --git a/devices/statistics_writer/statistics_reader.py b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py
similarity index 93%
rename from devices/statistics_writer/statistics_reader.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py
index f0906e7d4122b2f1d0d8d864d8c6a47ad793c0f4..67ff6377cdf90326d7a9fa33a015c1ac5861064d 100644
--- a/devices/statistics_writer/statistics_reader.py
+++ b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py
@@ -5,16 +5,9 @@ import argparse
 import os
 import psutil
 import pytz
-import time
 
 process = psutil.Process(os.getpid())
 
-parser = argparse.ArgumentParser(description='Select a file to explore')
-parser.add_argument('--files', type=str, nargs="+", help='the name and path of the files, takes one or more files')
-parser.add_argument('--start_time', type=str, help='lowest timestamp to process (uses isoformat, ex: 2021-10-04T07:50:08.937+00:00)')
-parser.add_argument('--end_time', type=str, help='highest timestamp to process (uses isoformat, ex: 2021-10-04T07:50:08.937+00:00)')
-
-
 import logging
 logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger("hdf5_explorer")
@@ -219,7 +212,20 @@ class statistics_data:
         self.values = numpy.array(file.get(f"{group_key}/values"))
 
 
-if __name__ == "__main__":
+def main():
+    parser = argparse.ArgumentParser(description='Select a file to explore')
+    parser.add_argument(
+        '--files', type=str, nargs="+", required=True,
+        help='the name and path of the files, takes one or more files')
+    parser.add_argument(
+        '--start_time', type=str, required=True,
+        help='lowest timestamp to process (uses isoformat, ex: 2021-10-04T07:50'
+             ':08.937+00:00)')
+    parser.add_argument(
+        '--end_time', type=str,  required=True,
+        help='highest timestamp to process (usesisoformat, ex: 2021-10-04T07:50'
+             ':08.937+00:00)')
+
     args = parser.parse_args()
     files = args.files
     end_time = args.end_time
diff --git a/devices/statistics_writer/statistics_writer.py b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py
similarity index 56%
rename from devices/statistics_writer/statistics_writer.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py
index 8bf8fa64fbba9626f60abec0bb6cacb7e7288c51..1a1ecb671159e1b3ca143ecbf860000d6cdbe0c5 100644
--- a/devices/statistics_writer/statistics_writer.py
+++ b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py
@@ -1,32 +1,49 @@
 import argparse
-from receiver import tcp_receiver, file_receiver
-from hdf5_writer import hdf5_writer
-
 import time
-from datetime import datetime
-
 import sys
-import signal
+
+from tangostationcontrol.statistics_writer.receiver import tcp_receiver, file_receiver
+from tangostationcontrol.statistics_writer.hdf5_writer import hdf5_writer
 
 import logging
 logging.basicConfig(level=logging.INFO, format = '%(asctime)s:%(levelname)s: %(message)s')
 logger = logging.getLogger("statistics_writer")
 
+def main():
+    parser = argparse.ArgumentParser(
+        description='Converts a stream of statistics packets into HDF5 files.')
+    parser.add_argument(
+        '-a', '--host', type=str, required=True, help='the host to connect to')
+    parser.add_argument(
+        '-p', '--port', type=int, default=0,
+        help='the port to connect to, or 0 to use default port for the '
+             'selected mode (default: %(default)s)')
+    parser.add_argument(
+        '-f', '--file', type=str, required=True, help='the file to read from')
+    parser.add_argument(
+        '-m', '--mode', type=str, choices=['SST', 'XST', 'BST'], default='SST',
+        help='sets the statistics type to be decoded options (default: '
+             '%(default)s)')
+    parser.add_argument(
+        '-i', '--interval', type=float, default=3600, nargs="?",
+        help='The time between creating new files in seconds (default: '
+             '%(default)s)')
+    parser.add_argument(
+        '-o', '--output_dir', type=str, default=".", nargs="?",
+        help='specifies the folder to write all the files (default: '
+             '%(default)s)')
+    parser.add_argument(
+        '-v', '--debug', dest='debug', action='store_true', default=False,
+        help='increase log output')
+    parser.add_argument(
+        '-d', '--decimation', type=int, default=1,
+        help='Configure the writer to only store one every n samples. Saves '
+             'storage space')
+    parser.add_argument(
+        '-r', '--reconnect', dest='reconnect', action='store_true', default=False,
+        help='Set the writer to keep trying to reconnect whenever connection '
+             'is lost. (default: %(default)s)')
 
-parser = argparse.ArgumentParser(description='Converts a stream of statistics packets into HDF5 files.')
-parser.add_argument('-a', '--host', type=str, help='The host to connect to.')
-parser.add_argument('-p', '--port', type=int, default=0, help='The port to connect to, or 0 to use default port for the selected mode. (default: %(default)s)')
-parser.add_argument('-f', '--file', type=str, help='The file to read from. (will ignore --host and --port)')
-
-parser.add_argument('-m', '--mode', type=str, choices=['SST', 'XST', 'BST'], default='SST', help='Sets the statistics type to be decoded options. (default: %(default)s)')
-parser.add_argument('-i', '--interval', type=float, default=3600, nargs="?", help='The time between creating new files in seconds. (default: %(default)s)')
-parser.add_argument('-o', '--output_dir', type=str, default=".", nargs="?", help='Specifies the folder to write all the files. (default: %(default)s)')
-parser.add_argument('-d', '--decimation', type=int, default=1, help='Configure the writer to only store one every n samples. Saves storage space.')
-parser.add_argument('-v', '--debug', dest='debug', action='store_true', default=False, help='Increase log output.')
-parser.add_argument('-r', '--reconnect', dest='reconnect', action='store_true', default=False, help='Set the writer to keep trying to reconnect whenever connection is lost. (default: %(default)s)')
-
-
-if __name__ == "__main__":
     args = parser.parse_args()
 
     # argparse arguments
diff --git a/devices/statistics_writer/test/SST_10m_test_1.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5
similarity index 100%
rename from devices/statistics_writer/test/SST_10m_test_1.h5
rename to tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5
diff --git a/devices/statistics_writer/test/SST_10m_test_2.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5
similarity index 100%
rename from devices/statistics_writer/test/SST_10m_test_2.h5
rename to tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5
diff --git a/devices/statistics_writer/test/SST_10m_test_3.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5
similarity index 100%
rename from devices/statistics_writer/test/SST_10m_test_3.h5
rename to tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin
similarity index 100%
rename from devices/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin
rename to tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin
diff --git a/devices/statistics_writer/test/test_server.py b/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py
similarity index 100%
rename from devices/statistics_writer/test/test_server.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/statistics_writer/udp_dev/udp_client.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py
similarity index 100%
rename from devices/statistics_writer/udp_dev/udp_client.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py
diff --git a/devices/statistics_writer/udp_dev/udp_server.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py
similarity index 100%
rename from devices/statistics_writer/udp_dev/udp_server.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py
diff --git a/devices/statistics_writer/udp_dev/udp_write_manager.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py
similarity index 100%
rename from devices/statistics_writer/udp_dev/udp_write_manager.py
rename to tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py
diff --git a/devices/test/README.md b/tangostationcontrol/tangostationcontrol/test/README.md
similarity index 100%
rename from devices/test/README.md
rename to tangostationcontrol/tangostationcontrol/test/README.md
diff --git a/devices/test/SDP_SST_statistics_packet.bin b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packet.bin
similarity index 100%
rename from devices/test/SDP_SST_statistics_packet.bin
rename to tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packet.bin
diff --git a/devices/test/SDP_SST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packets.bin
similarity index 100%
rename from devices/test/SDP_SST_statistics_packets.bin
rename to tangostationcontrol/tangostationcontrol/test/SDP_SST_statistics_packets.bin
diff --git a/devices/test/SDP_XST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/test/SDP_XST_statistics_packets.bin
similarity index 100%
rename from devices/test/SDP_XST_statistics_packets.bin
rename to tangostationcontrol/tangostationcontrol/test/SDP_XST_statistics_packets.bin
diff --git a/tangostationcontrol/tangostationcontrol/test/__init__.py b/tangostationcontrol/tangostationcontrol/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/test/base.py b/tangostationcontrol/tangostationcontrol/test/base.py
similarity index 91%
rename from devices/test/base.py
rename to tangostationcontrol/tangostationcontrol/test/base.py
index 1c2eff09be8e6a4034a476173944c8ec2a1fe61c..7cf3af7f8becb1f92cde139290394ea540f5d8d6 100644
--- a/devices/test/base.py
+++ b/tangostationcontrol/tangostationcontrol/test/base.py
@@ -7,7 +7,7 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from common.lofar_logging import configure_logger
+from tangostationcontrol.common.lofar_logging import configure_logger
 
 import unittest
 import testscenarios
diff --git a/tangostationcontrol/tangostationcontrol/test/clients/__init__.py b/tangostationcontrol/tangostationcontrol/test/clients/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/test/clients/test_attr_wrapper.py b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py
similarity index 99%
rename from devices/test/clients/test_attr_wrapper.py
rename to tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py
index 8711d989a67730667c10aed91de7c9929c500fcb..f1c5b652c545b31f897388ddce0c8f66df37912e 100644
--- a/devices/test/clients/test_attr_wrapper.py
+++ b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py
@@ -10,13 +10,13 @@
 from tango import DevState
 
 # Internal imports
-from test.clients.test_client import test_client
-from clients.attribute_wrapper import *
-from devices.hardware_device import *
+from tangostationcontrol.test.clients.test_client import test_client
+from tangostationcontrol.clients.attribute_wrapper import *
+from tangostationcontrol.devices.hardware_device import *
 
 # Test imports
 from tango.test_context import DeviceTestContext
-from test import base
+from tangostationcontrol.test import base
 
 import asyncio
 
diff --git a/devices/test/clients/test_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py
similarity index 98%
rename from devices/test/clients/test_client.py
rename to tangostationcontrol/tangostationcontrol/test/clients/test_client.py
index 7e002c3ad28a531b0ba16f12a22e782d4ba3bb01..ee3e0faf62b780dfb83a252dcf99897690090a35 100644
--- a/devices/test/clients/test_client.py
+++ b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py
@@ -3,7 +3,7 @@ import numpy
 
 
 # Test imports
-from clients.comms_client import CommClient
+from tangostationcontrol.clients.comms_client import CommClient
 
 
 class test_client(CommClient):
diff --git a/devices/test/clients/test_opcua_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py
similarity index 98%
rename from devices/test/clients/test_opcua_client.py
rename to tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py
index bc13dc5e1d1d04c800e35f113825015b38779cd9..c1c29ee04279bab3c943ccc35d4e3a5071345607 100644
--- a/devices/test/clients/test_opcua_client.py
+++ b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py
@@ -1,16 +1,15 @@
 import numpy
-from clients.opcua_client import OPCUAConnection
-from clients import opcua_client
-
 import asyncua
 import io
-import asyncio
 
+import asynctest
 from unittest import mock
-import unittest
 
-from test import base
-import asynctest
+
+from tangostationcontrol.clients.opcua_client import OPCUAConnection
+from tangostationcontrol.clients import opcua_client
+
+from tangostationcontrol.test import base
 
 
 class attr_props:
diff --git a/devices/test/clients/test_statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py
similarity index 86%
rename from devices/test/clients/test_statistics_client_thread.py
rename to tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py
index fd7ce0701f9d792863909b9f8ee4a9d39a2b1fd1..17f866871bd682b3f289364c16a55e5ee2010a7c 100644
--- a/devices/test/clients/test_statistics_client_thread.py
+++ b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py
@@ -10,9 +10,10 @@
 import logging
 from unittest import mock
 
-from clients.statistics_client_thread import StatisticsClientThread
+from tangostationcontrol.clients.statistics_client_thread import \
+    StatisticsClientThread
 
-from test import base
+from tangostationcontrol.test import base
 
 logger = logging.getLogger()
 
diff --git a/devices/test/clients/test_tcp_replicator.py b/tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py
similarity index 97%
rename from devices/test/clients/test_tcp_replicator.py
rename to tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py
index a9babed0eb7af7a58544b3ff7535c3113ed12ca3..04c87f1d5a15705f7d0b8ce1460141255cb02b27 100644
--- a/devices/test/clients/test_tcp_replicator.py
+++ b/tangostationcontrol/tangostationcontrol/test/clients/test_tcp_replicator.py
@@ -9,15 +9,14 @@
 
 import logging
 import time
-from queue import Queue
 from unittest import mock
 
-from clients.tcp_replicator import TCPReplicator
-from clients import tcp_replicator
+import timeout_decorator
 
-from test import base
+from tangostationcontrol.clients.tcp_replicator import TCPReplicator
+from tangostationcontrol.clients import tcp_replicator
 
-import timeout_decorator
+from tangostationcontrol.test import base
 
 logger = logging.getLogger()
 
diff --git a/tangostationcontrol/tangostationcontrol/test/common/__init__.py b/tangostationcontrol/tangostationcontrol/test/common/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/test/common/test_baselines.py b/tangostationcontrol/tangostationcontrol/test/common/test_baselines.py
similarity index 91%
rename from devices/test/common/test_baselines.py
rename to tangostationcontrol/tangostationcontrol/test/common/test_baselines.py
index 206b4ca0eccefe1012519c8236d158e52f1cdc38..25eb5d1dfffa2fca8748d74020893edfb17c2037 100644
--- a/devices/test/common/test_baselines.py
+++ b/tangostationcontrol/tangostationcontrol/test/common/test_baselines.py
@@ -7,9 +7,9 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from common import baselines
+from tangostationcontrol.common import baselines
 
-from test import base
+from tangostationcontrol.test import base
 
 
 class TestBaselines(base.TestCase):
diff --git a/devices/test/common/test_lofar_logging.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py
similarity index 97%
rename from devices/test/common/test_lofar_logging.py
rename to tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py
index c1030b28f3b9b7861e6eec9d25b4ca6ef22a0c22..5140d05e58ca370509a080211ca96f2df21fe399 100644
--- a/devices/test/common/test_lofar_logging.py
+++ b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py
@@ -7,16 +7,16 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-import git
 from unittest import mock
-
-from common import lofar_logging
 import logging
+
 from tango.server import Device
 from tango import device_server
 from tango.test_context import DeviceTestContext
 
-from test import base
+from tangostationcontrol.common import lofar_logging
+
+from tangostationcontrol.test import base
 
 
 class TestLofarLogging(base.TestCase):
diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..89ac894d9388d3939671c43239033ce1147fef30
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of the LOFAR 2.0 Station Software
+#
+#
+#
+# Distributed under the terms of the APACHE license.
+# See LICENSE.txt for more info.
+
+import git
+from unittest import mock
+
+from tangostationcontrol.common import lofar_version
+
+from tangostationcontrol.test import base
+
+
+class TestLofarVersion(base.TestCase):
+
+    def setUp(self):
+        super(TestLofarVersion, self).setUp()
+
+        # Clear the cache as this function of lofar_version uses LRU decorator
+        # This is a good demonstration of how unit tests in Python can have
+        # permanent effects, typically fixtures are needed to restore these.
+        lofar_version.get_version.cache_clear()
+
+    def test_get_version(self):
+        """Test if attributes of get_repo are correctly used by get_version"""
+
+        with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
+            m_commit = mock.Mock()
+            m_commit.return_value.__str__ = mock.Mock(return_value="123456")
+            m_commit.return_value.iter_items.return_value = []
+
+            m_is_dirty = mock.Mock()
+            m_is_dirty.return_value = False
+
+            m_head = mock.Mock(is_detached=False)
+
+            m_get_repo.return_value = mock.Mock(
+                active_branch="main", commit=m_commit, tags=[],
+                is_dirty=m_is_dirty, head=m_head)
+
+            # No need for special string equal in Python
+            self.assertEqual("0.0.0.main.123456", lofar_version.get_version())
+
+    def test_get_version_tag(self):
+        """Test if get_version determines production_ready for tagged commit"""
+
+        with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
+            m_commit = mock.Mock()
+            m_commit.return_value.__str__ = mock.Mock(return_value="123456")
+            m_commit.return_value.iter_items.return_value = []
+
+            m_is_dirty = mock.Mock()
+            m_is_dirty.return_value = False
+
+            m_head = mock.Mock(is_detached=False)
+
+            m_tag_commit = mock.Mock(type="commit")
+            m_tag_commit.__str__ = mock.Mock(return_value="123456")
+
+            m_tag = mock.Mock(commit=m_tag_commit)
+            m_tag.name = "v0.0.3"
+            m_tag.__str__ = mock.Mock(return_value= "v0.0.3")
+
+            m_commit.return_value = m_tag_commit
+            m_commit.return_value.iter_items.return_value = [m_tag_commit]
+
+            m_get_repo.return_value = mock.Mock(
+                active_branch="main", commit=m_commit,
+                tags=[m_tag], is_dirty=m_is_dirty, head=m_head)
+
+            self.assertEqual("0.0.3", lofar_version.get_version())
+
+    @mock.patch.object(lofar_version, 'get_repo')
+    def test_get_version_tag_dirty(self, m_get_repo):
+
+        """Test if get_version determines dirty tagged commit"""
+        m_commit = mock.Mock()
+        m_commit.return_value.__str__ = mock.Mock(return_value="123456")
+        m_commit.return_value.iter_items.return_value = []
+
+        m_is_dirty = mock.Mock()
+        m_is_dirty.return_value = True
+
+        m_head = mock.Mock(is_detached=False)
+
+        m_get_repo.return_value = mock.Mock(
+            active_branch="main", commit=m_commit, tags=[],
+            is_dirty=m_is_dirty, head=m_head)
+
+        # No need for special string equal in Python
+        self.assertEqual("0.0.0.main.123456.dirty", lofar_version.get_version())
+
+    def test_catch_repo_error(self):
+        """Test if invalid git directories will raise error"""
+
+        with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
+
+            # Configure lofar_version.get_repo to raise InvalidGitRepositoryError
+            m_get_repo.side_effect = git.InvalidGitRepositoryError
+
+            # Test that error is raised by get_version
+            self.assertRaises(
+                git.InvalidGitRepositoryError, lofar_version.get_version)
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/__init__.py b/tangostationcontrol/tangostationcontrol/test/devices/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/test/devices/random_data.py b/tangostationcontrol/tangostationcontrol/test/devices/random_data.py
similarity index 97%
rename from devices/test/devices/random_data.py
rename to tangostationcontrol/tangostationcontrol/test/devices/random_data.py
index 43e1a037624a516f88d05d644fd86e23fab6baa8..51d7b269f99c814cb340dc9f0e8feb44f3393f94 100644
--- a/devices/test/devices/random_data.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/random_data.py
@@ -7,13 +7,6 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-# TODO(Corne): Remove sys.path.append hack once packaging is in place!
-import os, sys
-currentdir = os.path.dirname(os.path.realpath(__file__))
-parentdir = os.path.dirname(currentdir)
-parentdir = os.path.dirname(parentdir)
-sys.path.append(parentdir)
-
 # PyTango imports
 from tango import DevState
 from tango.server import run, Device, attribute, command
@@ -497,6 +490,3 @@ def main(args = None, **kwargs):
     Main function of the RandomData module.
     """
     return run((Random_Data,), args = args, **kwargs)
-
-if __name__ == '__main__':
-    main()
diff --git a/devices/test/devices/test_abstract_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py
similarity index 95%
rename from devices/test/devices/test_abstract_device.py
rename to tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py
index f54383c9a1b85f5c9e442f51d7f04d061951f772..469ea19fa970cf48c2de9c4c6b5648bd7b756040 100644
--- a/devices/test/devices/test_abstract_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py
@@ -16,9 +16,9 @@ from tango.server import attribute
 
 from tango.test_context import DeviceTestContext
 
-from devices.abstract_device import AbstractDeviceMetas
+from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas
 
-from test import base
+from tangostationcontrol.test import base
 
 
 class TestAbstractDevice(base.TestCase):
diff --git a/devices/test/devices/test_statistics_collector.py b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py
similarity index 96%
rename from devices/test/devices/test_statistics_collector.py
rename to tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py
index 5fe4e24dabbf169664b19250cba13f19b8020327..4b58141c06d9b09d68dba295b007b41081ca3618 100644
--- a/devices/test/devices/test_statistics_collector.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_collector.py
@@ -1,7 +1,7 @@
-from devices.sdp.statistics_collector import XSTCollector
-from devices.sdp.statistics_packet import XSTPacket
+from tangostationcontrol.devices.sdp.statistics_collector import XSTCollector
+from tangostationcontrol.devices.sdp.statistics_packet import XSTPacket
 
-from test import base
+from tangostationcontrol.test import base
 
 class TestXSTCollector(base.TestCase):
     def test_valid_packet(self):
diff --git a/devices/toolkit/README.md b/tangostationcontrol/tangostationcontrol/toolkit/README.md
similarity index 100%
rename from devices/toolkit/README.md
rename to tangostationcontrol/tangostationcontrol/toolkit/README.md
diff --git a/tangostationcontrol/tangostationcontrol/toolkit/__init__.py b/tangostationcontrol/tangostationcontrol/toolkit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/toolkit/archiver.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py
similarity index 99%
rename from devices/toolkit/archiver.py
rename to tangostationcontrol/tangostationcontrol/toolkit/archiver.py
index 35012767add234b2b0421b359bfacf19ca6b9155..f4a0974f82a18b213746264be133911893ab54ee 100644
--- a/devices/toolkit/archiver.py
+++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py
@@ -3,7 +3,9 @@
 #from logging import raiseExceptions
 import logging
 import traceback
-from clients.attribute_wrapper import attribute_wrapper
+
+from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
+
 from tango import DeviceProxy, AttributeProxy
 from datetime import datetime, timedelta
 
diff --git a/devices/toolkit/archiver_base.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py
similarity index 100%
rename from devices/toolkit/archiver_base.py
rename to tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py
diff --git a/devices/toolkit/archiver_config/lofar2.json b/tangostationcontrol/tangostationcontrol/toolkit/archiver_config/lofar2.json
similarity index 100%
rename from devices/toolkit/archiver_config/lofar2.json
rename to tangostationcontrol/tangostationcontrol/toolkit/archiver_config/lofar2.json
diff --git a/devices/toolkit/get_internal_attribute_history.py b/tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py
similarity index 99%
rename from devices/toolkit/get_internal_attribute_history.py
rename to tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py
index 735be01613611d73f39678f6b0ed5fb1f1c56a70..8b6b4254f6c10dc8207232b16fd5e22d44e7c556 100644
--- a/devices/toolkit/get_internal_attribute_history.py
+++ b/tangostationcontrol/tangostationcontrol/toolkit/get_internal_attribute_history.py
@@ -1,10 +1,8 @@
 #! /usr/bin/env python3
 
-
 from tango import DeviceProxy
 from numpy import array, transpose
 
-
 def get_internal_attribute_history(device: DeviceProxy, attribute_name: str, depth: int = 10):
     try:
         history = array(device.attribute_history(attr_name = attribute_name, depth = depth))
diff --git a/devices/toolkit/lofar2_config.py b/tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py
similarity index 99%
rename from devices/toolkit/lofar2_config.py
rename to tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py
index 581eea4f73a4d276613123ec9bf86bdb7e97a0ea..811f4f27abbbac832c6de6811a223a27229e9032 100644
--- a/devices/toolkit/lofar2_config.py
+++ b/tangostationcontrol/tangostationcontrol/toolkit/lofar2_config.py
@@ -2,7 +2,6 @@
 
 import logging
 
-
 def configure_logging():
     # Always also log the hostname because it makes the origin of the log clear.
     import socket
diff --git a/devices/toolkit/udp_simulator.py b/tangostationcontrol/tangostationcontrol/toolkit/udp_simulator.py
similarity index 100%
rename from devices/toolkit/udp_simulator.py
rename to tangostationcontrol/tangostationcontrol/toolkit/udp_simulator.py
diff --git a/devices/test-requirements.txt b/tangostationcontrol/test-requirements.txt
similarity index 100%
rename from devices/test-requirements.txt
rename to tangostationcontrol/test-requirements.txt
diff --git a/devices/tox.ini b/tangostationcontrol/tox.ini
similarity index 95%
rename from devices/tox.ini
rename to tangostationcontrol/tox.ini
index 59d2347f3ff42ccb084033aea67d478fd63513cb..69782d5e33c1ab14ce9abcd50253e0db9eabdf9a 100644
--- a/devices/tox.ini
+++ b/tangostationcontrol/tox.ini
@@ -22,7 +22,7 @@ commands = stestr run {posargs}
 ; Warning running integration tests will make changes to your docker system!
 ; These tests should only be run by the integration-test docker container.
 passenv = TANGO_HOST
-setenv = TESTS_DIR=./integration_test
+setenv = TESTS_DIR=./tangostationcontrol/integration_test
 commands =
     stestr run --serial {posargs}