diff --git a/.gitignore b/.gitignore
index cfd4dc461a50e0a01b60ca0f88152e9ca9a2d787..f777364050f38eddf7f7867a0326b5dd3199074c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ tangostationcontrol/docs/build
 **/.eggs
 
 docker-compose/alerta-web/alerta-secrets.json
+docker-compose/tmp
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0ea0331e30bc29d9361e4189752478a6dd9b9057..fcecc7c684ba15b7c8437b5c38b92276d70aaf6f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -550,8 +550,10 @@ unit_test:
         path: tangostationcontrol/coverage.xml
     paths:
       - tangostationcontrol/cover/*
+      - tangostationcontrol/.coverage
 integration_test_docker:
   stage: integration-tests
+  allow_failure: true
   image: docker:latest
   tags:
     - privileged
@@ -559,6 +561,8 @@ integration_test_docker:
     - name: docker:dind
   variables:
     DOCKER_TLS_CERTDIR: "/certs"
+  needs:
+    - unit_test
   artifacts:
     when: always
     paths:
@@ -572,6 +576,7 @@ integration_test_docker:
         tag="$CI_COMMIT_REF_SLUG"
         echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
       fi
+    - apk update
     - apk add --update make bash docker-compose
     - apk add --update bind-tools
     - apk add --update postgresql14-client gzip
diff --git a/CDB/stations/DTS_Outside_ConfigDb.json b/CDB/stations/DTS_Outside_ConfigDb.json
index 7e6c49fa6f8f1631cf5955914baca1ecfb4e04e0..258ac6a1c2436ecd25c3f7cd9993277895658f6c 100644
--- a/CDB/stations/DTS_Outside_ConfigDb.json
+++ b/CDB/stations/DTS_Outside_ConfigDb.json
@@ -18,25 +18,25 @@
                             "Initialise_Hardware": [
                                 "True"
                             ],
-			    "Device_Names" : [
+                            "Device_Names": [
                                 "STAT/Docker/1",
-                       		"STAT/PSOC/1",
-                       		"STAT/PCON/1",
-                       		"STAT/APSPU/1",
-                       		"STAT/APSCT/1",
-                       		"STAT/CCD/1",
-                       		"STAT/RECV/1",
-                       		"STAT/UNB2/1",
-                       		"STAT/SDP/1",
-                       		"STAT/BST/1",
-                       		"STAT/SST/1",
-                       		"STAT/XST/1",
-                       		"STAT/Beamlet/1",
-                       		"STAT/AntennaField/1",
-                       		"STAT/TileBeam/2",
-                       		"STAT/DigitalBeam/1",
-                       		"STAT/TemperatureManager/1"
-				]
+                                "STAT/PSOC/1",
+                                "STAT/PCON/1",
+                                "STAT/APSPU/1",
+                                "STAT/APSCT/1",
+                                "STAT/CCD/1",
+                                "STAT/RECV/1",
+                                "STAT/UNB2/1",
+                                "STAT/SDP/1",
+                                "STAT/BST/1",
+                                "STAT/SST/1",
+                                "STAT/XST/1",
+                                "STAT/Beamlet/1",
+                                "STAT/AntennaField/1",
+                                "STAT/TileBeam/2",
+                                "STAT/DigitalBeam/1",
+                                "STAT/TemperatureManager/1"
+                            ]
                         }
                     }
                 }
@@ -112,19 +112,6 @@
                 }
             }
         },
-        "DigitalBeam": {
-            "STAT": {
-                "DigitalBeam": {
-                    "STAT/DigitalBeam/1": {
-                        "properties": {
-                            "Tracking_enabled_RW_default": [
-                                "False"
-                            ]
-                        }
-                    }
-                }
-            }
-        },
         "Beamlet": {
             "STAT": {
                 "Beamlet": {
@@ -185,32 +172,39 @@
                 "AntennaField": {
                     "STAT/AntennaField/2": {
                         "properties": {
-			    "RECV_devices": [
-				"STAT/RECV/1"
-			    ],
+                            "RECV_devices": [
+                                "STAT/RECV/1"
+                            ],
+                            "Antenna_Names": [
+                                "C0",
+                                "C1",
+                                "C2",
+                                "C3",
+                                "C4"
+                            ],
                             "HBAT_Control_to_RECV_mapping": [
-				"1", "27",
-				"0", "-1",
-				"0", "-1",
-				"1", "28",
-				"1", "29"
-			    ],
+                                "1","27",
+                                "0","-1",
+                                "0","-1",
+                                "1","28",
+                                "1","29"
+                            ],
                             "HBAT_Power_to_RECV_mapping": [
-				"1", "24",
-				"0", "-1",
-				"0", "-1",
-				"1", "25",
-				"1", "26"
-			    ],
+                                "1","24",
+                                "0","-1",
+                                "0","-1",
+                                "1","25",
+                                "1","26"
+                            ],
                             "Antenna_Field_Reference_ETRS": [
-                                "3839371.416", "430339.901", "5057958.886"
+                                "3839371.416","430339.901","5057958.886"
                             ],
                             "HBAT_reference_ETRS": [
-                                "3839371.416", "430339.901", "5057958.886",
-                                "3839368.919", "430335.979", "5057961.1",
-                                "3839365.645", "430339.299", "5057963.288",
-                                "3839368.142", "430343.221", "5057961.074",
-                                "3839374.094", "430299.513", "5057960.017"
+                                "3839371.416","430339.901","5057958.886",
+                                "3839368.919","430335.979","5057961.1",
+                                "3839365.645","430339.299","5057963.288",
+                                "3839368.142","430343.221","5057961.074",
+                                "3839374.094","430299.513","5057960.017"
                             ],
                             "HBAT_PQR_rotation_angles_deg": [
                                 "45.73",
@@ -220,52 +214,65 @@
                                 "54.40"
                             ],
                             "HBAT_PQR_to_ETRS_rotation_matrix": [
-                               "-0.11660087", "-0.79095632", "0.60065992",
-                               " 0.99317077", "-0.09529842", "0.06730545",
-                               " 0.00400627", " 0.60440575", "0.79666658"
+                                "-0.11660087","-0.79095632","0.60065992",
+                                " 0.99317077","-0.09529842","0.06730545",
+                                " 0.00400627"," 0.60440575","0.79666658"
                             ]
                         }
                     },
                     "STAT/AntennaField/1": {
                         "properties": {
-			    "RECV_devices": [
-				"STAT/RECV/1"
-			    ],
+                            "RECV_devices": [
+                                "STAT/RECV/1"
+                            ],
+                            "Antenna_Names": [
+                                "LBA1",
+                                "LBA2",
+                                "LBA3",
+                                "LBA4",
+                                "LBA5",
+                                "LBA6",
+                                "LBA7",
+                                "LBA8",
+                                "LBA9"
+                            ],
                             "HBAT_Control_to_RECV_mapping": [
-				"1", "1",
-				"1", "3",
-				"1", "5",
-				"1", "7",
-				"1", "9",
-				"1", "11",
-				"1", "13",
-				"1", "15",
-				"1", "17"
-			    ],
+                                "1","1",
+                                "1","3",
+                                "1","5",
+                                "1","7",
+                                "1","9",
+                                "1","11",
+                                "1","13",
+                                "1","15",
+                                "1","17"
+                            ],
                             "HBAT_Power_to_RECV_mapping": [
-				"1", "1",
-				"1", "3",
-				"1", "5",
-				"1", "7",
-				"1", "9",
-				"1", "11",
-				"1", "13",
-				"1", "15",
-				"1", "17"
-			    ],
+                                "1","1",
+                                "1","3",
+                                "1","5",
+                                "1","7",
+                                "1","9",
+                                "1","11",
+                                "1","13",
+                                "1","15",
+                                "1","17"
+                            ],
                             "Antenna_Field_Reference_ETRS": [
-				"3839358.189", "430354.482", "5057967.804"
+                                "3839358.189",
+                                "430354.482",
+                                "5057967.804"
                             ],
                             "HBAT_reference_ETRS": [
-				"3839358.189", "430354.482", "5057967.804",
-                                "3839359.127", "430348.074", "5057967.607",
-                                "3839360.084", "430341.872", "5057967.379",
-                                "3839361.024", "430335.466", "5057967.180",
-                                "3839361.721", "430329.482", "5057967.130",
-                                "3839362.786", "430323.311", "5057966.818",
-                                "3839363.705", "430317.204", "5057966.611",
-				"3839364.563", "430311.056", "5057966.454",
-                                "3839365.497", "430304.996", "5057966.232"
+                                "3839358.189","430354.482","5057967.804",
+                                "3839359.127","430348.074","5057967.607",
+                                "3839360.084","430341.872","5057967.379",
+                                "3839361.024","430335.466","5057967.180",
+                                "3839361.721","430329.482","5057967.130",
+                                "3839362.786","430323.311","5057966.818",
+                                "3839363.705","430317.204","5057966.611",
+                                "3839364.563","430311.056","5057966.454",
+                                "3839365.497","430304.996","5057966.232"
                             ]
                         }
                     }
@@ -283,7 +290,7 @@
                             "Input_to_Antenna_Mapping": [
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
-                                 "0",  "3",  "4", "-1", "-1", "-1",
+                                "0",  "3",  "4", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
@@ -306,8 +313,8 @@
                                 "STAT/AntennaField/1"
                             ],
                             "Input_to_Antenna_Mapping": [
-                                 "0",  "1",  "2",  "3",  "4",  "5",
-                                 "6",  "7",  "8", "-1", "-1", "-1",
+                                "0",  "1",  "2",  "3",  "4",  "5",
+                                "6",  "7",  "8", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
@@ -322,6 +329,9 @@
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1",
                                 "-1", "-1", "-1", "-1", "-1", "-1"
+                            ],
+                            "Tracking_enabled_RW_default": [
+                                "False"
                             ]
                         }
                     }
@@ -352,9 +362,9 @@
                 "SDP": {
                     "STAT/SDP/1": {
                         "properties": {
-                            "AntennaType" : [
-				"LBA"
-			    ],
+                            "AntennaType": [
+                                "LBA"
+                            ],
                             "OPC_Server_Name": [
                                 "10.99.0.250"
                             ],
@@ -575,7 +585,9 @@
                 "PSOC": {
                     "STAT/PSOC/1": {
                         "properties": {
-                            "SNMP_host": ["10.87.2.145"],
+                            "SNMP_host": [
+                                "10.87.2.145"
+                            ],
                             "PSOC_sockets": [
                                 "ccd_socket",
                                 "sdptr0_socket",
@@ -596,7 +608,9 @@
                 "PCON": {
                     "STAT/PCON/1": {
                         "properties": {
-                            "SNMP_host": ["10.151.225.5"]
+                            "SNMP_host": [
+                                "10.151.225.5"
+                            ]
                         }
                     }
                 }
diff --git a/README.md b/README.md
index b8f08504fbcd221ac8a0ed3e4e6c98869b242f11..c72d120f54c46d5bf9eba273863b4572de9595ff 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Station Control software related to Tango devices.
   * [Attribute wrapper documentation](tangostationcontrol/tangostationcontrol/clients/README.md)
   * [Archiver documentation](tangostationcontrol/tangostationcontrol/toolkit/README.md)
   * [Adding a new tango device](tangostationcontrol/tangostationcontrol/devices/README.md)
-  * [HDF5 statistics](tangostationcontrol/tangostationcontrol/statistics_writer/README.md)
+  * [HDF5 statistics](tangostationcontrol/tangostationcontrol/statistics/README.md)
 * [Unit tests](tangostationcontrol/tangostationcontrol/test/README.md)
 * [Integration tests](tangostationcontrol/tangostationcontrol/integration_test/README.md)
 
diff --git a/bin/start-ds.sh b/bin/start-ds.sh
index 8a5baba501cf7c48dd53a7d3b92196874020d3d6..66389714f17770339ee645129adc9dcb26fa21d7 100755
--- a/bin/start-ds.sh
+++ b/bin/start-ds.sh
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+set -e
+
 # Serves as entrypoint script for docker containers
 
 if [[ ! -d "/opt/lofar/tango" ]]; then
@@ -31,8 +33,11 @@ if [[ $TANGOSTATIONCONTROL ]]; then
   exit 2
 else
   # Install the package, exit 1 if it fails
-  cd tangostationcontrol || exit 1
-  pip install --upgrade --force-reinstall ./
+  # pip install ./ will _NOT_ install dependencies in requirements.txt!
+  rm -rf /tmp/tangostationcontrol
+  cp -R /opt/lofar/tango/tangostationcontrol /tmp/
+  cd /tmp/tangostationcontrol || exit 1
+  pip -vvv install --upgrade --force-reinstall ./
 fi
 
 # Return to the stored the directory, this preserves the working_dir argument in
diff --git a/docker-compose/Makefile b/docker-compose/Makefile
index e0689cc7ceedc874798ecf20ce32de7cd69ca6d7..7606e94abd344a9c255463664bf2f55d8e556b93 100644
--- a/docker-compose/Makefile
+++ b/docker-compose/Makefile
@@ -150,20 +150,21 @@ DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) \
     CONTAINER_NAME_PREFIX=$(CONTAINER_NAME_PREFIX) \
     COMPOSE_IGNORE_ORPHANS=true \
     CONTAINER_EXECUTION_UID=$(shell id -u) \
-    DOCKER_GID=$(DOCKER_GID)
+    DOCKER_GID=$(DOCKER_GID) \
+    TEST_MODULE=$(INTEGRATION_MODULE)
 
 
-.PHONY: up down minimal run integration start stop restart build build-nocache status clean pull help
+.PHONY: up down minimal context run integration start stop restart build build-nocache status clean pull help
 .DEFAULT_GOAL := help
 
 pull: ## pull the images from the Docker hub
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) pull
 
-build: ## rebuild images
+build: context ## rebuild images
 	# docker-compose does not support build dependencies, so manage those here
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build --parallel --progress=plain $(SERVICE)
 
-build-nocache: ## rebuild images from scratch
+build-nocache: context ## rebuild images from scratch
 	# docker-compose does not support build dependencies, so manage those here
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build --no-cache --progress=plain $(SERVICE)
 
@@ -174,7 +175,7 @@ run: minimal  ## run a service using arguments and delete it afterwards
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) run --no-deps --rm $(SERVICE) $(SERVICE_ARGS)
 
 integration: minimal  ## run a service using arguments and delete it afterwards
-	TEST_MODULE=$(INTEGRATION_MODULE) $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) run --no-deps --rm integration-test $(INTEGRATION_ARGS)
+	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) run --no-deps --rm integration-test $(INTEGRATION_ARGS)
 
 down:  ## stop all services and tear down the system
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) down
@@ -183,13 +184,18 @@ ifneq ($(NETWORK_MODE),host)
 	docker network inspect 9000-$(NETWORK_MODE) &> /dev/null && ([ $$? -eq 0 ] && docker network rm 9000-$(NETWORK_MODE)) || true
 endif
 
-minimal: ## start the base TANGO system
+minimal: context ## start the base TANGO system
 ifneq ($(NETWORK_MODE),host)
 	docker network inspect $(NETWORK_MODE) &> /dev/null || ([ $$? -ne 0 ] && docker network create $(NETWORK_MODE))
 	docker network inspect 9000-$(NETWORK_MODE) &> /dev/null || ([ $$? -ne 0 ] && docker network create 9000-$(NETWORK_MODE) -o com.docker.network.driver.mtu=9000)
 endif
+
 	$(DOCKER_COMPOSE_ARGS) docker-compose -f tango.yml -f networks.yml up --no-recreate -d
 
+context: ## Move the necessary files to create minimal docker context
+	@mkdir -p tmp
+	@cp ../tangostationcontrol/requirements.txt tmp/
+
 bootstrap: pull build # first start, initialise from scratch
 	$(MAKE) start elk-configure-host # configure host kernel for elk container
 	$(MAKE) start dsconfig # boot up containers to load configurations
diff --git a/docker-compose/device-antennafield.yml b/docker-compose/device-antennafield.yml
index d33dacac0139a2a2946f3c19f3cdc2979cf76e30..0e16043f1170a937d0fe16e3e2853801c3b1339b 100644
--- a/docker-compose/device-antennafield.yml
+++ b/docker-compose/device-antennafield.yml
@@ -19,8 +19,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-antennafield
diff --git a/docker-compose/device-apsct.yml b/docker-compose/device-apsct.yml
index cb43adf7b800498d4448fdcc2ef99cbf577e11d5..8addefff2bc1ead3510e835fad51187b40e4a996 100644
--- a/docker-compose/device-apsct.yml
+++ b/docker-compose/device-apsct.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-apsct
diff --git a/docker-compose/device-apspu.yml b/docker-compose/device-apspu.yml
index 6613b210971f7514c0822cd82f5185af550f4e2a..55a2d5a9a5d95a9c0e1617bb4732a1d96ab26a20 100644
--- a/docker-compose/device-apspu.yml
+++ b/docker-compose/device-apspu.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-apspu
diff --git a/docker-compose/device-beamlet.yml b/docker-compose/device-beamlet.yml
index 6d067c1544bb805578cfa53ba486a39313238b75..4e80eba8f4033efe31749c51c95002204a0136f4 100644
--- a/docker-compose/device-beamlet.yml
+++ b/docker-compose/device-beamlet.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-beamlet
diff --git a/docker-compose/device-boot.yml b/docker-compose/device-boot.yml
index cbeb916536845b47aa0ded5dba2900fa1fa5d7f5..4d4108d9ffb189e05b8696039d68b2f1be20de2a 100644
--- a/docker-compose/device-boot.yml
+++ b/docker-compose/device-boot.yml
@@ -17,8 +17,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-boot
diff --git a/docker-compose/device-bst.yml b/docker-compose/device-bst.yml
index c06514b4f3a738ad45ffb847d5b261e515b58a6a..92522527c2071e9e94b9561ab010717c37c63c07 100644
--- a/docker-compose/device-bst.yml
+++ b/docker-compose/device-bst.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-bst
diff --git a/docker-compose/device-digitalbeam.yml b/docker-compose/device-digitalbeam.yml
index 32847ca8d6ce995baeee599ea20cb1c0919b9084..c5b45e9baaef63e15f44b4a539cf93a73244bde3 100644
--- a/docker-compose/device-digitalbeam.yml
+++ b/docker-compose/device-digitalbeam.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-digitalbeam
diff --git a/docker-compose/device-docker.yml b/docker-compose/device-docker.yml
index 2be9467ea6d63fbf08fc30e954de74a585bbe6a3..db33c9aed034fa02fbafe2bc7ab4b66535fe0f33 100644
--- a/docker-compose/device-docker.yml
+++ b/docker-compose/device-docker.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-docker
diff --git a/docker-compose/device-observation-control.yml b/docker-compose/device-observation-control.yml
index 197d192a54ca10360439f41cfd6aeb0adeca70e3..7025b8b3ab38cbb1290971c1907729b8aa09ca0f 100644
--- a/docker-compose/device-observation-control.yml
+++ b/docker-compose/device-observation-control.yml
@@ -17,8 +17,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-observation-control
diff --git a/docker-compose/device-observation.yml b/docker-compose/device-observation.yml
index ee8f3a1653b447965af9891258de1e8642242e60..3379e41e1887d670b734a1f75f8b942ca51d17df 100644
--- a/docker-compose/device-observation.yml
+++ b/docker-compose/device-observation.yml
@@ -16,8 +16,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-observation
diff --git a/docker-compose/device-pcon.yml b/docker-compose/device-pcon.yml
index 40ebb3f58605ab09ad402d3e4b5bf80bc201d95e..17fad681d96fbe9cb1b84168144b3668ce4f96f7 100644
--- a/docker-compose/device-pcon.yml
+++ b/docker-compose/device-pcon.yml
@@ -13,8 +13,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-pcon
diff --git a/docker-compose/device-psoc.yml b/docker-compose/device-psoc.yml
index bc251d2de28a132953259de924b1cc8ee557175e..6d6578e6aa6aa3b44b34f16d6cd1f3373f45217b 100644
--- a/docker-compose/device-psoc.yml
+++ b/docker-compose/device-psoc.yml
@@ -13,8 +13,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-psoc
diff --git a/docker-compose/device-recv.yml b/docker-compose/device-recv.yml
index 10126cae3612ad12ca6e77c1b191b2879091f1d4..3c79a0a149528557a0d3ca3aa087773538942207 100644
--- a/docker-compose/device-recv.yml
+++ b/docker-compose/device-recv.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-recv
diff --git a/docker-compose/device-sdp.yml b/docker-compose/device-sdp.yml
index 2e9c77312d96b800f06e3c85e6773613a0af758b..144630c883d741c166c6f1a1c48f9e8eda5ab096 100644
--- a/docker-compose/device-sdp.yml
+++ b/docker-compose/device-sdp.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-sdp
diff --git a/docker-compose/device-sst.yml b/docker-compose/device-sst.yml
index 54c221f3b6c051078d55009ec583d0ddab8dd46b..e6b0edb75008791f365d4ec8281c35a314935ca3 100644
--- a/docker-compose/device-sst.yml
+++ b/docker-compose/device-sst.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-sst
diff --git a/docker-compose/device-temperature-manager.yml b/docker-compose/device-temperature-manager.yml
index 4729bd631b87508b12be14b24a9c4e1bbbccf98b..d1b20359bf0f827b99450edd93cf4687ac263532 100644
--- a/docker-compose/device-temperature-manager.yml
+++ b/docker-compose/device-temperature-manager.yml
@@ -13,8 +13,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-temperature-manager
diff --git a/docker-compose/device-tilebeam.yml b/docker-compose/device-tilebeam.yml
index 20e44c90c112c3ce7a4f3c7c09682c897c032a30..7919beded3237fecc98cddc64b734747e3187304 100644
--- a/docker-compose/device-tilebeam.yml
+++ b/docker-compose/device-tilebeam.yml
@@ -13,8 +13,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-tilebeam
diff --git a/docker-compose/device-unb2.yml b/docker-compose/device-unb2.yml
index 9a7505f0beb6b6f3f7448a402e812a86ad2c033d..2c05d6e66b887b903d17278e3252cd8f9ea70493 100644
--- a/docker-compose/device-unb2.yml
+++ b/docker-compose/device-unb2.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-unb2
diff --git a/docker-compose/device-xst.yml b/docker-compose/device-xst.yml
index 1ed7a1fe3892c900b14c9c275099816fb45b90a3..6f49e17f6389ff510736543d7cb42aed4ea104b9 100644
--- a/docker-compose/device-xst.yml
+++ b/docker-compose/device-xst.yml
@@ -18,8 +18,8 @@ services:
     # build explicitly, as docker-compose does not understand a local image
     # being shared among services.
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}device-xst
diff --git a/docker-compose/integration-test.yml b/docker-compose/integration-test.yml
index 346bbbcc5b179c2066c08a0064335c23957fb902..6e7e5407d422afd1989ef7a127d1f54217307cc3 100644
--- a/docker-compose/integration-test.yml
+++ b/docker-compose/integration-test.yml
@@ -9,8 +9,8 @@ version: '2.1'
 services:
   integration-test:
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}integration-test
diff --git a/docker-compose/lofar-device-base.yml b/docker-compose/lofar-device-base.yml
index 34f3f1c92c06fadd45b634a2a93bae994a54edeb..8f31696433aaee502eea2af83f15c54119b22ec7 100644
--- a/docker-compose/lofar-device-base.yml
+++ b/docker-compose/lofar-device-base.yml
@@ -17,8 +17,8 @@ services:
   lofar-device-base:
     image: lofar-device-base
     build:
-        context: ..
-        dockerfile: docker-compose/lofar-device-base/Dockerfile
+        context: .
+        dockerfile: lofar-device-base/Dockerfile
         args:
             SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION}
     container_name: ${CONTAINER_NAME_PREFIX}lofar-device-base
diff --git a/docker-compose/lofar-device-base/Dockerfile b/docker-compose/lofar-device-base/Dockerfile
index 39b9d652f61d4afb3903b0ea98e3df98f51d2b8a..becc95d0833408a8e4a04f074bff9ed0b0b2b6da 100644
--- a/docker-compose/lofar-device-base/Dockerfile
+++ b/docker-compose/lofar-device-base/Dockerfile
@@ -4,20 +4,23 @@ FROM ${SOURCE_IMAGE}
 RUN sudo apt-get update && sudo apt-get install -y git g++ gcc shellcheck graphviz python3-dev && sudo apt-get clean
 
 
-COPY docker-compose/lofar-device-base/lofar-requirements.txt /lofar-requirements.txt
+COPY lofar-device-base/lofar-requirements.txt /lofar-requirements.txt
 RUN sudo pip3 install -r /lofar-requirements.txt
 
-COPY tangostationcontrol/requirements.txt /tangostationcontrol-requirements.txt
+# Manually install all requirements from the .txt as part of the base image
+# This reduces runtime overhead as well as preventing issues around dependency
+# installation for development builds (pip install ./ ignores requirements.txt)
+COPY tmp/requirements.txt /tangostationcontrol-requirements.txt
 RUN sudo pip3 install -r /tangostationcontrol-requirements.txt
 
 # install and use ephimerides and geodetic ("measures") tables for casacore.
 # we install a _stub_ since the tables need to be deployed explicitly from within the software.
 RUN sudo mkdir -p /opt/IERS && sudo chmod a+rwx /opt/IERS
 ARG IERS_DIRNAME=IERS-1970-01-01T00:00:00-stub
-COPY docker-compose/lofar-device-base/WSRT_Measures_stub /opt/IERS/${IERS_DIRNAME}
+COPY lofar-device-base/WSRT_Measures_stub /opt/IERS/${IERS_DIRNAME}
 RUN ln -sfT /opt/IERS/${IERS_DIRNAME} /opt/IERS/current
 
-COPY docker-compose/lofar-device-base/casarc /home/tango/.casarc
+COPY lofar-device-base/casarc /home/tango/.casarc
 
 ENV TANGO_LOG_PATH=/var/log/tango
 RUN sudo mkdir -p /var/log/tango && sudo chmod a+rwx /var/log/tango
diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh
index b910bae025cee9c225d4a52301082d546c2a49d4..f1ee76c7606c485f5cf68ecb92bf9965390bd192 100755
--- a/sbin/run_integration_test.sh
+++ b/sbin/run_integration_test.sh
@@ -24,12 +24,16 @@ function integration_test {
   IFS=" " read -r -a restarts <<< "${2}"
   IFS=" " read -r -a configs <<< "${3}"
   for config in "${configs[@]}"; do
+    echo "Updating config ${config} ..."
     bash "${LOFAR20_DIR}"/sbin/update_ConfigDb.sh "${config}"
   done
   if [ ! -z "${2+x}" ]; then
+    # shellcheck disable=SC2145
+    echo "make restart ${restarts[@]} ..."
     make restart "${restarts[@]}"
   fi
   sleep 5
+  echo "make integration ${1} ..."
   make integration "${1}"
 }
 
diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt
index 21a4422bbb65100a0e3b9d3c10136b2f3345e9bb..b252910091df6eaca22673b2931b1ab73405c2f0 100644
--- a/tangostationcontrol/requirements.txt
+++ b/tangostationcontrol/requirements.txt
@@ -2,13 +2,14 @@
 # order of appearance. Changing the order has an impact on the overall
 # integration process, which may cause wedges in the gate later.
 
-lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client@0.3.0
+lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client@0.6.0
 asyncua >= 0.9.90 # LGPLv3
 PyMySQL[rsa] >= 1.0.2 # MIT
 psycopg2-binary >= 2.9.2 # LGPL
 sqlalchemy >= 1.4.26 # MIT
 pysnmp >= 0.1.7 # BSD
 h5py >= 3.1.0 # BSD
+jsonschema >= 3.2.0 # MIT
 psutil >= 5.8.0 # BSD
 docker >= 5.0.3 # Apache 2
 python-logstash-async >= 2.3.0 # MIT
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
index 29b26f98acbd83850ac9712dd6717a1af3a3e562..11c8c2d5a19bc410bbe76e36eb77f1027ea50c1b 100644
--- a/tangostationcontrol/setup.cfg
+++ b/tangostationcontrol/setup.cfg
@@ -51,8 +51,8 @@ console_scripts =
     l2ss-sdp = tangostationcontrol.devices.sdp.sdp:main
     l2ss-bst = tangostationcontrol.devices.sdp.bst:main
     l2ss-sst = tangostationcontrol.devices.sdp.sst:main
-    l2ss-statistics-reader = tangostationcontrol.statistics_writer.statistics_reader:main
-    l2ss-statistics-writer = tangostationcontrol.statistics.writer:main
+    l2ss-statistics-reader = tangostationcontrol.statistics.reader:main
+    l2ss-statistics-writer = tangostationcontrol.statistics.writer.entry:main
     l2ss-unb2 = tangostationcontrol.devices.unb2:main
     l2ss-xst = tangostationcontrol.devices.sdp.xst:main
     l2ss-temperature-manager = tangostationcontrol.devices.temperature_manager:main
diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index 4c39a6bdea1db44f52c87f2fd3a4e6add71e3d99..c7d43b9226de469e877cf6bfdc4c728a8afcfd0e 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -85,6 +85,14 @@ class AntennaField(lofar_device):
         calculated, as well as the geohash.
     """
 
+    # ----- Antenna names
+
+    Antenna_Names = device_property(
+        doc="Name of each antenna",
+        dtype='DevVarStringArray',
+        mandatory=False
+    )
+
     # ----- Antenna states
 
     Antenna_Quality = device_property(
@@ -195,6 +203,8 @@ class AntennaField(lofar_device):
         default_value = []
     )
 
+    Antenna_Names_R = attribute(access=AttrWriteType.READ,
+        dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT)
     Antenna_Quality_R = attribute(access=AttrWriteType.READ,
         dtype=(numpy.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT)
     Antenna_Use_R = attribute(access=AttrWriteType.READ,
@@ -247,6 +257,9 @@ class AntennaField(lofar_device):
         doc='Number of HBAT in this field',
         dtype=numpy.int32)
 
+    def read_Antenna_Names_R(self):
+        return self.Antenna_Names
+    
     def read_Antenna_Use_R(self):
         return self.Antenna_Use
     
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/statistics/test_writer_sst.py b/tangostationcontrol/tangostationcontrol/integration_test/default/statistics/test_writer_sst.py
index 88382175385a01e3493984fd1a53d026a19bbb02..d8932263df5079790e14cdd988f73c3224e8c53a 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/statistics/test_writer_sst.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/statistics/test_writer_sst.py
@@ -9,9 +9,10 @@
 
 from tangostationcontrol.integration_test.base import BaseIntegrationTestCase
 from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
+
 from tangostationcontrol.statistics.collector import StationSSTCollector
-from tangostationcontrol.statistics_writer import statistics_reader
-from tangostationcontrol.statistics import writer
+from tangostationcontrol.statistics import reader
+from tangostationcontrol.statistics.writer import entry
 
 import sys
 from os.path import dirname, isfile, join
@@ -19,56 +20,84 @@ from tempfile import TemporaryDirectory
 from unittest import mock
 
 from tango import DevState
+import numpy
 
 
 class TestStatisticsWriterSST(BaseIntegrationTestCase):
 
+    RECV_PROXY_STRING = "STAT/RECV/1"
+
     def setUp(self):
         self.recv_proxy = self.setup_recv_proxy()
         return super().setUp()
-    
-    def setup_recv_proxy(self):
+
+    @staticmethod
+    def setup_recv_proxy():
         # setup RECV
-        recv_proxy = TestDeviceProxy("STAT/RECV/1")
+        recv_proxy = TestDeviceProxy(TestStatisticsWriterSST.RECV_PROXY_STRING)
         recv_proxy.off()
         recv_proxy.warm_boot()
         recv_proxy.set_defaults()
         return recv_proxy
     
     def test_retrieve_data_from_RECV(self):
-        recv_proxy = self.setup_recv_proxy()
-        self.assertEqual(DevState.ON, recv_proxy.state())
-        self.assertIsNotNone(recv_proxy.RCU_Attenuator_dB_R)
-        self.assertIsNotNone(recv_proxy.RCU_Band_Select_R)
-        self.assertIsNotNone(recv_proxy.RCU_DTH_on_R)
+        self.assertEqual(DevState.ON, self.recv_proxy.state())
+        self.assertIsNotNone(self.recv_proxy.RCU_Attenuator_dB_R)
+        self.assertIsNotNone(self.recv_proxy.RCU_Band_Select_R)
+        self.assertIsNotNone(self.recv_proxy.RCU_DTH_on_R)
     
     def test_insert_tango_SST_statistics(self):
-        self.setup_recv_proxy()
         self.assertEqual(DevState.ON, self.recv_proxy.state())
-        collector = StationSSTCollector()
+        collector = StationSSTCollector(device=self.recv_proxy)
 
         # Test attribute values retrieval 
-        collector.parse_device_attributes(self.recv_proxy)
-        self.assertListEqual(collector.parameters["rcu_attenuator_dB"].tolist(), self.recv_proxy.rcu_attenuator_dB_r.tolist())
-        self.assertListEqual(collector.parameters["rcu_band_select"].tolist(), self.recv_proxy.rcu_band_select_r.tolist())
-        self.assertListEqual(collector.parameters["rcu_dth_on"].tolist(), self.recv_proxy.rcu_dth_on_r.tolist())
+        collector.parse_device_attributes()
+        numpy.testing.assert_equal(
+            collector.parameters["rcu_attenuator_dB"].flatten(),
+            self.recv_proxy.rcu_attenuator_dB_r
+        )
+        numpy.testing.assert_equal(
+            collector.parameters["rcu_band_select"].flatten(),
+            self.recv_proxy.rcu_band_select_r.tolist()
+        )
+        numpy.testing.assert_equal(
+            collector.parameters["rcu_dth_on"].flatten(),
+            self.recv_proxy.rcu_dth_on_r.tolist()
+        )
 
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "SST", "--file", join(dirname(dirname(dirname(dirname(__file__)))), "test/statistics", "SDP_SST_statistics_packets.bin"), "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "SST",
+                "--file", join(
+                    dirname(dirname(dirname(dirname(__file__)))),
+                    "test/statistics", "SDP_SST_statistics_packets.bin"
+                ),
+                ""
+                "--output_dir", tmpdir
+            ]
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
 
             # check if file was written
             self.assertTrue(isfile(f"{tmpdir}/SST_2021-09-20-12-17-40.h5"))
         
             # test statistics reader
-            new_sys_argv = [sys.argv[0], "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5", "--start_time", "2021-09-20#07:40:08.937+00:00", "--end_time", "2021-10-04#07:50:08.937+00:00"]
-            with mock.patch.object(statistics_reader.sys, 'argv', new_sys_argv):           
-                stat_parser = statistics_reader.setup_stat_parser()
+            new_sys_argv = [
+                sys.argv[0],
+                "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5",
+                "--start_time", "2021-09-20#07:40:08.937+00:00",
+                "--end_time", "2021-10-04#07:50:08.937+00:00"
+            ]
+            with mock.patch.object(reader.sys, 'argv', new_sys_argv):
+                stat_parser = reader.setup_stat_parser()
                 SSTstatistics = stat_parser.list_statistics()
                 self.assertIsNotNone(SSTstatistics)
-                stat = stat_parser.get_statistic('2021-09-20T12:17:40.000+00:00') # same as stat_parser.statistics[0] 
+                # same as stat_parser.statistics[0]
+                stat = stat_parser.get_statistic(
+                    '2021-09-20T12:17:40.000+00:00'
+                )
                 self.assertIsNotNone(stat)
                 self.assertEqual(121, stat.data_id_signal_input_index)
                 # Test RECV attributes
@@ -77,23 +106,40 @@ class TestStatisticsWriterSST(BaseIntegrationTestCase):
                 self.assertListEqual(stat.rcu_dth_on.tolist(), [False] * 96)
 
     def test_no_tango_SST_statistics(self):
-
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "SST", "--no-tango", "--file", join(dirname(dirname(dirname(dirname(__file__)))), "test/statistics", "SDP_SST_statistics_packets.bin"), "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "SST",
+                "--no-tango",
+                "--file", join(
+                    dirname(dirname(dirname(dirname(__file__)))),
+                    "test/statistics", "SDP_SST_statistics_packets.bin"
+                ),
+                "--output_dir", tmpdir
+            ]
+
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
             
             # check if file was written
             self.assertTrue(isfile(f"{tmpdir}/SST_2021-09-20-12-17-40.h5"))
 
             # test statistics reader
-            new_sys_argv = [sys.argv[0], "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5", "--start_time", "2021-09-20#07:40:08.937+00:00", "--end_time", "2021-10-04#07:50:08.937+00:00"]
-            with mock.patch.object(statistics_reader.sys, 'argv', new_sys_argv):           
-                stat_parser = statistics_reader.setup_stat_parser()
+            new_sys_argv = [
+                sys.argv[0],
+                "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5",
+                "--start_time", "2021-09-20#07:40:08.937+00:00",
+                "--end_time", "2021-10-04#07:50:08.937+00:00"
+            ]
+            with mock.patch.object(reader.sys, 'argv', new_sys_argv):
+                stat_parser = reader.setup_stat_parser()
                 SSTstatistics = stat_parser.list_statistics()
                 self.assertIsNotNone(SSTstatistics)
-                stat = stat_parser.get_statistic('2021-09-20T12:17:40.000+00:00') # same as stat_parser.statistics[0] 
+                # same as stat_parser.statistics[0]
+                stat = stat_parser.get_statistic(
+                    '2021-09-20T12:17:40.000+00:00'
+                )
                 self.assertIsNotNone(stat)
                 self.assertEqual(121, stat.data_id_signal_input_index)
                 # Test RECV attributes      
@@ -101,27 +147,39 @@ class TestStatisticsWriterSST(BaseIntegrationTestCase):
                 self.assertEqual(stat.rcu_band_select.tolist(), None)
                 self.assertEqual(stat.rcu_dth_on.tolist(), None) 
             
-    def test_SST_statistics_with_device_in_off(self):                
+    def test_SST_statistics_with_device_in_off(self):     
         self.setup_recv_proxy()
         self.recv_proxy.Off()
         self.assertEqual(DevState.OFF, self.recv_proxy.state())
 
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "SST", "--file", join(dirname(dirname(dirname(dirname(__file__)))), "test/statistics", "SDP_SST_statistics_packets.bin"), "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "SST",
+                "--file", join(
+                    dirname(dirname(dirname(dirname(__file__)))),
+                    "test/statistics", "SDP_SST_statistics_packets.bin"
+                ), "--output_dir", tmpdir
+            ]
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
 
             # check if file was written
             self.assertTrue(isfile(f"{tmpdir}/SST_2021-09-20-12-17-40.h5"))
 
             # test statistics reader
-            new_sys_argv = [sys.argv[0], "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5", "--start_time", "2021-09-20#07:40:08.937+00:00", "--end_time", "2021-10-04#07:50:08.937+00:00"]
-            with mock.patch.object(statistics_reader.sys, 'argv', new_sys_argv):           
-                stat_parser = statistics_reader.setup_stat_parser()
+            new_sys_argv = [
+                sys.argv[0],
+                "--files", f"{tmpdir}/SST_2021-09-20-12-17-40.h5",
+                "--start_time", "2021-09-20#07:40:08.937+00:00",
+                "--end_time", "2021-10-04#07:50:08.937+00:00"
+            ]
+            with mock.patch.object(reader.sys, 'argv', new_sys_argv):
+                stat_parser = reader.setup_stat_parser()
                 SSTstatistics = stat_parser.list_statistics()
                 self.assertIsNotNone(SSTstatistics)
-                stat = stat_parser.get_statistic('2021-09-20T12:17:40.000+00:00') # same as stat_parser.statistics[0] 
+                stat = stat_parser.get_statistic('2021-09-20T12:17:40.000+00:00')  # same as stat_parser.statistics[0]
                 self.assertIsNotNone(stat)
                 self.assertEqual(121, stat.data_id_signal_input_index)
                 # Test RECV attributes      
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/README.md b/tangostationcontrol/tangostationcontrol/statistics/README.md
similarity index 70%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/README.md
rename to tangostationcontrol/tangostationcontrol/statistics/README.md
index dc9e285ea47039351d98cb5b8c82d104b85e11bd..ad2a9d3e894dcc2d16c04060487832cc62db8b78 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/README.md
+++ b/tangostationcontrol/tangostationcontrol/statistics/README.md
@@ -1,9 +1,12 @@
 # TCP to HDF5 statistics writer
-The TCP to HDF5 statistics writer can be started with `statistics_writer.py` This script imports the receiver script and `hdf5_writer.py`. `receiver.py` only takes care of receiving packets. 
-`hdf5_writer.py` takes the receive function from the receiver and uses it to obtain packets. 
-Any function that can deliver statistics packets can be used by this code. 
-`hdf5_writer.py` takes care of processing the packets it receives, filling statistics matrices
-and writing those matrices (as well as a bunch of metadata) to hdf5.
+
+The TCP to HDF5 statistics writer can be started with `statistics_writer.py`
+This script imports the receiver script and `HDF5Writer.py`. `receiver.py` only
+takes care of receiving packets. `HDF5Writer.py` takes the receive function from
+the receiver and uses it to obtain packets.  Any function that can deliver
+statistics packets can be used by this code. `HDF5Writer.py` takes care of
+processing the packets it receives, filling statistics matrices and writing
+those matrices (as well as a bunch of metadata) to hdf5.
 
 
 ### TCP Statistics writer
@@ -21,14 +24,19 @@ This script can be called with the following arguments:
   -d  --decimation  Configure the writer to only store one every n samples. Saves storage space (default: 1 (everything))
   -r  --reconnect   Set the writer to keep trying to reconnect whenever connection is lost. (default: off)
   ```
-An example call could be: `l2ss-statistics-writer statistics_writer.py --host localhost --port 1234 --mode XST --debug`
-This starts the script up to listen on localhost:1234 for XST's with debug mode on. 
+An example call could be:
 
-## HFD5 structure
-Statistics packets are collected by the StatisticsCollector in to a matrix. Once the matrix is done or a newer 
-timestamp arrives this matrix along with the header of first packet header, nof_payload_errors and nof_valid_payloads.
-The file will be named after the mode it is in and the timestamp of the statistics packets. For example: `SST_1970-01-01-00-00-00.h5`.
+```l2ss-statistics-writer --host localhost --port 1234--mode XST --debug```
+
+This starts the script up to listen on localhost:1234 for XSTs with debug mode
+on. 
 
+## HFD5 structure
+Statistics packets are collected by the StatisticsCollector in to a matrix. Once
+the matrix is done or a newer timestamp arrives this matrix along with the
+header of first packet header, nof_payload_errors and nof_valid_payloads. The
+file will be named after the mode it is in and the timestamp of the statistics
+packets. For example: `SST_1970-01-01-00-00-00.h5`.
 
 ```
 File
@@ -47,8 +55,11 @@ File
 ```
 
 ### reader
-There is a statistics reader that is capable of parsing multiple HDF5 statistics files in to
-a more easily usable format. It also allows for filtering between certain timestamps.
+
+There is a statistics reader that is capable of parsing multiple HDF5 statistics
+files in to  a more easily usable format. It also allows for filtering between
+certain timestamps.
+
 `statistics_reader.py` takes the following arguments:
 `--files        list of files to parse`
 `--end_time     highest timestamp to process in isoformat`
diff --git a/tangostationcontrol/tangostationcontrol/statistics/collector.py b/tangostationcontrol/tangostationcontrol/statistics/collector.py
index 7befd038d108ba3c147126082447a8e813838e42..0f68d1adb2ab2679a665ce5d1f20ae434a6efa13 100644
--- a/tangostationcontrol/tangostationcontrol/statistics/collector.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/collector.py
@@ -19,37 +19,51 @@ from tango import DevState
 logger = logging.getLogger()
 
 
-class StationStatisticsCollectorInterface(abc.ABC):
+class DeviceCollectorInterface(abc.ABC):
+    """Small interface for deviceproxy enabled collectors"""
+
+    def __init__(self, device: DeviceProxy = None):
+        self.device = device
 
     @abc.abstractmethod
-    def parse_device_attributes(self, device: DeviceProxy):
+    def parse_device_attributes(self):
         """Update information based on device attributes"""
         raise NotImplementedError
 
 
-class StationSSTCollector(StationStatisticsCollectorInterface, SSTCollector):
+class StationSSTCollector(DeviceCollectorInterface, SSTCollector):
+
+    def __init__(self, device: DeviceProxy = None):
+        """Manually combine the constructors with appropriate arguments"""
+
+        DeviceCollectorInterface.__init__(self, device=device)
+        SSTCollector.__init__(self)
 
-    def parse_packet(self, packet, obj):
-        super(StationSSTCollector, self).parse_packet(packet, obj)
+    def _parse_packet(self, packet):
+        super()._parse_packet(packet)
 
         # add tango values to packet
-        self.parse_device_attributes(obj)
+        self.parse_device_attributes()
 
-    def parse_device_attributes(self, device: DeviceProxy):
+    def parse_device_attributes(self):
 
         # If Tango connection has been disabled, set explicitly to None,
         # because otherwise default_values are inserted
-        if device is None or device.state() != DevState.ON:
+        if not self.device or self.device.state() != DevState.ON:
             self.parameters["rcu_attenuator_dB"] = None
             self.parameters["rcu_band_select"] = None
             self.parameters["rcu_dth_on"] = None
         else:
             try:
-                self.parameters["rcu_attenuator_dB"] = device.RCU_Attenuator_dB_R
-                self.parameters["rcu_band_select"] = device.RCU_Band_Select_R
-                self.parameters["rcu_dth_on"] = device.RCU_DTH_on_R
+                self.parameters[
+                    "rcu_attenuator_dB"
+                ] = self.device.RCU_Attenuator_dB_R
+                self.parameters[
+                    "rcu_band_select"
+                ] = self.device.RCU_Band_Select_R
+                self.parameters["rcu_dth_on"] = self.device.RCU_DTH_on_R
             except DevFailed as e:
-                logger.warning(f"Device {device.name()} not responding.")
+                logger.warning("Device: %s not responding.", self.device.name())
                 self.parameters["rcu_attenuator_dB"] = None
                 self.parameters["rcu_band_select"] = None
                 self.parameters["rcu_dth_on"] = None
diff --git a/tangostationcontrol/tangostationcontrol/statistics/packet.py b/tangostationcontrol/tangostationcontrol/statistics/packet.py
deleted file mode 100644
index 87383704daf73fc77e2a34d7d329c6e0246725e2..0000000000000000000000000000000000000000
--- a/tangostationcontrol/tangostationcontrol/statistics/packet.py
+++ /dev/null
@@ -1,88 +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 sys
-import pprint
-
-from lofar_station_client.statistics.packet import SDPPacket
-from lofar_station_client.statistics.packet import PACKET_CLASS_FOR_MARKER
-
-
-def read_packet(read_func) -> SDPPacket:
-    """Read a packet using the given read function, with signature
-
-        ```read_func(num_bytes: int) -> bytes```
-
-    and return it. The packet type is sensed from the data and
-    the correct subclass of SDPPacket is returned.
-
-    If read_func() returns None, this function will as well.
-    """
-
-    # read just the marker
-    marker = read_func(1)
-    if not marker:
-        return None
-
-    # read the packet header based on type
-    packetClass = PACKET_CLASS_FOR_MARKER[marker]
-
-    # read the rest of the header
-    header = read_func(packetClass.HEADER_SIZE - len(marker))
-    if not header:
-        return None
-
-    header = marker + header
-
-    # parse the packet header size
-    packet = packetClass(header)
-
-    # read the payload
-    payload_size = packet.expected_size() - len(header)
-    payload = read_func(payload_size)
-
-    if not payload:
-        return None
-
-    # return full packet
-    return packetClass(header + payload)
-
-
-def main(args=None, **kwargs):
-    # parse one packet from stdin
-
-    # packet counter
-    nr = 0
-
-    # byte offset in the stream
-    offset = 0
-
-    while True:
-        # read the packet from input
-        packet = read_packet(sys.stdin.buffer.read)
-
-        if not packet:
-            break
-
-        # print header
-        print(
-            f"# Packet {nr} of class {packet.__class__.__name__} starting at "
-            f"offset {offset} with length {packet.size()}"
-        )
-        pprint.pprint(packet.header())
-
-        # increment counters
-        nr += 1
-        offset += packet.size()
-
-
-# this file is very useful to have stand alone to parse raw packet files, so make it
-# work as such
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py b/tangostationcontrol/tangostationcontrol/statistics/reader.py
similarity index 99%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py
rename to tangostationcontrol/tangostationcontrol/statistics/reader.py
index 54755e6d67ed60cb3c4c5d5eb2d1c3bddb2f3536..cb9370fd206440de37f31bdbaae7218f71470726 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_reader.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/reader.py
@@ -164,7 +164,6 @@ class statistics_data:
                 "data_id_subband_index", "data_id_first_baseline", "data_id_beamlet_index", "nof_valid_payloads", "nof_payload_errors", "values", 
                 "rcu_attenuator_dB", "rcu_band_select", "rcu_dth_on")
 
-
     def __init__(self, file, group_key):
 
         # get all the general header info
@@ -218,6 +217,7 @@ class statistics_data:
         self.nof_payload_errors = numpy.array(file.get(f"{group_key}/nof_payload_errors"))
         self.values = numpy.array(file.get(f"{group_key}/values"))
 
+
 def parse_arguments():
     """
     This function parses the input arguments.
@@ -243,6 +243,7 @@ def parse_arguments():
 
     return files, end_time, start_time
 
+
 def setup_stat_parser():
     """
     This function takes care of setting up the statistics parser with the end_time, start_time and files arguments
@@ -267,6 +268,7 @@ def setup_stat_parser():
 
     return stat_parser
 
+
 def main():
 
     # set up everything
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py b/tangostationcontrol/tangostationcontrol/statistics/receiver.py
similarity index 99%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py
rename to tangostationcontrol/tangostationcontrol/statistics/receiver.py
index 233b819c5173fcfefc010f9025563ddcaef3e65c..b9c823c2a822cd4aee4a02bf6e921246e7dec471 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/receiver.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/receiver.py
@@ -12,6 +12,7 @@ import socket
 
 from lofar_station_client.statistics.packet import StatisticsPacket
 
+
 class receiver:
     """ Reads data from a file descriptor. """
 
@@ -57,6 +58,7 @@ class receiver:
 
         return data
 
+
 class tcp_receiver(receiver):
     def __init__(self, HOST, PORT):
         self.host = HOST
@@ -79,7 +81,6 @@ class tcp_receiver(receiver):
         return True
 
 
-
 class file_receiver(receiver):
     def __init__(self, filename):
         self.filename = filename
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/__init__.py
similarity index 100%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/__init__.py
rename to tangostationcontrol/tangostationcontrol/statistics/udp_dev/__init__.py
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_client.py
similarity index 98%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py
rename to tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_client.py
index ea5a644f10985257452b4d93249cea36c37708e9..98a85d62d1d3e610ed747b49f5034eb1dc3afeec 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_client.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_client.py
@@ -4,6 +4,7 @@ import netifaces as ni
 from datetime import datetime
 import time
 
+
 class UDP_Client:
 
     def __init__(self, server_ip:str, server_port:int):
@@ -40,6 +41,7 @@ class UDP_Client:
         # close the socket
         s.close()
 
+
 if __name__ == '__main__':
 
     if len(sys.argv) == 3:
@@ -56,7 +58,3 @@ if __name__ == '__main__':
 
     client = UDP_Client(server_ip,server_port)
     client.run()
-
-    
-        
-        
\ No newline at end of file
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_server.py
similarity index 99%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py
rename to tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_server.py
index 35ccb6bd92975bcfbd2ea877e5e5b38c3962b0c5..aab1365dfa661b0bef5ab280f1fe0f32ebbb493a 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_server.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_server.py
@@ -43,6 +43,7 @@ class UDP_Server:
     def get_recv_data(self):
         return self.recv_data
 
+
 if __name__ == '__main__':
     local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
     server = UDP_Server(local_ip,5600)
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py b/tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_write_manager.py
similarity index 100%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/udp_write_manager.py
rename to tangostationcontrol/tangostationcontrol/statistics/udp_dev/udp_write_manager.py
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py b/tangostationcontrol/tangostationcontrol/statistics/writer/__init__.py
similarity index 100%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/test/__init__.py
rename to tangostationcontrol/tangostationcontrol/statistics/writer/__init__.py
diff --git a/tangostationcontrol/tangostationcontrol/statistics/writer.py b/tangostationcontrol/tangostationcontrol/statistics/writer/entry.py
similarity index 79%
rename from tangostationcontrol/tangostationcontrol/statistics/writer.py
rename to tangostationcontrol/tangostationcontrol/statistics/writer/entry.py
index 23d3869959ccad60d0fa2d9c098aef259b6b9f88..0b3c60484c893d76918e0e926cd1a9c8114974d8 100644
--- a/tangostationcontrol/tangostationcontrol/statistics/writer.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/writer/entry.py
@@ -12,11 +12,18 @@ import time
 import sys
 from tango import DeviceProxy
 
-from tangostationcontrol.statistics_writer.receiver import tcp_receiver, file_receiver
-from tangostationcontrol.statistics_writer.hdf5_writer import sst_hdf5_writer, parallel_xst_hdf5_writer, bst_hdf5_writer
+from tangostationcontrol.statistics.receiver import file_receiver
+from tangostationcontrol.statistics.receiver import tcp_receiver
+from tangostationcontrol.statistics.writer.hdf5 import BstHdf5Writer
+from tangostationcontrol.statistics.writer.hdf5 import SstHdf5Writer
+from tangostationcontrol.statistics.writer.hdf5 import ParallelXstHdf5Writer
 
 import logging
-logging.basicConfig(level=logging.INFO, format = '%(asctime)s:%(levelname)s: %(message)s')
+
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s:%(levelname)s: %(message)s'
+)
 logger = logging.getLogger("statistics_writer")
 
 
@@ -77,24 +84,39 @@ def _create_receiver(filename, host, port):
         sys.exit(1)
 
 
-def _create_writer(mode, interval, output_dir, decimation):
+def _create_writer(
+    mode, interval, output_dir, decimation, device: DeviceProxy = None
+):
     """Create the writer"""
     if mode == "XST":
-        return parallel_xst_hdf5_writer(new_file_time_interval=interval, file_location=output_dir, decimation_factor=decimation)
+        return ParallelXstHdf5Writer(
+            new_file_time_interval=interval,
+            file_location=output_dir,
+            decimation_factor=decimation,
+        )
     elif mode == "SST":
-        return sst_hdf5_writer(new_file_time_interval=interval, file_location=output_dir, decimation_factor=decimation)
+        return SstHdf5Writer(
+            new_file_time_interval=interval,
+            file_location=output_dir,
+            decimation_factor=decimation,
+            device=device
+        )
     elif mode == "BST":
-        return bst_hdf5_writer(new_file_time_interval=interval, file_location=output_dir, decimation_factor=decimation)
+        return BstHdf5Writer(
+            new_file_time_interval=interval,
+            file_location=output_dir,
+            decimation_factor=decimation,
+        )
     else:
-        logger.fatal(f"Invalid mode: {mode}")
+        logger.fatal("Invalid mode: %s", mode)
         sys.exit(1)
 
 
-def _start_loop(receiver, writer, reconnect, filename, device):
+def _start_loop(receiver, writer, reconnect, filename):
     """Main loop"""
     try:
         while True:
-            _receive_packets(receiver, writer, reconnect, filename, device)
+            _receive_packets(receiver, writer, reconnect, filename)
     except KeyboardInterrupt:
         # user abort, don't complain
         logger.warning("Received keyboard interrupt. Stopping.")
@@ -102,10 +124,10 @@ def _start_loop(receiver, writer, reconnect, filename, device):
         writer.close_writer()
 
 
-def _receive_packets(receiver, writer, reconnect, filename, device):
+def _receive_packets(receiver, writer, reconnect, filename):
     try:
         packet = receiver.get_packet()
-        writer.next_packet(packet, device)
+        writer.next_packet(packet)
     except EOFError:
         if reconnect and not filename:
             logger.warning("Connection lost, attempting to reconnect")
@@ -168,7 +190,8 @@ def main():
     receiver = _create_receiver(filename, host, port)
 
     # create the writer
-    writer = _create_writer(mode, interval, output_dir, decimation)
+    writer = _create_writer(mode, interval, output_dir, decimation, device)
 
     # start looping
-    _start_loop(receiver, writer, reconnect, filename, device)
+    _start_loop(receiver, writer, reconnect, filename)
+
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py b/tangostationcontrol/tangostationcontrol/statistics/writer/hdf5.py
similarity index 50%
rename from tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py
rename to tangostationcontrol/tangostationcontrol/statistics/writer/hdf5.py
index 846621208075c644d00eb8260f4d70b4cd4fd122..4ac9dd9d451ae77a93b0f5f0d848d4de138cfd8c 100644
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/hdf5_writer.py
+++ b/tangostationcontrol/tangostationcontrol/statistics/writer/hdf5.py
@@ -20,21 +20,29 @@ from lofar_station_client.statistics.collector import BSTCollector
 from lofar_station_client.statistics.collector import XSTCollector
 from tangostationcontrol.statistics.collector import StationSSTCollector
 
-from lofar_station_client.statistics.packet import SSTPacket, XSTPacket, BSTPacket
+from lofar_station_client.statistics.packet import BSTPacket
+from lofar_station_client.statistics.packet import SSTPacket
+from lofar_station_client.statistics.packet import XSTPacket
 
+from tango import DeviceProxy
 
 logger = logging.getLogger("statistics_writer")
 
-__all__ = ["hdf5_writer", "parallel_xst_hdf5_writer", "xst_hdf5_writer", "sst_hdf5_writer", "bst_hdf5_writer"]
+__all__ = [
+    "HDF5Writer", "ParallelXstHdf5Writer", "XstHdf5Writer",
+    "SstHdf5Writer", "BstHdf5Writer",
+]
 
 
-class hdf5_writer(ABC):
-
+class HDF5Writer(ABC):
     SST_MODE = "SST"
     XST_MODE = "XST"
     BST_MODE = "BST"
 
-    def __init__(self, new_file_time_interval, file_location, statistics_mode, decimation_factor):
+    def __init__(
+        self, new_file_time_interval: int, file_location, statistics_mode,
+        decimation_factor, device: DeviceProxy = None
+    ):
 
         # all variables that deal with the matrix that's currently being decoded
         self.current_matrix = None
@@ -44,7 +52,8 @@ class hdf5_writer(ABC):
         self.statistics_counter = 0
 
         # the header of the first packet of a new matrix is written as metadata.
-        # Assumes all subsequent headers of the same matrix are identical (minus index)
+        # Assumes all subsequent headers of the same matrix are identical
+        # (minus index)
         self.statistics_header = None
 
         # file handing
@@ -54,25 +63,33 @@ class hdf5_writer(ABC):
         self.last_file_time = datetime.min.replace(tzinfo=pytz.UTC)
         self.file = None
 
-        # parameters that are configured depending on the mode the statistics writer is in (SST,XST,BST)
+        # parameters that are configured depending on the mode the statistics
+        # writer is in (SST,XST,BST)
         self.mode = statistics_mode.upper()
 
+        # Set device if any, defaults to None
+        self.device = device
+
     @abstractmethod
-    def decoder(self):
+    def decoder(self, packet):
         pass
 
     @abstractmethod
     def new_collector(self):
         pass
 
-    def next_packet(self, packet, device):
+    def next_packet(self, packet):
         """
-        All statistics packets come with a timestamp of the time they were measured. All the values will be spread across multiple packets.
-        As long as the timestamp is the same they belong in the same matrix. This code handles collecting the matrix from those multiple
-        packets as well as storing matrices and starting new ones
 
-        The code receives new packets and checks the statistics timestamp of them. If the timestamp is higher than the current timestamp
-        it will close the current matrix, store it and start a new one.
+        All statistics packets come with a timestamp of the time they were
+        measured. All the values will be spread across multiple packets.
+        As long as the timestamp is the same they belong in the same matrix.
+        This code handles collecting the matrix from those multiple packets as
+        well as storing matrices and starting new ones
+
+        The code receives new packets and checks the statistics timestamp of
+        them. If the timestamp is higher than the current timestamp it will
+        close the current matrix, store it and start a new one.
         """
 
         # process the packet
@@ -84,54 +101,68 @@ class hdf5_writer(ABC):
         # grab the timestamp
         statistics_timestamp = statistics_packet.timestamp()
 
-        # ignore packets with no timestamp, as they indicate FPGA processing was disabled
-        # and are useless anyway.
+        # ignore packets with no timestamp, as they indicate FPGA processing was
+        # disabled and are useless anyway.
         if statistics_packet.block_serial_number == 0:
-            logger.warning(f"Received statistics with no timestamp. Packet dropped.")
+            logger.warning(
+                "Received statistics with no timestamp. Packet dropped."
+            )
             return
 
-        # check if te statistics timestamp is unexpectedly older than the current one
+        # check if te statistics timestamp is unexpectedly older than the
+        # current one
         if statistics_timestamp < self.current_timestamp:
-            logger.warning(f"Received statistics with earlier timestamp than is currently being processed ({statistics_timestamp}). Packet dropped.")
+            logger.warning(
+                "Received statistics with earlier timestamp than is currently"
+                "being processed (%s). Packet dropped.",
+                statistics_timestamp
+            )
             return
 
-        # if this statistics packet has a new timestamp it means we need to start a new matrix
+        # if this statistics packet has a new timestamp it means we need to
+        # start a new matrix
         if statistics_timestamp > self.current_timestamp:
             self.start_new_matrix(statistics_timestamp)
             self.current_timestamp = statistics_timestamp
 
-        self.process_packet(packet, device)
+        self.process_packet(packet)
 
     def start_new_matrix(self, timestamp):
         """
         is called when a statistics packet with a newer timestamp is received.
-        Writes the matrix to the hdf5 file
-        Creates a new hdf5 file if needed
-        updates current timestamp and statistics matrix collector
+        Writes the matrix to the hdf5 file, creates a new hdf5 file if needed
+        and updates current timestamp and statistics matrix collector.
         """
 
+        # always increment
+        self.statistics_counter += 1
+
         # only write the specified fraction of statistics, skip the rest
         if self.statistics_counter % self.decimation_factor != 0:
-            logger.debug(f"Skipping statistic with timestamp: {timestamp}. Only writing 1/{self.decimation_factor} statistics")
-
-            # increment even though its skipped
-            self.statistics_counter += 1
+            logger.debug(
+                "Skipping statistic with timestamp: %s. Only writing"
+                "1/%d statistics", timestamp,
+                self.decimation_factor
+            )
             return
 
-        # received new statistic, so increment counter
-        self.statistics_counter += 1
-
-        logger.debug(f"starting new matrix with timestamp: {timestamp}")
+        logger.debug("starting new matrix with timestamp: %s", timestamp)
 
         # write the finished (and checks if its the first matrix)
         if self.current_matrix is not None:
             try:
                 self.write_matrix()
             except Exception as e:
-                time = self.current_timestamp.strftime("%Y-%m-%d-%H-%M-%S-%f")[:-3]
-                logger.exception(f"Exception while attempting to write matrix to HDF5. Matrix: {time} dropped")
-
-        # only start a new file if its time AND we are done with the previous matrix.
+                time = self.current_timestamp.strftime(
+                    "%Y-%m-%d-%H-%M-%S-%f"
+                )[:-3]
+                logger.exception(
+                    "Exception while attempting to write matrix to HDF5."
+                    "Matrix: %s dropped", time
+                )
+
+        # only start a new file if its time AND we are done with the previous
+        # matrix.
         if timestamp >= self.new_file_time_interval + self.last_file_time:
             self.start_new_hdf5(timestamp)
 
@@ -140,20 +171,29 @@ class hdf5_writer(ABC):
         self.statistics_header = None
 
     def write_matrix(self):
+        """Writes the finished matrix to the hdf5 file"""
+
         logger.debug("writing matrix to file")
-        """
-        Writes the finished matrix to the hdf5 file
-        """
 
         # create the new hdf5 group based on the timestamp of packets
-        current_group = self.file.create_group("{}_{}".format(self.mode, self.current_timestamp.isoformat(timespec="milliseconds")))
+        current_group = self.file.create_group(
+            "{}_{}".format(self.mode, self.current_timestamp.isoformat(
+                timespec="milliseconds"
+            ))
+        )
 
         # store the statistics values for the current group
         self.write_values_matrix(current_group)
 
         # might be optional, but they're easy to add.
-        current_group.create_dataset(name="nof_payload_errors", data=self.current_matrix.parameters["nof_payload_errors"])
-        current_group.create_dataset(name="nof_valid_payloads", data=self.current_matrix.parameters["nof_valid_payloads"])
+        current_group.create_dataset(
+            name="nof_payload_errors",
+            data=self.current_matrix.parameters["nof_payload_errors"]
+        )
+        current_group.create_dataset(
+            name="nof_valid_payloads",
+            data=self.current_matrix.parameters["nof_valid_payloads"]
+        )
 
         # get the statistics header
         header = self.statistics_header
@@ -163,10 +203,13 @@ class hdf5_writer(ABC):
             return
 
         # can't store datetime objects, convert to string instead
-        header["timestamp"] = header["timestamp"].isoformat(timespec="milliseconds")
+        header["timestamp"] = header["timestamp"].isoformat(
+            timespec="milliseconds"
+        )
 
-        # Stores the header of the packet received for this matrix as a list of atttributes
-        for k,v in header.items():
+        # Stores the header of the packet received for this matrix as a list of
+        # attributes
+        for k, v in header.items():
             if type(v) == dict:
                 for subk, subv in v.items():
                     current_group.attrs[f"{k}_{subk}"] = subv
@@ -181,7 +224,7 @@ class hdf5_writer(ABC):
         time_str = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
         return f"{self.file_location}/{self.mode}_{time_str}{suffix}"
 
-    def process_packet(self, packet, device):
+    def process_packet(self, packet):
         """
         Adds the newly received statistics packet to the statistics matrix
         """
@@ -189,7 +232,7 @@ class hdf5_writer(ABC):
         if self.statistics_counter % self.decimation_factor != 0:
             return
 
-        self.current_matrix.process_packet(packet, device)
+        self.current_matrix.process_packet(packet)
 
     def start_new_hdf5(self, timestamp):
 
@@ -197,7 +240,11 @@ class hdf5_writer(ABC):
             try:
                 self.file.close()
             except Exception as e:
-                logger.exception(f"Error while attempting to close hdf5 file to disk. file {self.file} likely empty, please verify integrity.")
+                logger.exception(
+                    "Error while attempting to close hdf5 file to disk. file"
+                    "%s likely empty, please verify integrity.",
+                    self.file,
+                )
 
         filename = self.next_filename(timestamp)
         logger.info(f"creating new file: {filename}")
@@ -211,9 +258,8 @@ class hdf5_writer(ABC):
         self.last_file_time = timestamp
 
     def close_writer(self):
-        """
-        Function that can be used to stop the writer without data loss.
-        """
+        """Function that can be used to stop the writer without data loss."""
+
         logger.debug("closing hdf5 file")
         if self.file is not None:
             if self.current_matrix is not None:
@@ -224,35 +270,84 @@ class hdf5_writer(ABC):
                 finally:
                     filename = str(self.file)
                     self.file.close()
-                    logger.debug(f"{filename} closed")
-                    logger.debug(f"Received a total of {self.statistics_counter} statistics while running. With {int(self.statistics_counter/self.decimation_factor)} written to disk ")
-
-
-class sst_hdf5_writer(hdf5_writer):
-    def __init__(self, new_file_time_interval, file_location, decimation_factor):
-        super().__init__(new_file_time_interval, file_location, hdf5_writer.SST_MODE, decimation_factor)
+                    logger.debug("%s closed", filename)
+                    logger.debug(
+                        "Received a total of %d statistics while running. With "
+                        "%d written to disk",
+                        self.statistics_counter,
+                        int(self.statistics_counter / self.decimation_factor)
+                    )
+
+
+class SstHdf5Writer(HDF5Writer):
+    def __init__(
+        self,
+        new_file_time_interval,
+        file_location,
+        decimation_factor,
+        device: DeviceProxy = None,
+    ):
+        super().__init__(
+            new_file_time_interval,
+            file_location,
+            HDF5Writer.SST_MODE,
+            decimation_factor,
+            device=device
+        )
 
     def decoder(self, packet):
         return SSTPacket(packet)
 
     def new_collector(self):
-        return StationSSTCollector()
+        return StationSSTCollector(self.device)
 
     def write_values_matrix(self, current_group):
         # store the SST values
-        current_group.create_dataset(name="values", data=self.current_matrix.parameters["sst_values"].astype(numpy.float32), compression="gzip")
-        try: 
-            current_group.create_dataset(name="rcu_attenuator_dB", data=self.current_matrix.parameters["rcu_attenuator_dB"].astype(numpy.int64), compression="gzip")
-            current_group.create_dataset(name="rcu_band_select", data=self.current_matrix.parameters["rcu_band_select"].astype(numpy.int64), compression="gzip")
-            current_group.create_dataset(name="rcu_dth_on", data=self.current_matrix.parameters["rcu_dth_on"].astype(numpy.bool_), compression="gzip")
+        current_group.create_dataset(
+            name="values",
+            data=self.current_matrix.parameters[
+                "sst_values"
+            ].astype(numpy.float32),
+            compression="gzip",
+        )
+        try:
+            current_group.create_dataset(
+                name="rcu_attenuator_dB",
+                data=self.current_matrix.parameters["rcu_attenuator_dB"].astype(
+                    numpy.int64
+                ),
+                compression="gzip",
+            )
+            current_group.create_dataset(
+                name="rcu_band_select",
+                data=self.current_matrix.parameters["rcu_band_select"].astype(
+                    numpy.int64
+                ),
+                compression="gzip",
+            )
+            current_group.create_dataset(
+                name="rcu_dth_on",
+                data=self.current_matrix.parameters["rcu_dth_on"].astype(
+                    numpy.bool_
+                ),
+                compression="gzip",
+            )
         except AttributeError as e:
             logger.warning("Device values not written.")
         except Exception as e:
             raise Exception from e
 
-class bst_hdf5_writer(hdf5_writer):
-    def __init__(self, new_file_time_interval, file_location, decimation_factor):
-        super().__init__(new_file_time_interval, file_location, hdf5_writer.BST_MODE, decimation_factor)
+
+class BstHdf5Writer(HDF5Writer):
+    def __init__(
+        self, new_file_time_interval, file_location, decimation_factor
+    ):
+        super().__init__(
+            new_file_time_interval,
+            file_location,
+            HDF5Writer.BST_MODE,
+            decimation_factor,
+        )
 
     def decoder(self, packet):
         return BSTPacket(packet)
@@ -262,12 +357,29 @@ class bst_hdf5_writer(hdf5_writer):
 
     def write_values_matrix(self, current_group):
         # store the BST values
-        current_group.create_dataset(name="values", data=self.current_matrix.parameters["bst_values"].astype(numpy.float32), compression="gzip")
-
-
-class xst_hdf5_writer(hdf5_writer):
-    def __init__(self, new_file_time_interval, file_location, decimation_factor, subband_index):
-        super().__init__(new_file_time_interval, file_location, hdf5_writer.XST_MODE, decimation_factor)
+        current_group.create_dataset(
+            name="values",
+            data=self.current_matrix.parameters["bst_values"].astype(
+                numpy.float32
+            ),
+            compression="gzip",
+        )
+
+
+class XstHdf5Writer(HDF5Writer):
+    def __init__(
+        self,
+        new_file_time_interval,
+        file_location,
+        decimation_factor,
+        subband_index,
+    ):
+        super().__init__(
+            new_file_time_interval,
+            file_location,
+            HDF5Writer.XST_MODE,
+            decimation_factor,
+        )
         self.subband_index = subband_index
 
     def decoder(self, packet):
@@ -276,37 +388,50 @@ class xst_hdf5_writer(hdf5_writer):
     def new_collector(self):
         return XSTCollector()
 
-    def next_filename(self, timestamp):
+    def next_filename(self, timestamp, suffix=".h5"):
         time_str = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
-        return f"{self.file_location}/{self.mode}_SB{self.subband_index}_{time_str}.h5"
+        return (
+            f"{self.file_location}/{self.mode}_SB{self.subband_index}_"
+            f"{time_str}{suffix}"
+        )
 
     def write_values_matrix(self, current_group):
-        # requires a function call to transform the xst_blocks in to the right structure
+        # requires a function call to transform the xst_blocks in to the right
+        # structure
         #
-        # since we have a dedicated writer per subband_index, they all end up in slot 0
-        # in their writer, so we only need to store the first set of xst_values.
-        current_group.create_dataset(name="values", data=self.current_matrix.xst_values([0])[0].astype(numpy.cfloat), compression="gzip")
-
-
-class parallel_xst_hdf5_writer:
-    """ Writes multiple subbands in parallel. Each subband gets written to its own HDF5 file(s). """
-
-    def __init__(self, new_file_time_interval, file_location, decimation_factor):
-        # maintain a dedicated hdf5_writer per subband
+        # since we have a dedicated writer per subband_index, they all end up in
+        # slot 0 in their writer, so we only need to store the first set of
+        # xst_values.
+        current_group.create_dataset(
+            name="values", data=self.current_matrix.xst_values([0])[0].astype(
+                numpy.cfloat
+            ),
+            compression="gzip",
+        )
+
+
+class ParallelXstHdf5Writer:
+    """Writes multiple subbands in parallel. Each subband to separate file."""
+
+    def __init__(
+            self, new_file_time_interval, file_location, decimation_factor
+    ):
+        # maintain a dedicated HDF5Writer per subband
         self.writers = {}
 
         # function to create a new writer, to avoid having to store
         # all the init parameters just for this purpose.
         def new_writer(subband):
-            return xst_hdf5_writer(
-                       new_file_time_interval,
-                       file_location,
-                       decimation_factor,
-                       subband)
+            return XstHdf5Writer(
+                new_file_time_interval,
+                file_location,
+                decimation_factor,
+                subband,
+            )
 
         self.new_writer = new_writer
 
-    def next_packet(self, packet, device=None):
+    def next_packet(self, packet):
         # decode to get subband of this packet
         fields = XSTPacket(packet)
         subband = fields.subband_index
@@ -316,11 +441,10 @@ class parallel_xst_hdf5_writer:
             self.writers[subband] = self.new_writer(subband)
 
         # demux packet to the correct writer
-        self.writers[subband].next_packet(packet, device)
+        self.writers[subband].next_packet(packet)
 
     def close_writer(self):
         for writer in self.writers.values():
             writer.close_writer()
 
         self.writers = {}
-
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5
deleted file mode 100644
index 26179fc59a2fb032bb35d779676befd4ebe26356..0000000000000000000000000000000000000000
Binary files a/tangostationcontrol/tangostationcontrol/statistics_writer/SST_2021-10-04-07-36-52.h5 and /dev/null differ
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5
deleted file mode 100644
index 2d04a526e1ef73d7bd636e3b564192d95e49cef5..0000000000000000000000000000000000000000
Binary files a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_1.h5 and /dev/null differ
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5
deleted file mode 100644
index 45fd32d831508f8d632c6f1778d4d9bb73059294..0000000000000000000000000000000000000000
Binary files a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_2.h5 and /dev/null differ
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5 b/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5
deleted file mode 100644
index 5c971e8e2cea131d6c9ba8b7e6b1d645f205f276..0000000000000000000000000000000000000000
Binary files a/tangostationcontrol/tangostationcontrol/statistics_writer/test/SST_10m_test_3.h5 and /dev/null differ
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin b/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin
deleted file mode 100644
index e94347b86a0a03b940eb84980ec8f6d3b6d4e2d7..0000000000000000000000000000000000000000
Binary files a/tangostationcontrol/tangostationcontrol/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin and /dev/null differ
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py b/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py
deleted file mode 100644
index 74101b93de2e83824c70e4630e8560ae24b28fa8..0000000000000000000000000000000000000000
--- a/tangostationcontrol/tangostationcontrol/statistics_writer/test/test_server.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import socket
-import time
-
-import argparse
-
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger("statistics_test_server")
-logger.setLevel(logging.DEBUG)
-
-parser = argparse.ArgumentParser(description='Select what hostname to use and what port to use')
-parser.add_argument('--port', type=int, help='port to use', default=65433)
-parser.add_argument('--host',  help='host to use', default='127.0.0.1')
-parser.add_argument('--file',  help='file to use as data', default='devices_test_SDP_SST_statistics_packets.bin')
-parser.add_argument('--interval', type=int,  help='ime between sending entire files content', default=1)
-
-args = parser.parse_args()
-HOST = args.host
-PORT = args.port
-FILE = args.file
-INTERVAL = args.interval
-
-
-while True:
-    try:
-        f = open(FILE, "rb")
-        data = f.read()
-    except Exception as e:
-        logger.error(f"File not found, are you sure: '{FILE}' is a valid path, Exception: {e}")
-        exit()
-
-    try:
-        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
-            logger.debug(f"Starting TCP test server on {HOST} {PORT}")
-            logger.debug("To interrupt the script, press Ctrl-C twice within a second")
-
-            s.bind((HOST, PORT))
-            s.listen()
-            conn, addr = s.accept()
-
-            with conn:
-                logger.debug(f'Connected by: {addr}')
-
-                while True:
-                    time.sleep(INTERVAL)
-                    conn.sendall(data)
-
-    except KeyboardInterrupt:
-        logger.info("Received keyboard interrupt. Stopping.")
-        exit()
-    except Exception as e:
-        logger.warning(f"Exception occurred: {e}")
-
-        # just do 2 interrupt within a second to quit the program
-        time.sleep(1)
diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py b/tangostationcontrol/tangostationcontrol/statistics_writer/udp_dev/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
index 96eb83300bec3352b1c0572653f38bf96a2e77d9..c567f476b21f05aa1806b454092c5a1660849ade 100644
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
+++ b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py
@@ -389,3 +389,11 @@ class TestAntennafieldDevice(device_base.DeviceTestCase):
         antenna_properties = {'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use}
         with DeviceTestContext(antennafield.AntennaField, properties={**self.AT_PROPERTIES, **antenna_properties}, process=True) as proxy:
             numpy.testing.assert_equal(numpy.array([True] + [False] * 95), proxy.Antenna_Usage_Mask_R)
+    
+    def test_read_Antenna_Names(self):
+       """ Verify if Antenna_Names_R is correctly retrieved """
+       antenna_names = ["C0","C1","C2","C3","C4"]
+       antenna_properties = {'Antenna_Names' : antenna_names}
+       with DeviceTestContext(antennafield.AntennaField, properties={**self.AT_PROPERTIES, **antenna_properties}, process=True) as proxy:
+        for i in range(len(antenna_names)):
+            self.assertTrue(proxy.Antenna_Names_R[i]==f"C{i}")
diff --git a/tangostationcontrol/tangostationcontrol/test/statistics/test_writer.py b/tangostationcontrol/tangostationcontrol/test/statistics/test_writer.py
index 56d6d85e0d83c6b9ac1e35e981ade193ab90d317..9b73607fafa546aa0cc0db63c1569aebdff99114 100644
--- a/tangostationcontrol/tangostationcontrol/test/statistics/test_writer.py
+++ b/tangostationcontrol/tangostationcontrol/test/statistics/test_writer.py
@@ -7,13 +7,14 @@
 # Distributed under the terms of the APACHE license.
 # See LICENSE.txt for more info.
 
-from tangostationcontrol.test import base
-from tangostationcontrol.statistics import writer
 import sys
 from os.path import dirname, isfile
 from tempfile import TemporaryDirectory
 from unittest import mock
 
+from tangostationcontrol.test import base
+from tangostationcontrol.statistics.writer import entry
+
 
 class TestStatisticsWriter(base.TestCase):
 
@@ -21,20 +22,30 @@ class TestStatisticsWriter(base.TestCase):
 
     def test_xst(self):
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "XST", "--file", dirname(__file__) + "/SDP_XST_statistics_packets.bin", "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "XST",
+                "--file", dirname(__file__) + "/SDP_XST_statistics_packets.bin",
+                "--output_dir", tmpdir
+            ]
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
 
             # check if file was written
             self.assertTrue(isfile(f"{tmpdir}/XST_SB102_2021-09-13-13-21-32.h5"))
 
     def test_xst_multiple_subbands(self):
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "XST", "--file", dirname(__file__) + "/SDP_XST_statistics_packets_multiple_subbands.bin", "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "XST",
+                "--file", dirname(__file__) + "/SDP_XST_statistics_packets_multiple_subbands.bin",
+                "--output_dir", tmpdir
+            ]
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
 
             # check if files were written
             self.assertTrue(isfile(f"{tmpdir}/XST_SB102_2021-09-13-13-21-32.h5"))
@@ -42,10 +53,15 @@ class TestStatisticsWriter(base.TestCase):
 
     def test_bst(self):
         with TemporaryDirectory() as tmpdir:
-            new_sys_argv = [sys.argv[0], "--mode", "BST", "--file", dirname(__file__) + "/SDP_BST_statistics_packets.bin", "--output_dir", tmpdir]
-            with mock.patch.object(writer.sys, 'argv', new_sys_argv):
+            new_sys_argv = [
+                sys.argv[0],
+                "--mode", "BST",
+                "--file", dirname(__file__) + "/SDP_BST_statistics_packets.bin",
+                "--output_dir", tmpdir
+            ]
+            with mock.patch.object(entry.sys, 'argv', new_sys_argv):
                 with self.assertRaises(SystemExit):
-                    writer.main()
+                    entry.main()
 
             # check if file was written
             self.assertTrue(isfile(f"{tmpdir}/BST_2022-05-20-11-08-44.h5"))
diff --git a/tangostationcontrol/tox.ini b/tangostationcontrol/tox.ini
index 46de7e82971df8e253affc1f8c7b6f47cd409f9f..9bd8b804bcfabb04d4caf630c926fdcbe30ef1f9 100644
--- a/tangostationcontrol/tox.ini
+++ b/tangostationcontrol/tox.ini
@@ -29,10 +29,19 @@ commands = {envpython} -m stestr run {posargs}
 [testenv:integration]
 ; Warning running integration tests will make changes to your docker system!
 ; These tests should only be run by the integration-test docker container.
+allowlist_externals = echo
 passenv = TANGO_HOST
-setenv = TESTS_DIR=./tangostationcontrol/integration_test/{env:TEST_MODULE:default}
+setenv =
+    VIRTUAL_ENV={envdir}
+    TESTS_DIR=./tangostationcontrol/integration_test/{env:TEST_MODULE:default}
+    PYTHON={envpython} -m coverage run --source tangostationcontrol --parallel-mode
 commands =
+    echo "Integration test directory configured for{env:TESTS_DIR} ({env:TEST_MODULE})"
     {envpython} -m stestr run --serial {posargs}
+    {envpython} -m coverage combine
+    {envpython} -m coverage html --omit='*test*' -d cover
+    {envpython} -m coverage xml -o coverage.xml
+    {envpython} -m coverage report --omit='*test*'
 
 [testenv:cover]
 ; stestr does not natively support generating coverage reports use