diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b57b144c34d32e6e69c0029025b01a11d329e78e..2bc29c89d7025579853037d7793db1281dc0fdde 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,17 +15,30 @@ stages:
   - static-analysis
   - unit-tests
   - integration-tests
-linting:
+newline_at_eof:
+      stage: linting
+      before_script:
+        - pip3 install -r devices/test-requirements.txt
+      script:
+        - flake8 --filename *.sh,*.conf,*.md,*.yml --select=W292 --exclude .tox,.egg-info,docker
+python_linting:
   stage: linting
   script:
     - cd devices
     - tox -e pep8
-static-analysis:
+bandit:
   stage: static-analysis
-  allow_failure: true
   script:
     - cd devices
     - tox -e bandit
+shellcheck:
+  stage: static-analysis
+  allow_failure: true
+  before_script:
+    - sudo apt-get update
+    - sudo apt-get install -y shellcheck
+  script:
+    - shellcheck **/*.sh
 unit_test:
   stage: unit-tests
   before_script:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..ef820d4039a54bf590e5c675c97a718b0681dc6e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter"]
+	path = docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter
+	url = https://git.astron.nl/lofar2.0/ska-tango-grafana-exporter.git
+	branch = station-control
diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json
index 00a2035a6e8eacc4d72da04fe648f5d6b6f25a9d..9fe53ba0388a86fb2f6503e3f239a5b6d5c3464b 100644
--- a/CDB/LOFAR_ConfigDb.json
+++ b/CDB/LOFAR_ConfigDb.json
@@ -816,6 +816,25 @@
                 }
             }
         },
+        "UNB2": {
+            "LTS": {
+                "UNB2": {
+                    "LTS/UNB2/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "despi.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4842"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "StatsCrosslet": {
             "CS997": {
                 "StatsCrosslet": {
diff --git a/CDB/jasper_ConfigDb.json b/CDB/jasper_ConfigDb.json
new file mode 100644
index 0000000000000000000000000000000000000000..d31074cc3537624d9f3e73f9e19baa388494706d
--- /dev/null
+++ b/CDB/jasper_ConfigDb.json
@@ -0,0 +1,816 @@
+{
+    "servers": {
+        "Femto": {
+            "CS999": {
+                "Femto": {
+                    "opc-ua/test-femto/1": {}
+                }
+            }
+        },
+        "observation_control": {
+            "LTS": {
+                "ObservationControl": {
+                    "LTS/ObservationControl/1": {}
+                }
+            }
+        },
+        "PCC": {
+            "LTS": {
+                "PCC": {
+                    "LTS/PCC/1": {
+                        "attribute_properties": {
+                            "Ant_mask_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "CLK_Enable_PWR_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "CLK_I2C_STATUS_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "CLK_PLL_error_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "CLK_PLL_locked_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "CLK_monitor_rate_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "CLK_translator_busy_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "HBA_element_LNA_pwr_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_LNA_pwr_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_beamformer_delays_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_beamformer_delays_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_led_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_led_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_pwr_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "HBA_element_pwr_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_ADC_lock_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_I2C_STATUS_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_ID_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_LED0_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_LED0_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_LED1_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_LED1_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_Pwr_dig_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_attenuator_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_attenuator_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_band_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_band_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "RCU_mask_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_monitor_rate_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1.0",
+                                    "1.0"
+                                ],
+                                "rel_change": [
+                                    "-1.0",
+                                    "1.0"
+                                ]
+                            },
+                            "RCU_temperature_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1.0",
+                                    "1.0"
+                                ],
+                                "rel_change": [
+                                    "-1.0",
+                                    "1.0"
+                                ]
+                            },
+                            "RCU_translator_busy_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "RCU_version_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "State": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "event_period": [
+                                    "0"
+                                ]
+                            },
+                            "Status": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "event_period": [
+                                    "0"
+                                ]
+                            }
+                        },
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "ltspi.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4842"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "polled_attr": [
+                                "state",
+                                "1000",
+                                "status",
+                                "1000",
+                                "ant_mask_rw",
+                                "1000",
+                                "rcu_adc_lock_r",
+                                "1000",
+                                "rcu_attenuator_r",
+                                "1000",
+                                "rcu_attenuator_rw",
+                                "1000",
+                                "rcu_band_r",
+                                "1000",
+                                "rcu_band_rw",
+                                "1000",
+                                "rcu_id_r",
+                                "1000",
+                                "rcu_led0_r",
+                                "1000",
+                                "rcu_led0_rw",
+                                "1000",
+                                "rcu_mask_rw",
+                                "1000",
+                                "rcu_monitor_rate_rw",
+                                "1000",
+                                "rcu_pwr_dig_r",
+                                "1000",
+                                "rcu_temperature_r",
+                                "1000",
+                                "rcu_version_r",
+                                "1000",
+                                "hba_element_beamformer_delays_r",
+                                "1000",
+                                "hba_element_beamformer_delays_rw",
+                                "1000",
+                                "hba_element_led_r",
+                                "1000",
+                                "hba_element_led_rw",
+                                "1000",
+                                "hba_element_pwr_r",
+                                "1000",
+                                "hba_element_pwr_rw",
+                                "1000",
+                                "clk_enable_pwr_r",
+                                "1000",
+                                "clk_i2c_status_r",
+                                "1000",
+                                "clk_monitor_rate_rw",
+                                "1000",
+                                "clk_pll_error_r",
+                                "1000",
+                                "clk_pll_locked_r",
+                                "1000",
+                                "clk_translator_busy_r",
+                                "1000",
+                                "hba_element_lna_pwr_r",
+                                "1000",
+                                "hba_element_lna_pwr_rw",
+                                "1000",
+                                "rcu_i2c_status_r",
+                                "1000",
+                                "rcu_led1_r",
+                                "1000",
+                                "rcu_led1_rw",
+                                "1000",
+                                "rcu_translator_busy_r",
+                                "1000"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "random_data": {
+            "LTS": {
+                "Random_Data": {
+                    "LTS/random_data/1": {
+                        "properties": {
+                            "polled_attr": [
+                                "rnd1",
+                                "1000",
+                                "rnd2",
+                                "1000",
+                                "rnd3",
+                                "1000",
+                                "rnd4",
+                                "1000",
+                                "rnd5",
+                                "1000",
+                                "rnd6",
+                                "1000",
+                                "rnd7",
+                                "1000",
+                                "rnd8",
+                                "1000",
+                                "rnd9",
+                                "1000",
+                                "rnd10",
+                                "1000",
+                                "rnd11",
+                                "1000",
+                                "rnd12",
+                                "1000",
+                                "rnd13",
+                                "1000",
+                                "rnd14",
+                                "1000",
+                                "rnd15",
+                                "1000",
+                                "rnd16",
+                                "1000",
+                                "rnd17",
+                                "1000",
+                                "rnd18",
+                                "1000",
+                                "rnd19",
+                                "1000",
+                                "rnd20",
+                                "1000",
+                                "rnd21",
+                                "1000",
+                                "state",
+                                "1000",
+                                "status",
+                                "1000"
+                            ]
+                        }
+                    },
+                    "LTS/random_data/2": {
+                        "properties": {
+                            "polled_attr": [
+                                "rnd1",
+                                "100",
+                                "rnd2",
+                                "100",
+                                "rnd3",
+                                "100",
+                                "rnd4",
+                                "100",
+                                "rnd5",
+                                "100",
+                                "rnd6",
+                                "100",
+                                "rnd7",
+                                "100",
+                                "rnd8",
+                                "100",
+                                "rnd9",
+                                "100",
+                                "rnd10",
+                                "100",
+                                "rnd11",
+                                "100",
+                                "rnd12",
+                                "100",
+                                "rnd13",
+                                "100",
+                                "rnd14",
+                                "100",
+                                "rnd15",
+                                "100",
+                                "rnd16",
+                                "100",
+                                "rnd17",
+                                "100",
+                                "rnd18",
+                                "100",
+                                "rnd19",
+                                "100",
+                                "rnd20",
+                                "100"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "SDP": {
+            "LTS": {
+                "SDP": {
+                    "LTS/SDP/1": {
+                        "attribute_properties": {
+                            "SDP_mask_RW": {
+                                "event_period": [
+                                    "60000"
+                                ]
+                            },
+                            "State": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "Status": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "fpga_mask_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "fpga_scrap_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "fpga_scrap_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "fpga_status_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "fpga_temp_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "fpga_version_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "fpga_weights_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "fpga_weights_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "tr_busy_R": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "tr_reload_RW": {
+                                "archive_period": [
+                                    "600000"
+                                ]
+                            },
+                            "tr_tod_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-1",
+                                    "1"
+                                ],
+                                "rel_change": [
+                                    "-1",
+                                    "1"
+                                ]
+                            },
+                            "tr_uptime_R": {
+                                "archive_period": [
+                                    "600000"
+                                ],
+                                "archive_rel_change": [
+                                    "-3600",
+                                    "3600"
+                                ],
+                                "rel_change": [
+                                    "-10",
+                                    "10"
+                                ]
+                            }
+                        },
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "dop36.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "polled_attr": [
+                                "fpga_temp_r",
+                                "1000",
+                                "state",
+                                "1000",
+                                "status",
+                                "1000",
+                                "fpga_mask_rw",
+                                "1000",
+                                "fpga_scrap_r",
+                                "1000",
+                                "fpga_scrap_rw",
+                                "1000",
+                                "fpga_status_r",
+                                "1000",
+                                "fpga_version_r",
+                                "1000",
+                                "fpga_weights_r",
+                                "1000",
+                                "fpga_weights_rw",
+                                "1000",
+                                "tr_busy_r",
+                                "1000",
+                                "tr_reload_rw",
+                                "1000",
+                                "tr_tod_r",
+                                "1000",
+                                "tr_uptime_r",
+                                "1000"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "SST": {
+            "LTS": {
+                "SST": {
+                    "LTS/SST/1": {
+                        "properties": {
+                            "Statistics_Client_Port": [
+                                "5001"
+                            ],
+                            "OPC_Server_Name": [
+                                "dop36.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "UNB2": {
+            "LTS": {
+                "UNB2": {
+                    "LTS/UNB2/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "despi.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4842"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "StatsCrosslet": {
+            "CS997": {
+                "StatsCrosslet": {
+                    "opc-ua/test-crossletstats/1": {
+                        "attribute_properties": {
+                            "visibilities_imag": {
+                                "archive_rel_change": [
+                                    "-0.1",
+                                    "0.1"
+                                ],
+                                "rel_change": [
+                                    "-0.1",
+                                    "0.1"
+                                ]
+                            },
+                            "visibilities_real": {
+                                "archive_rel_change": [
+                                    "-0.1",
+                                    "0.1"
+                                ],
+                                "rel_change": [
+                                    "-0.1",
+                                    "0.1"
+                                ]
+                            }
+                        },
+                        "properties": {
+                            "polled_attr": [
+                                "integration_time",
+                                "0",
+                                "pause_time",
+                                "0",
+                                "rcu_modes",
+                                "0",
+                                "state",
+                                "0",
+                                "status",
+                                "0",
+                                "subband",
+                                "0",
+                                "time_stamp",
+                                "0",
+                                "visibilities_imag",
+                                "0",
+                                "visibilities_real",
+                                "0"
+                            ]
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/README.md b/README.md
index b7b4398a9581bf0771fa2e8a669f1e53c92b75d2..192b3edb7713088120b672065296575c255adfa6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
 # Tango Station Control
 
-Station Control software related to Tango devices.
\ No newline at end of file
+Station Control software related to Tango devices.
diff --git a/devices/clients/README.md b/devices/clients/README.md
index 3613344461e8abb64e5a68a1d30c68b3927d22b4..083420b38dc611fd8096110ca42d46c375d3db60 100644
--- a/devices/clients/README.md
+++ b/devices/clients/README.md
@@ -1,4 +1,4 @@
 this folder contains all the comms_client implementations for organisation
 
 ### How to add a new client
-soon™
\ No newline at end of file
+soon™
diff --git a/devices/clients/opcua_client.py b/devices/clients/opcua_client.py
index 8a986a0c7f98819ecad9ea6a5710aaca19c1ac0c..6b687837a393a97727a231cea698fb9137485946 100644
--- a/devices/clients/opcua_client.py
+++ b/devices/clients/opcua_client.py
@@ -18,7 +18,6 @@ numpy_to_OPCua_dict = {
     numpy.uint32: opcua.ua.VariantType.UInt32,
     numpy.int64: opcua.ua.VariantType.Int64,
     numpy.uint64: opcua.ua.VariantType.UInt64,
-    numpy.datetime_data: opcua.ua.VariantType.DateTime, # is this the right type, does it even matter?
     numpy.float32: opcua.ua.VariantType.Float,
     numpy.double: opcua.ua.VariantType.Double,
     numpy.float64: opcua.ua.VariantType.Double,
@@ -59,9 +58,8 @@ class OPCUAConnection(CommClient):
                 self.name_space_index = namespace
 
         except Exception as e:
-            #TODO remove once SDP is fixed
-            self.streams.warn_stream("Cannot determine the OPC-UA name space index.  Will try and use the default = 2.")
-            self.name_space_index = 2
+            self.streams.error_stream("Could not determine namespace index from namespace: %s: %s", namespace, e)
+            raise Exception("Could not determine namespace index from namespace %s", namespace) from e
 
         self.obj = self.client.get_objects_node()
         self.check_nodes()
diff --git a/devices/clients/statistics_client.py b/devices/clients/statistics_client.py
index d2dfee9a9796a392ae02968cb0d48a6eeb584918..6f91c37be91738b559041dc0e4b02475e162ad53 100644
--- a/devices/clients/statistics_client.py
+++ b/devices/clients/statistics_client.py
@@ -6,6 +6,8 @@ from .comms_client import CommClient
 from .tcp_replicator import TCPReplicator
 from .udp_receiver import UDPReceiver
 
+from devices.sdp.statistics_collector import StatisticsConsumer
+
 logger = logging.getLogger()
 
 
@@ -18,11 +20,11 @@ class StatisticsClient(CommClient):
     def start(self):
         super().start()
 
-    def __init__(self, statistics_collector_class, udp_options, tcp_options, fault_func, streams, try_interval=2, queuesize=1024):
+    def __init__(self, collector, udp_options, tcp_options, fault_func, streams, try_interval=2, queuesize=1024):
         """
         Create the statistics client and connect() to it and get the object node.
 
-        statistics_collector_class: a subclass of StatisticsCollector that specialises in processing the received packets.
+        collector: a subclass of StatisticsCollector that specialises in processing the received packets.
         host: hostname to listen on
         port: port number to listen on
         """
@@ -30,7 +32,7 @@ class StatisticsClient(CommClient):
         self.udp_options = udp_options
         self.tcp_options = tcp_options
         self.queuesize = queuesize
-        self.statistics_collector_class = statistics_collector_class
+        self.collector = collector
 
         super().__init__(fault_func, streams, try_interval)
 
@@ -60,7 +62,7 @@ class StatisticsClient(CommClient):
                                    self.udp_options)
 
             self.tcp = TCPReplicator(self.replicator_queue, self.tcp_options)
-            self.statistics = self.statistics_collector_class(self.collector_queue)
+            self.statistics = StatisticsConsumer(self.collector_queue, self.collector)
 
         return super().connect()
 
@@ -79,14 +81,13 @@ class StatisticsClient(CommClient):
         try:
             self.statistics.disconnect()
         except Exception:
-            # nothing we can do, but we should continue cleaning up
-            logger.log_exception("Could not disconnect statistics processing class")
+            logger.exception("Could not disconnect statistics processing class")
 
         try:
             self.udp.disconnect()
         except Exception:
             # nothing we can do, but we should continue cleaning up
-            logger.log_exception("Could not disconnect UDP receiver class")
+            logger.exception("Could not disconnect UDP receiver class")
 
         try:
             self.tcp.disconnect()
@@ -121,7 +122,7 @@ class StatisticsClient(CommClient):
         # redirect to right object. this works as long as the parameter names are unique among them.
         if annotation["type"] == "statistics":
             def read_function():
-                return self.statistics.parameters[parameter]
+                return self.collector.parameters[parameter]
         elif annotation["type"] == "udp":
             def read_function():
                 return self.udp.parameters[parameter]
diff --git a/devices/devices/sdp/sdp.py b/devices/devices/sdp/sdp.py
index c1730ab621f0da57bc486240ec662c84f6cde1ed..9bc94e6c811ac4e63a88c035ef62eba3998df895 100644
--- a/devices/devices/sdp/sdp.py
+++ b/devices/devices/sdp/sdp.py
@@ -86,6 +86,12 @@ class SDP(hardware_device):
         mandatory=True
     )
 
+    FPGA_subband_weights_RW_default = device_property(
+        dtype='DevVarULongArray',
+        mandatory=False,
+        default_value=[[8192] * 12 * 512] * 16
+    )
+
     # ----------
     # Attributes
     # ----------
@@ -102,15 +108,16 @@ class SDP(hardware_device):
     FPGA_sdp_info_antenna_band_index_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_antenna_band_index_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_block_period_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_block_period_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_f_adc_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_f_adc_R"], datatype=numpy.uint32, dims=(16,))
-    FPGA_sdp_info_f_sub_type_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_f_sub_type_R"], datatype=numpy.uint32, dims=(16,))
+    FPGA_sdp_info_fsub_type_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_fsub_type_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_nyquist_sampling_zone_index_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_nyquist_sampling_zone_index_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_nyquist_sampling_zone_index_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_nyquist_sampling_zone_index_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE)
     FPGA_sdp_info_observation_id_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_observation_id_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_observation_id_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_observation_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE)
     FPGA_sdp_info_station_id_R = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_station_id_R"], datatype=numpy.uint32, dims=(16,))
     FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["2:FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE)
+    FPGA_subband_weights_R = attribute_wrapper(comms_annotation=["2:FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(12 * 512, 16))
+    FPGA_subband_weights_RW = attribute_wrapper(comms_annotation=["2:FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(12 * 512, 16))
     FPGA_temp_R = attribute_wrapper(comms_annotation=["2:FPGA_temp_R"], datatype=numpy.float_, dims=(16,))
-    FPGA_version_R = attribute_wrapper(comms_annotation=["2:FPGA_version_R"], datatype=numpy.str_, dims=(16,))
     FPGA_weights_R = attribute_wrapper(comms_annotation=["2:FPGA_weights_R"], datatype=numpy.int16, dims=(12 * 488 * 2, 16))
     FPGA_weights_RW = attribute_wrapper(comms_annotation=["2:FPGA_weights_RW"], datatype=numpy.int16, dims=(12 * 488 * 2, 16), access=AttrWriteType.READ_WRITE)
     FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["2:FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(12, 16))
diff --git a/devices/devices/sdp/sst.py b/devices/devices/sdp/sst.py
index 62fbc260e98c98ae8e1cd6590184af442dc8fd9f..79fb6fb272b199d3069be03825cfd395f9d18929 100644
--- a/devices/devices/sdp/sst.py
+++ b/devices/devices/sdp/sst.py
@@ -57,6 +57,12 @@ class SST(Statistics):
         mandatory=True
     )
 
+    FPGA_sst_offload_weighted_subbands_RW_default = device_property(
+        dtype='DevVarBooleanArray',
+        mandatory=False,
+        default_value=[True] * 16
+    )
+
     # ----------
     # Attributes
     # ----------
@@ -70,8 +76,8 @@ class SST(Statistics):
     FPGA_sst_offload_hdr_ip_destination_address_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_ip_destination_address_R"], datatype=numpy.str_, dims=(16,))
     FPGA_sst_offload_hdr_udp_destination_port_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE)
     FPGA_sst_offload_hdr_udp_destination_port_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,))
-    FPGA_sst_offload_selector_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_selector_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE)
-    FPGA_sst_offload_selector_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_selector_R"], datatype=numpy.bool_, dims=(16,))
+    FPGA_sst_offload_weighted_subbands_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_weighted_subbands_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE)
+    FPGA_sst_offload_weighted_subbands_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["2:FPGA_sst_offload_weighted_subbands_R"], datatype=numpy.bool_, dims=(16,))
 
     # number of packets with valid payloads
     nof_valid_payloads_R    = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_valid_payloads"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64)
diff --git a/devices/devices/sdp/statistics.py b/devices/devices/sdp/statistics.py
index 71d19ee7413671bccd3cc4310b3804ed8e69868d..8434d7bd9dc0193c0f09466a37a278be060fcb1f 100644
--- a/devices/devices/sdp/statistics.py
+++ b/devices/devices/sdp/statistics.py
@@ -136,7 +136,8 @@ class Statistics(hardware_device, metaclass=ABCMeta):
             # tcp_host has default value
         }
 
-        self.statistics_client = StatisticsClient(self.STATISTICS_COLLECTOR_CLASS, udp_options, tcp_options, self.Fault, self)
+        self.statistics_collector = self.STATISTICS_COLLECTOR_CLASS()
+        self.statistics_client = StatisticsClient(self.statistics_collector, udp_options, tcp_options, self.Fault, self)
 
         self.OPCUA_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Fault, self)
 
diff --git a/devices/devices/sdp/statistics_collector.py b/devices/devices/sdp/statistics_collector.py
index 23a9b734cf58d7c1836569533f13ba9cc7a9dd1e..d19ad01b2a10096ebc637b5a24d51917317afc9f 100644
--- a/devices/devices/sdp/statistics_collector.py
+++ b/devices/devices/sdp/statistics_collector.py
@@ -8,9 +8,8 @@ from clients.statistics_client_thread import StatisticsClientThread
 
 logger = logging.getLogger()
 
-
-class StatisticsCollector(Thread, StatisticsClientThread):
-    """ Base class to process statistics packets from a queue, asynchronously. """
+class StatisticsCollector:
+    """ Base class to process statistics packets into parameters matrices. """
 
     # Maximum number of antenna inputs we support (used to determine array sizes)
     MAX_INPUTS = 192
@@ -18,22 +17,9 @@ class StatisticsCollector(Thread, StatisticsClientThread):
     # Maximum number of subbands we support (used to determine array sizes)
     MAX_SUBBANDS = 512
 
-    # No default options required, for now?
-    _default_options = {}
-
-    def __init__(self, queue: Queue):
-        self.queue = queue
-        self.last_packet = None
-
+    def __init__(self):
         self.parameters = self._default_parameters()
 
-        super().__init__()
-        self.start()
-
-    @property
-    def _options(self) -> dict:
-        return StatisticsCollector._default_options
-
     def _default_parameters(self):
         return {
             "nof_packets":           numpy.uint64(0),
@@ -45,50 +31,18 @@ class StatisticsCollector(Thread, StatisticsClientThread):
             "last_invalid_packet":   numpy.zeros((9000,), dtype=numpy.uint8),
         }
 
-    def run(self):
-        logger.info("Starting statistics thread")
-
-        while True:
-            self.last_packet = self.queue.get()
-
-            # This is the exception/slow path, but python doesn't allow us to optimise that
-            if self.last_packet is None:
-                # None is the magic marker to stop processing
-                break
-
-            self.parameters["nof_packets"] += numpy.uint64(1)
-
-            try:
-                self.process_packet(self.last_packet)
-            except Exception as e:
-                logger.exception("Could not parse statistics UDP packet")
-
-                self.parameters["last_invalid_packet"] = numpy.frombuffer(self.last_packet, dtype=numpy.uint8)
-                self.parameters["nof_invalid_packets"] += numpy.uint64(1)
-
-        logger.info("Stopped statistics thread")
-
-    def join(self, timeout=0):
-        # insert magic marker
-        self.queue.put(None)
-        logger.info("Sent shutdown to statistics thread")
-
-        super().join(timeout)
-
-    def disconnect(self):
-        # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver
-        #              and StatisticsCollector.
-        if not self.is_alive():
-            return
+    def process_packet(self, packet):
+        self.parameters["nof_packets"] += numpy.uint64(1)
 
-        # try to get the thread shutdown, but don't stall forever
-        self.join(self.DISCONNECT_TIMEOUT)
+        try:
+            self.parse_packet(packet)
+        except Exception as e:
+            self.parameters["last_invalid_packet"] = numpy.frombuffer(packet, dtype=numpy.uint8)
+            self.parameters["nof_invalid_packets"] += numpy.uint64(1)
 
-        if self.is_alive():
-            # there is nothing we can do except wait (stall) longer, which could be indefinitely.
-            logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.")
+            raise ValueError("Could not parse statistics packet") from e
 
-    def process_packet(self, packet):
+    def parse_packet(self, packet):
         """ Update any information based on this packet. """
 
         raise NotImplementedError
@@ -121,7 +75,7 @@ class SSTCollector(StatisticsCollector):
 
         return defaults
 
-    def process_packet(self, packet):
+    def parse_packet(self, packet):
         fields = SSTPacket(packet)
 
         # determine which input this packet contains data for
@@ -143,3 +97,66 @@ class SSTCollector(StatisticsCollector):
         self.parameters["sst_values"][input_index][:fields.nof_statistics_per_packet] = fields.payload
         self.parameters["sst_timestamps"][input_index]        = numpy.float64(fields.timestamp().timestamp())
         self.parameters["integration_intervals"][input_index] = fields.integration_interval()
+
+
+class StatisticsConsumer(Thread, StatisticsClientThread):
+    """ Base class to process statistics packets from a queue, asynchronously. """
+
+    # Maximum time to wait for the Thread to get unstuck, if we want to stop
+    DISCONNECT_TIMEOUT = 10.0
+
+    # No default options required, for now?
+    _default_options = {}
+
+    def __init__(self, queue: Queue, collector: StatisticsCollector):
+        self.queue = queue
+        self.collector = collector
+        self.last_packet = None
+
+        super().__init__()
+        self.start()
+
+    @property
+    def _options(self) -> dict:
+        return StatisticsConsumer._default_options
+
+    def run(self):
+        logger.info("Starting statistics thread")
+
+        while True:
+            self.last_packet = self.queue.get()
+
+            # This is the exception/slow path, but python doesn't allow us to optimise that
+            if self.last_packet is None:
+                # None is the magic marker to stop processing
+                break
+
+            try:
+                self.collector.process_packet(self.last_packet)
+            except ValueError as e:
+                logger.exception("Could not parse statistics packet")
+
+                # continue processing
+
+        logger.info("Stopped statistics thread")
+
+    def join(self, timeout=0):
+        # insert magic marker
+        self.queue.put(None)
+        logger.info("Sent shutdown to statistics thread")
+
+        super().join(timeout)
+
+    def disconnect(self):
+        # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver
+        #              and StatisticsConsumer.
+        if not self.is_alive():
+            return
+
+        # try to get the thread shutdown, but don't stall forever
+        self.join(self.DISCONNECT_TIMEOUT)
+
+        if self.is_alive():
+            # there is nothing we can do except wait (stall) longer, which could be indefinitely.
+            logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.")
+
diff --git a/devices/devices/apsctl.py b/devices/devices/unb2.py
similarity index 66%
rename from devices/devices/apsctl.py
rename to devices/devices/unb2.py
index b555cb1b68ac5f76261c73d9723aa050316dc9d8..2df8528a621811ac80ca88a08f954ee09acbb3a9 100644
--- a/devices/devices/apsctl.py
+++ b/devices/devices/unb2.py
@@ -32,10 +32,10 @@ from common.lofar_git import get_version
 
 import numpy
 
-__all__ = ["APSCTL", "main"]
+__all__ = ["UNB2", "main"]
 
 @device_logging_to_python()
-class APSCTL(hardware_device):
+class UNB2(hardware_device):
     """
 
     **Properties:**
@@ -79,63 +79,90 @@ class APSCTL(hardware_device):
     N_ddr = 2
     N_qsfp = 6
 
-    # Central CP per Uniboard
-    UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_TEMP_R"], datatype=numpy.double, dims=((N_unb * N_ddr), N_fpga))
-    UNB2_I2C_bus_QSFP_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_QSFP_STATUS_R"], datatype=numpy.int64, dims=((N_unb * N_fpga), N_qsfp))
-    UNB2_I2C_bus_DDR4_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_DDR4_STATUS_R"], datatype=numpy.int64, dims=(N_ddr, N_fpga))
-    UNB2_I2C_bus_FPGA_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_FPGA_PS_STATUS_R"], datatype=numpy.int64, dims=(N_unb * N_fpga,))
-    UNB2_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_translator_busy_R"], datatype=numpy.bool_)
+    ### All CP/MP are in order of appearance in the ICD
+    ### Central CP per Uniboard
 
+    ### Some points are not working yet on the UNB2 or under discussion
+    #XXX means Not working yet, but they are working on it
+    ##XXX Means Under discussion
+
+    # Special case for the on off switch: instead of UNB2_Power_ON_OFF_R we use UNB2_POL_FPGA_CORE_VOUT_R as the MP
+    UNB2_Power_ON_OFF_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Power_ON_OFF_RW"], datatype=numpy.bool_, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
     UNB2_Front_Panel_LED_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Front_Panel_LED_RW"], datatype=numpy.uint8, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
     UNB2_Front_Panel_LED_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Front_Panel_LED_R"], datatype=numpy.uint8, dims=(N_unb,))
-    UNB2_EEPROM_Serial_Number_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_EEPROM_Serial_Number_R"], datatype=numpy.str, dims=(N_unb,))
+    UNB2_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_mask_RW"], datatype=numpy.bool_, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
+    # Not yet deployed
+    #UNB2_mask_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_mask_R"], datatype=numpy.bool_, dims=(N_unb,))
+
+    ### Central MP per Uniboard
+    # These three are only available in UNB2c
+    UNB2_I2C_bus_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_STATUS_R"], datatype=numpy.bool_, dims=(N_unb,))
+    ##UNB2_I2C_bus_STATUS_R will probably be renamed to UNB2_I2C_bus_OK_R
+    ##UNB2_I2C_bus_OK_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_OK_R"], datatype=numpy.bool_, dims=(N_unb,))
+    #UNB2_EEPROM_Serial_Number_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_EEPROM_Serial_Number_R"], datatype=numpy.str, dims=(N_unb,))
     UNB2_EEPROM_Unique_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_EEPROM_Unique_ID_R"], datatype=numpy.uint32, dims=(N_unb,))
-    UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R"], datatype=numpy.str, dims=(N_unb * N_qsfp, N_fpga))
-    UNB2_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_monitor_rate_RW"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
-    UNB2_I2C_bus_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_STATUS_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_I2C_bus_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_PS_STATUS_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_mask_RW"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
-    UNB2_Power_ON_OFF_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_Power_ON_OFF_R"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
-
-    UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_TEMP_R"], datatype=numpy.double, dims=(N_unb * N_qsfp,N_fpga))
-    UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_LOS_R"], datatype=numpy.uint8, dims=(N_unb * N_qsfp,N_fpga))
-    UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_POL_FPGA_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_TXGXB_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_POL_FPGA_CORE_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_CORE_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_VOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_IOUT_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_TEMP_R"], datatype=numpy.double, dims=(N_unb,N_fpga))
-    UNB2_POL_CLOCK_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_CLOCK_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_CLOCK_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_1V2_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_1V2_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_1V2_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_PHY_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_PHY_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_POL_SWITCH_PHY_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_DC_DC_48V_12V_VIN_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VIN_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_DC_DC_48V_12V_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_DC_DC_48V_12V_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_DC_DC_48V_12V_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N01_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N01_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N01_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N01_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N23_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N23_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
     UNB2_POL_QSFP_N23_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_QSFP_N23_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_DC_DC_48V_12V_VIN_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VIN_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_DC_DC_48V_12V_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_DC_DC_48V_12V_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
-    UNB2_DC_DC_48V_12V_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_DC_DC_48V_12V_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_1V2_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_1V2_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_1V2_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_1V2_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_PHY_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_PHY_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_SWITCH_PHY_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_SWITCH_PHY_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_CLOCK_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_VOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_CLOCK_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_IOUT_R"], datatype=numpy.double, dims=(N_unb,))
+    UNB2_POL_CLOCK_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_CLOCK_TEMP_R"], datatype=numpy.double, dims=(N_unb,))
+
+    ### Local MP per FPGA
+    UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_TEMP_R"], datatype=numpy.double, dims=((N_fpga * N_ddr), N_unb))
+    #UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_DDR4_SLOT_PART_NUMBER_R"], datatype=numpy.str, dims=(N_fpga * N_ddr), N_unb))
+    #UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_0_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_1_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_1_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_2_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_2_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_3_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_3_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_4_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_4_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_5_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_5_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_0_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_1_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_1_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_2_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_2_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_3_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_3_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_4_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_4_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_QSFP_CAGE_5_LOS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_QSFP_CAGE_5_LOS_R"], datatype=numpy.uint8, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_POL_CORE_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_CORE_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_CORE_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_ERAM_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_RXGXB_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_TXGXB_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    #UNB2_FPGA_POL_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_POL_FPGA_TXGXB_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_HGXB_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_VOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_IOUT_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+    UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_FPGA_POL_PGM_TEMP_R"], datatype=numpy.double, dims=(N_fpga, N_unb))
+
+
+  ##UNB2_I2C_bus_QSFP_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_QSFP_STATUS_R"], datatype=numpy.int64, dims=((N_unb * N_fpga), N_qsfp))
+  ##UNB2_I2C_bus_DDR4_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_DDR4_STATUS_R"], datatype=numpy.int64, dims=(N_ddr, N_fpga))
+  ##UNB2_I2C_bus_FPGA_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_FPGA_PS_STATUS_R"], datatype=numpy.int64, dims=(N_unb * N_fpga,))
+  ##UNB2_I2C_bus_PS_STATUS_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_I2C_bus_PS_STATUS_R"], datatype=numpy.double, dims=(N_unb,))
+  ##UNB2_translator_busy_R = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_translator_busy_R"], datatype=numpy.bool_)
+  ##UNB2_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:UNB2_monitor_rate_RW"], datatype=numpy.double, dims=(N_unb,), access=AttrWriteType.READ_WRITE)
+
 
 
     # QualifiedName(2: UNB2_on)
@@ -180,7 +207,7 @@ class APSCTL(hardware_device):
             except Exception as e:
                 # use the pass function instead of setting read/write fails
                 i.set_pass_func()
-                self.warn_stream("error while setting the APSCTL attribute {} read/write function. {}".format(i, e))
+                self.warn_stream("error while setting the UNB2 attribute {} read/write function. {}".format(i, e))
 
         self.OPCua_client.start()
 
@@ -192,12 +219,12 @@ class APSCTL(hardware_device):
 # Run server
 # ----------
 def main(args=None, **kwargs):
-    """Main function of the SDP module."""
+    """Main function of the UNB2 module."""
 
-    from devices.common.lofar_logging import configure_logger
+    from common.lofar_logging import configure_logger
     configure_logger()
 
-    return run((APSCTL,), args=args, **kwargs)
+    return run((UNB2,), args=args, **kwargs)
 
 
 if __name__ == '__main__':
diff --git a/devices/integration_test/README.md b/devices/integration_test/README.md
index 3292bfa0049b5c2312f8e0536e00cc581433ed61..d4f91ace15cca03e924b9139b25bdb5294944576 100644
--- a/devices/integration_test/README.md
+++ b/devices/integration_test/README.md
@@ -23,4 +23,4 @@ $LOFAR20_DIR/sbin/run_integration_test.sh
 ## Limitations
 
 Our makefile will always launch the new container upon creation, resulting in
-the integration tests actually being run twice.
\ No newline at end of file
+the integration tests actually being run twice.
diff --git a/devices/setup.cfg b/devices/setup.cfg
index 586aa190649d3c54b04ce586cdbaa4565570b1b1..860016bbeeb6aa5b685c684bf23d232b98fb1436 100644
--- a/devices/setup.cfg
+++ b/devices/setup.cfg
@@ -1,11 +1,11 @@
 [metadata]
 name = TangoStationControl
 summary = LOFAR 2.0 Station Control
-description-file =
+description_file =
     README.md
-description-content-type = text/x-rst; charset=UTF-8
+description_content_type = text/x-rst; charset=UTF-8
 author = ASTRON
-home-page = https://astron.nl
+home_page = https://astron.nl
 project_urls =
     Bug Tracker = https://support.astron.nl/jira/projects/L2SS/issues/
     Source Code = https://git.astron.nl/lofar2.0/tango
diff --git a/devices/statistics_writer/README.md b/devices/statistics_writer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ea722c6cf552443364b196264034d768690955be
--- /dev/null
+++ b/devices/statistics_writer/README.md
@@ -0,0 +1,58 @@
+# TCP to HDF5 statistics writer
+The TCP to HDF5 statistics writer can be started with `tcp_hdf5_writer.py` This script imports
+`tcp_receiver.py` and `statistics_writer.py`. `tcp_receiver.py` only takes care of receiving packets. 
+`statistics_writer.py` takes the receive function from the tcp_receiver and uses it to obtain packets. 
+Any function that can deliver statistics packets can be used by this code. 
+`statistics_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.
+
+
+### TCP Statistics writer
+
+The TCP statistics writer can be called with the `tcp_hdf5_writer.py` script.
+This script can be called with the following arguments:
+  ```
+  --address     the address to connect to
+  --port        the port to use
+  --interval    The time between creating new files in hours
+  --location    specifies the folder to write all the files
+  --mode        sets the statistics type to be decoded options: "SST", "XST", "BST"
+  --debug       takes no arguments, when used prints a lot of extra data to help with debugging
+  ```
+
+
+##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
+|
+|------ {mode_timestamp}  |- {statistics matrix}
+|                         |- {first packet header}
+|                         |- {nof_valid_payloads}
+|                         |- {nof_payload_errors}
+|
+|------ {mode_timestamp}  |- {statistics matrix}
+|                         |- {first packet header}
+|                         |- {nof_valid_payloads}
+|                         |- {nof_payload_errors}
+|
+...
+```
+
+###explorer
+There is an hdf5 explorer that will walk through specified hdf5 files. 
+Its called `hdf5_explorer.py` and can be called with a `--file` argument
+ex: `python3 hdf5_explorer.py --file data/SST_1970-01-01-00-00-00.h5` This allows for easy manual checking 
+of the structure and content of hdf5 files. useful for testing and debugging.
+Can also be used as example of how to read the HDF5 statistics data files.
+Provides a number of example functions inside that go through the file in various ways.
+
+###test server
+There is a test server that will continuously send out the same statistics packet.
+Its called `test_server.py`. Takes `--host`, `--port` and `--file` as optional input arguments.
+Defaults to address `'127.0.0.1'`, port `65433` and file `devices_test_SDP_SST_statistics_packets.bin`
+
diff --git a/devices/statistics_writer/data/test.h5 b/devices/statistics_writer/data/test.h5
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/statistics_writer/data/testing.h5 b/devices/statistics_writer/data/testing.h5
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/devices/statistics_writer/statistics_writer.py b/devices/statistics_writer/statistics_writer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a013cb779b758b8e5256aba23e1ac0ff9a24e9d7
--- /dev/null
+++ b/devices/statistics_writer/statistics_writer.py
@@ -0,0 +1,187 @@
+# imports for working with datetime objects
+from datetime import datetime, timedelta
+import pytz
+
+# python hdf5
+import h5py
+
+import numpy
+import json
+import logging
+
+# import statistics classes with workaround
+import sys
+sys.path.append("..")
+from devices.sdp.statistics_packet import SSTPacket, XSTPacket, BSTPacket, StatisticsPacket
+import devices.sdp.statistics_collector as statistics_collector
+
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger("statistics_writer")
+
+__all__ = ["statistics_writer"]
+
+class statistics_writer:
+
+
+    def __init__(self, new_file_time_interval, file_location, statistics_mode):
+
+        # all variables that deal with the SST matrix that's currently being decoded
+        self.current_matrix = None
+        self.current_timestamp = datetime.min.replace(tzinfo=pytz.UTC)
+
+        # 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)
+        self.statistics_header = None
+
+        # file handing
+        self.file_location = file_location
+        self.new_file_time_interval = timedelta(hours=new_file_time_interval)
+        self.last_file_time = datetime.min.replace(tzinfo=pytz.UTC)
+        self.file = None
+
+        # config the writer for the correct statistics type
+        self.collector = None
+        self.decoder = None
+        self.mode = statistics_mode.upper()
+        self.config_mode()
+
+    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.
+        """
+
+        # process the packet
+        statistics_packet = self.decoder(packet)
+
+        # grab the timestamp
+        statistics_timestamp = statistics_packet.timestamp()
+
+        # 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.")
+            return
+
+        # if this statistics packet has a new timestamp it means we need to start a new matrix
+        if statistics_timestamp > self.current_timestamp:
+            self.statistics_header = statistics_packet.header()
+            self.start_new_matrix(statistics_timestamp)
+            self.current_timestamp = statistics_timestamp
+
+        self.process_packet(packet)
+
+    def start_new_matrix(self, timestamp):
+        logger.debug(f"starting new matrix with timestamp: {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
+        """
+
+        # 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 = str(self.current_timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
+                logger.error(f"Exception while attempting to write matrix to HDF5. Matrix: {time} dropped: {e}")
+
+        # 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)
+
+        # create a new and empty current_matrix
+        self.current_matrix = self.collector()
+
+    def write_matrix(self):
+        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, str(self.current_timestamp.strftime("%Y-%m-%d-%H-%M-%S"))))
+
+        # store the statistics values
+        current_group.create_dataset(name=f"{self.mode}_values", data=self.current_matrix.parameters["sst_values"])
+
+        # 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"])
+
+        # get the statistics header
+        header = self.statistics_header
+        # can't store datetime objects in json, converted to string instead
+        header["timestamp"] = header["timestamp"].isoformat(timespec="milliseconds")
+        # convert the header to JSON
+        json_header = json.dumps(header)
+        # Stores the header of the packet received for this matrix
+        current_group.create_dataset(name='first_packet_header', data=numpy.str(json_header))
+
+
+    def process_packet(self, packet):
+        logger.debug(f"Processing packet")
+        """
+        Adds the newly received statistics packet to the statistics matrix
+        """
+        self.current_matrix.process_packet(packet)
+
+    def start_new_hdf5(self, timestamp):
+
+        if self.file is not None:
+            try:
+                self.file.close()
+            except Exception as e:
+                logger.error(f"Error while attempting to close hdf5 file to disk. file {self.file} likely empty, please verify integrity. \r\n Exception: {e}")
+
+        current_time = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
+        logger.debug(f"creating new file: {self.file_location}/{self.mode}_{current_time}.h5")
+
+        try:
+            self.file = h5py.File(f"{self.file_location}/{self.mode}_{current_time}.h5", 'w')
+        except Exception as e:
+            logger.error(f"Error while creating new file: {e}")
+            raise e
+
+        self.last_file_time = timestamp
+
+    def config_mode(self):
+        logger.debug(f"attempting to configure {self.mode} mode")
+
+        """
+        Configures the object for the correct statistics type to be used.
+        """
+
+        if self.mode == 'SST':
+            self.decoder = SSTPacket
+            self.collector = statistics_collector.SSTCollector
+        elif self.mode == 'BST':
+            # self.decoder = XSTPacket
+            raise NotImplementedError("BST collector has not yet been implemented")
+        elif self.mode == 'XST':
+            # self.decoder = XSTPacket
+            raise NotImplementedError("BST collector has not yet been implemented")
+        else:
+            # make sure the mode is valid
+            raise ValueError("invalid statistics mode specified '{}', please use 'SST', 'XST' or 'BST' ".format(self.mode))
+
+    def close_writer(self):
+        """
+        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:
+                # Write matrix if one exists
+                # only creates file if there is a matrix to actually write
+                try:
+                    self.write_matrix()
+                finally:
+                    self.file.close()
+                    logger.debug(f"{self.file} closed")
diff --git a/devices/statistics_writer/tcp_hdf5_writer.py b/devices/statistics_writer/tcp_hdf5_writer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d290e293e2db82fa3e12e071e7dda4d6077ea40
--- /dev/null
+++ b/devices/statistics_writer/tcp_hdf5_writer.py
@@ -0,0 +1,59 @@
+import argparse
+from tcp_receiver import tcp_receiver
+from statistics_writer import statistics_writer
+
+import sys
+import signal
+
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger("statistics_writer")
+
+parser = argparse.ArgumentParser(description='Arguments to configure the TCP connection and writer mode')
+parser.add_argument('--address', type=str, help='the address to connect to')
+parser.add_argument('--port', type=int, help='the port to use')
+
+parser.add_argument('--interval', type=float, default=1, nargs="?", help='The time between creating new files in hours')
+parser.add_argument('--location', type=str, default="data", nargs="?", help='specifies the folder to write all the files')
+parser.add_argument('--mode', type=str, choices=['SST', 'XST', 'BST'], help='sets the statistics type to be decoded options: "SST", "XST", "BST"')
+parser.add_argument('--debug', dest='debug', action='store_true', default=False, help='when used stores failed packets')
+
+
+# create a data dumper that creates a new file every 10s (for testing)
+if __name__ == "__main__":
+    def signal_handler(signum, frame):
+        writer.close_writer()
+        sys.exit(0)
+
+
+    signal.signal(signal.SIGINT, signal_handler)
+
+    args = parser.parse_args()
+
+    # argparse arguments
+    address = args.address
+    port = args.port
+    location = args.location
+    interval = args.interval
+    mode = args.mode
+    debug = args.debug
+
+    if debug:
+        logger.setLevel(logging.DEBUG)
+        logger.debug("Setting loglevel to DEBUG")
+
+    # creates the TCP receiver that is given to the writer
+    receiver = tcp_receiver(address, port)
+
+    # create the writer
+    writer = statistics_writer(new_file_time_interval=interval, file_location=location, statistics_mode=mode)
+
+    # start looping
+    try:
+        while True:
+            packet = receiver.get_packet()
+            writer.next_packet(packet)
+    except KeyboardInterrupt:
+        writer.close_writer()
+
+
diff --git a/devices/statistics_writer/tcp_receiver.py b/devices/statistics_writer/tcp_receiver.py
new file mode 100644
index 0000000000000000000000000000000000000000..4112f926785d5fbb5b25672113c55445f8a8952b
--- /dev/null
+++ b/devices/statistics_writer/tcp_receiver.py
@@ -0,0 +1,46 @@
+import socket
+
+import sys
+sys.path.append("..")
+from devices.sdp.statistics_packet import StatisticsPacket
+
+class tcp_receiver:
+    HEADER_LENGTH = 32
+
+    def __init__(self, HOST, PORT):
+        self.host = HOST
+        self.port = PORT
+
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((self.host, self.port))
+
+    def get_packet(self) -> bytes:
+        """ Read exactly one statistics packet from the TCP connection. """
+
+        # read only the header, to compute the size of the packet
+        header = self.read_data(self.HEADER_LENGTH)
+        packet = StatisticsPacket(header)
+
+        # read the rest of the packet (payload)
+        payload_length = packet.expected_size() - len(header)
+        payload = self.read_data(payload_length)
+
+        # add payload to the header, and return the full packet
+        return header + payload
+
+    def read_data(self, data_length: int) -> bytes:
+        """ Read exactly data_length bytes from the TCP connection. """
+
+        data = b''
+        while len(data) < data_length:
+            # try to read the remainder.
+            # NOTE: recv() may return less data than requested, and returns 0
+            # if there is nothing left to read (end of stream)
+            more_data = self.sock.recv(data_length - len(data))
+            if not more_data:
+                # connection got dropped
+                raise IOError("Connection closed by peer")
+
+            data += more_data
+
+        return data
diff --git a/devices/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin b/devices/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin
new file mode 100644
index 0000000000000000000000000000000000000000..e94347b86a0a03b940eb84980ec8f6d3b6d4e2d7
Binary files /dev/null and b/devices/statistics_writer/test/devices_test_SDP_SST_statistics_packets.bin differ
diff --git a/devices/statistics_writer/test/hdf5_explorer.py b/devices/statistics_writer/test/hdf5_explorer.py
new file mode 100644
index 0000000000000000000000000000000000000000..29cc88049086f5bea22c441d1ca12f91769c7135
--- /dev/null
+++ b/devices/statistics_writer/test/hdf5_explorer.py
@@ -0,0 +1,132 @@
+import h5py
+import numpy
+
+import argparse
+
+parser = argparse.ArgumentParser(description='Select a file to explore')
+parser.add_argument('--file', type=str, help='the name and path of the file')
+
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger("hdf5_explorer")
+logger.setLevel(logging.DEBUG)
+
+
+class statistics_data:
+    """
+    Example class not used by anything
+    This class takes the file and the statistics name as its __init__ arguments and then stores the
+    the datasets in them.
+    """
+
+
+    NOF_PAYLOAD_ERRORS = "nof_payload_errors"
+    NOF_VALID_PAYLOADS = "nof_valid_payloads"
+    FIRST_PACKET_HEADER = "first_packet_header"
+    STATISTICS_VALUES = "statistics_values"
+
+    def __init__(self, file, statistics_name):
+        self.nof_valid_payloads = file.get(f"{statistics_name}/{statistics_data.NOF_VALID_PAYLOADS}")
+        self.nof_payload_errors = file.get(f"{statistics_name}/{statistics_data.NOF_PAYLOAD_ERRORS}")
+        self.first_packet_header = file.get(f"{statistics_name}/{statistics_data.FIRST_PACKET_HEADER}")
+        self.statistics_values = file.get(f"{statistics_name}/{statistics_data.STATISTICS_VALUES}")
+
+
+class explorer:
+    """
+    This class serves both as a tool to test and verify the content of HDF5 files as well as provide an example
+    of how you can go through HDF5 files.
+
+
+    The first 2 functions, print_high_level and print_full both call the hdf5 file.visititems function. this function
+    takes another function as argument and then calls that function for each and every group and dataset in the file.
+
+    The last 2 functions do this without this file.visititems function and instead have knowledge of how we structure the
+    statistics data.
+    """
+
+
+    def __init__(self, filename):
+        self.file = h5py.File(filename, 'r')
+
+    def print_high_level(self):
+        """Calls a function that will go through all groups and datasets in the file and pass data along to another specified function"""
+        self.file.visititems(self._high_level_explorer)
+
+    def print_full(self):
+        """Calls a function that will go through all groups and datasets in the file and pass data along to another specified function"""
+        self.file.visititems(self._full_explorer)
+
+    def _full_explorer(self, name, obj):
+        """
+        Called by the file.visititems(func) function. Gets called for each and every group and dataset.
+        Prints all groups and datasets including their content.
+        """
+
+        shift = name.count('/') * '    '
+        data = self.file.get(name)
+        logger.debug(f"{shift}{name}: {data}")
+        logger.debug(numpy.array(data))
+
+    def _high_level_explorer(self, name, obj):
+        """
+        Called by the file.visititems(func) function. Gets called for each and every group and dataset.
+        Only lists the groups and datasets without the actual content.
+        """
+        shift = name.count('/') * '    '
+        data = self.file.get(name)
+        logger.debug(f"{shift}{name}: {data}")
+
+    def print_all_statistics_full(self):
+        """
+        Explores the file with knowledge of the file structure. assumes all top level groups are statistics
+        and that all statistics groups are made up of datasets.
+        Prints the groups, the datasets and the content of the datasets.
+        """
+
+        # List all groups
+        logger.debug("Keys: %s" % self.file.keys())
+
+        for group_key in self.file.keys():
+            dataset = list(self.file[group_key])
+            for i in dataset:
+                data = self.file.get(f"{group_key}/{i}")
+                logger.debug(group_key)
+                logger.debug(numpy.array(data))
+
+    def print_all_statistics_top_level(self):
+        """
+        Explores the file with knowledge of the file structure. assumes all top level groups are statistics
+        and that all statistics groups are made up of datasets.
+        This function prints only the top level groups, AKA all the statistics collected. Useful when dealing with
+        potentially hundreds of statistics.
+        """
+        # List all groups
+        logger.debug("Listing all statistics stored in this file:")
+
+        for group_key in self.file.keys():
+            logger.debug(group_key)
+
+
+
+# create a data dumper that creates a new file every 10s (for testing)
+if __name__ == "__main__":
+    args = parser.parse_args()
+    Explorer = explorer(args.file)
+
+    """
+    Print the entire files content
+    """
+    Explorer.print_all_statistics_full()
+
+    """
+    Print only the names of all the statistics in this file
+    """
+    Explorer.print_all_statistics_top_level()
+
+
+
+
+
+
+
diff --git a/devices/statistics_writer/test/test_server.py b/devices/statistics_writer/test/test_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..eec9ec3eed992b03ee809ca37de012bad43bd213
--- /dev/null
+++ b/devices/statistics_writer/test/test_server.py
@@ -0,0 +1,52 @@
+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 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/devices/statistics_writer/udp_dev/udp_client.py b/devices/statistics_writer/udp_dev/udp_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..cef6a079d17dc0fb45d71f181ee2be908e9bd091
--- /dev/null
+++ b/devices/statistics_writer/udp_dev/udp_client.py
@@ -0,0 +1,62 @@
+import socket
+import sys
+import netifaces as ni
+from datetime import datetime
+import time
+
+class UDP_Client:
+
+    def __init__(self, server_ip:str, server_port:int):
+        self.server_ip = server_ip
+        self.server_port = server_port
+        self.server_data = None
+        self.server_addr = None # tuple of address info
+
+    def run(self):
+        # Create socket for server
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+        print("Do Ctrl+c to exit the program !!")
+        print('\n\n*** This Client keeps sending the same SST packet with an interval of 1s ***')
+
+        # Let's send data through UDP protocol
+        while True:
+
+            #Old interactive interface
+            #send_data = input("Type some text to send =>");
+            #s.sendto(send_data.encode('utf-8'), (self.server_ip, self.server_port))
+            #print("\n\n 1. Client Sent : ", send_data, "\n\n")
+            #self.server_data, self.server_addr = s.recvfrom(4096)
+            #print("\n\n 2. Client received : ", self.server_data.decode('utf-8'), "\n\n")
+
+            time.sleep(1)
+
+            f = open("../../test/SDP_SST_statistics_packet.bin", "rb")
+            send_data = f.read()
+            s.sendto(send_data, (self.server_ip, self.server_port))
+            print("\n\n 1. Client Sent SST Packet at: ", datetime.now())
+            self.server_data, self.server_addr = s.recvfrom(4096)
+            print("\n\n 2. Client received : ", self.server_data.decode('utf-8'), "\n\n")
+
+        # close the socket
+        s.close()
+
+if __name__ == '__main__':
+
+    if len(sys.argv) == 3:
+        if sys.argv[1]=='localhost':
+            server_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
+        else :
+            server_ip = sys.argv[1]
+        server_port = int(sys.argv[2])
+        #local_ip = local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
+        #server_ip = local_ip
+    else:
+        print("Run like : python3 udp_client.py <server_ip> <server_port>")
+        exit(1)
+
+    client = UDP_Client(server_ip,server_port)
+    client.run()
+
+    
+        
+        
\ No newline at end of file
diff --git a/devices/statistics_writer/udp_dev/udp_server.py b/devices/statistics_writer/udp_dev/udp_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..45624761519287b13bbce5c73cf8d8cb7dff9201
--- /dev/null
+++ b/devices/statistics_writer/udp_dev/udp_server.py
@@ -0,0 +1,50 @@
+import socket
+import sys
+import time
+import netifaces as ni
+from datetime import datetime
+
+class UDP_Server:
+
+    def __init__(self, ip:str, port:int, buffer_size:int = 8192):
+        self.ip = ip
+        self.port = port
+        self.buffer_size = buffer_size
+        self.recv_data = None
+        self.recv_addr = None
+
+    def run(self):
+        # Create a UDP socket
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        # Bind the socket to the port
+        server_address = (self.ip, self.port)
+        s.bind(server_address)
+        print("Do Ctrl+c to exit the program !!")
+        print("\n\n####### Server is listening on %s - port %s #######" % (self.ip,self.port))
+
+        while True:
+            
+            self.recv_data, self.recv_addr = s.recvfrom(self.buffer_size)
+            print("\n\n 2. Server received at: ", datetime.now(), "\n\n")
+
+            '''Server response'''
+            #send_data = input("Type some text to send => ")
+            send_data = 'Packet received. Waiting for the next one.'
+            s.sendto(send_data.encode('utf-8'), self.recv_addr)
+            print("\n\n 1. Server sent : ", send_data,"\n\n")
+
+            #time.sleep(10)
+            #s.close()
+            
+            break
+
+        # close the socket
+        s.close()
+    
+    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)
+    server.run()
diff --git a/devices/statistics_writer/udp_dev/udp_write_manager.py b/devices/statistics_writer/udp_dev/udp_write_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c11f6a82dc11f8151eb771b90033feb38ef9c42
--- /dev/null
+++ b/devices/statistics_writer/udp_dev/udp_write_manager.py
@@ -0,0 +1,81 @@
+from datetime import datetime
+import time
+import os
+import h5py
+import numpy as np
+from statistics_writer.udp_dev import udp_server as udp
+import netifaces as ni
+from statistics_packet import SSTPacket
+
+__all__ = ["statistics_writer"]
+
+
+class Statistics_Writer:
+
+    def __init__(self, new_file_time_interval):
+
+        self.new_file_time_interval = new_file_time_interval
+        self.packet_cnt = 0
+
+        # Define ip and port of the receiver
+        self.local_ip = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
+        self.server = udp.UDP_Server(self.local_ip, 5600)
+
+        # Create data directory if not exists
+        try:
+            os.makedirs('../data')
+        except:
+            print('Data directory already created')
+
+        # create initial file
+        self.last_file_time = time.time()
+        self.file = None
+        self.new_hdf5()
+
+    def write_packet(self, raw_data):
+        # create new file if the file was created more than the allowed time ago
+        if time.time() >= self.new_file_time_interval + self.last_file_time:
+            self.new_hdf5()
+
+        self.packet_cnt += 1
+
+        # create dataset with the raw data in it
+        self.write_raw(raw_data)
+        self.write_metadata(raw_data)
+
+    def new_hdf5(self):
+
+        if self.file is not None:
+            self.file.close()
+
+        timestamp = datetime.now()
+        current_time = str(timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
+        print("creating new file: data/{}.h5".format(current_time))
+        self.file = h5py.File("data/{}.h5".format(current_time), 'w')
+        self.last_file_time = time.time()
+
+    def write_metadata(self, packet):
+        # decode packet
+        self.sst = SSTPacket(packet)
+        header = self.sst.header()
+        header_bytes = bytes(str(header), "utf-8")
+        header_bytes = np.frombuffer(header_bytes, dtype=np.uint8)
+        self.file.create_dataset('packet_{}_header'.format(self.packet_cnt), data=header_bytes)
+
+    def write_raw(self, packet):
+        # create dataset with the raw data in it
+        data = np.frombuffer(packet, dtype=np.uint8)
+        self.file.create_dataset('packet_{}_raw'.format(self.packet_cnt), data=data)
+
+
+if __name__ == "__main__":
+    # create a data dumper that creates a new file every 10s (for testing)
+    test = Statistics_Writer(new_file_time_interval=10)
+
+    # simple loop to write data every second
+    while True:
+        test.server.run()
+        data = test.server.get_recv_data()
+        test.write_packet(data)
+
+    # time.sleep(1)
diff --git a/devices/test-requirements.txt b/devices/test-requirements.txt
index c7418b98897136aebb89a3fc13559289306bb35a..20ed449cd8f17f9110ebe1b70774916abe8c00cb 100644
--- a/devices/test-requirements.txt
+++ b/devices/test-requirements.txt
@@ -6,6 +6,9 @@ bandit>=1.6.0 # Apache-2.0
 coverage>=5.2.0 # Apache-2.0
 doc8>=0.8.0 # Apache-2.0
 flake8>=3.8.0 # MIT
+flake8-breakpoint>=1.1.0 # MIT
+flake8-debugger>=4.0.0 #MIT
+flake8-mock>=0.3 #GPL
 hacking>=3.2.0,<3.3.0 # Apache-2.0
 python-subunit>=1.4.0 # Apache-2.0/BSD
 Pygments>=2.6.0
diff --git a/devices/test/SDP_SST_statistics_packet.bin b/devices/test/SDP_SST_statistics_packet.bin
index ade2d62c32eb6cbf4fb9b5ec2d7c0368ab0af408..a45b77587a8104cbeb756d85cbb757f02abf39bf 100644
Binary files a/devices/test/SDP_SST_statistics_packet.bin and b/devices/test/SDP_SST_statistics_packet.bin differ
diff --git a/devices/test/SDP_SST_statistics_packets.bin b/devices/test/SDP_SST_statistics_packets.bin
new file mode 100644
index 0000000000000000000000000000000000000000..e94347b86a0a03b940eb84980ec8f6d3b6d4e2d7
Binary files /dev/null and b/devices/test/SDP_SST_statistics_packets.bin differ
diff --git a/devices/test/SDP_XST_statistics_packets.bin b/devices/test/SDP_XST_statistics_packets.bin
new file mode 100644
index 0000000000000000000000000000000000000000..97c08e3bfb47bf56c30288b5e62cc60c7034b417
Binary files /dev/null and b/devices/test/SDP_XST_statistics_packets.bin differ
diff --git a/devices/test/clients/test_client.py b/devices/test/clients/test_client.py
index 1d8c85f5e597a31d00bc1af105e0465b9c8a8a11..2c5a2df9c42431f28e6e8a8c3180b8902c4a4597 100644
--- a/devices/test/clients/test_client.py
+++ b/devices/test/clients/test_client.py
@@ -84,6 +84,7 @@ class test_client(CommClient):
 
         def write_function(write_value):
             self.streams.debug_stream("from write_function, writing {} array of type {}".format(dims, dtype))
+
             self.value = write_value
             return
 
diff --git a/devices/test/clients/test_opcua_client.py b/devices/test/clients/test_opcua_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..13b7863819fbcc9d60fc3ae95ad5a269546e200e
--- /dev/null
+++ b/devices/test/clients/test_opcua_client.py
@@ -0,0 +1,246 @@
+import numpy
+from clients.opcua_client import OPCUAConnection
+from clients import opcua_client
+
+import opcua
+import io
+
+from unittest import mock
+import unittest
+
+from test import base
+
+
+class attr_props:
+    def __init__(self, numpy_type):
+        self.numpy_type = numpy_type
+
+
+attr_test_types = [
+    attr_props(numpy_type=str),
+    attr_props(numpy_type=numpy.bool_),
+    attr_props(numpy_type=numpy.float32),
+    attr_props(numpy_type=numpy.float64),
+    attr_props(numpy_type=numpy.double),
+    attr_props(numpy_type=numpy.uint8),
+    attr_props(numpy_type=numpy.uint16),
+    attr_props(numpy_type=numpy.uint32),
+    attr_props(numpy_type=numpy.uint64),
+    attr_props(numpy_type=numpy.int16),
+    attr_props(numpy_type=numpy.int32),
+    attr_props(numpy_type=numpy.int64)
+]
+
+scalar_shape = (1,)
+spectrum_shape = (4,)
+image_shape = (2, 3)
+dimension_tests = [scalar_shape, spectrum_shape, image_shape]
+
+
+class TestOPCua(base.TestCase):
+    @mock.patch.object(OPCUAConnection, "check_nodes")
+    @mock.patch.object(OPCUAConnection, "connect")
+    @mock.patch.object(opcua_client, "Client")
+    def test_opcua_connection(self, m_opc_client, m_connect, m_check):
+        """
+        This tests verifies whether the correct connection steps happen. It checks whether we can init an OPCUAConnection object
+        Whether we can set the namespace, and the OPCua client.
+        """
+
+        m_get_namespace = mock.Mock()
+        m_get_namespace.get_namespace_index.return_value = 42
+        m_opc_client.return_value = m_get_namespace
+
+        test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), mock.Mock())
+
+        """Verify that construction of OPCUAConnection calls self.connect"""
+        m_connect.assert_called_once()  # the connect function in the opcua client
+        m_check.assert_called_once()  # debug function that prints out all nodes
+        m_opc_client.assert_called_once()  # makes sure the actual freeOPCua client object is created only once
+
+        m_get_namespace.get_namespace_index.assert_called_once_with("http://lofar.eu")
+        self.assertEqual(42, test_client.name_space_index)
+
+
+    @mock.patch.object(OPCUAConnection, "check_nodes")
+    @mock.patch.object(OPCUAConnection, "connect")
+    @mock.patch.object(opcua_client, "Client")
+    @mock.patch.object(opcua_client, 'ProtocolAttribute')
+    def test_opcua_attr_setup(self, m_protocol_attr, m_opc_client, m_connect, m_check):
+        """
+        This tests covers the correct creation of read/write functions.
+        In normal circumstances called by he attribute wrapper.
+        Will be given 'comms_annotation', for OPCua that will be a node path and can access the attributes type and dimensions
+
+        Test succeeds if there are no errors.
+        """
+
+        for i in attr_test_types:
+            class mock_attr:
+                def __init__(self, dtype, x, y):
+                    self.numpy_type = dtype
+                    self.dim_x = x
+                    self.dim_y = y
+
+            for j in dimension_tests:
+                if len(j) == 1:
+                    dim_x = j[0]
+                    dim_y = 0
+                else:
+                    dim_x = j[1]
+                    dim_y = j[0]
+
+                # create a fake attribute with only the required variables in it.
+                m_attribute = mock_attr(i.numpy_type, dim_x, dim_y)
+
+                # pretend like there is a running OPCua server with a node that has this name
+                m_annotation = ["2:PCC", f"2:testNode_{str(i.numpy_type)}_{str(dim_x)}_{str(dim_y)}"]
+
+                test = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), mock.Mock())
+                test.setup_attribute(m_annotation, m_attribute)
+
+                # success if there are no errors.
+
+
+
+    def test_protocol_attr(self):
+        """
+        This tests finding an OPCua node and returning a valid object with read/write functions.
+        (This step is normally initiated by the attribute_wrapper)
+        """
+
+        # for all datatypes
+        for i in attr_test_types:
+            # for all dimensions
+            for j in dimension_tests:
+
+                node = mock.Mock()
+
+                # handle scalars slightly differently
+                if len(j) == 1:
+                    dims = (j[0], 0)
+                else:
+                    dims = (j[1], j[0])
+
+                ua_type = opcua_client.numpy_to_OPCua_dict[i.numpy_type]
+                test = opcua_client.ProtocolAttribute(node, dims[0], dims[1], ua_type)
+                print(test.dim_y, test.dim_x, test.ua_type)
+
+                """
+                Part of the test already includes simply not throwing an exception, but for the sake coverage these asserts have also
+                been added.
+                """
+                self.assertTrue(test.dim_y == dims[1], f"Dimensionality error, ProtocolAttribute.dim_y got: {test.dim_y} expected: {dims[1]}")
+                self.assertTrue(test.dim_x == dims[0], f"Dimensionality error, ProtocolAttribute.dim_y got: {test.dim_x} expected: {dims[0]}")
+                self.assertTrue(test.ua_type == ua_type, f"type error. Got: {test.ua_type} expected: {ua_type}")
+                self.assertTrue(hasattr(test, "write_function"), f"No write function found")
+                self.assertTrue(hasattr(test, "read_function"), f"No read function found")
+
+    def test_read(self):
+        """
+        This tests the read functions.
+        """
+
+        for j in dimension_tests:
+            for i in attr_test_types:
+                def get_test_value():
+                    return numpy.zeros(j, i.numpy_type)
+
+                def get_flat_value():
+                    return get_test_value().flatten()
+
+                m_node = mock.Mock()
+
+                if len(j) == 1:
+                    test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type])
+                else:
+                    test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type])
+                m_node.get_value = get_flat_value
+                val = test.read_function()
+
+                comp = val == get_test_value()
+                self.assertTrue(comp.all(), "Read value unequal to expected value: \n\t{} \n\t{}".format(val, get_test_value()))
+
+    def test_type_map(self):
+        for numpy_type, opcua_type in opcua_client.numpy_to_OPCua_dict.items():
+            # derive a default value that can get lost in a type translation
+            if numpy_type in [str, numpy.str, numpy.str_]:
+              default_value = "foo"
+            elif numpy_type == numpy.bool_:
+              default_value = True
+            else:
+              # integer or float type
+              # integers: numpy will drop the decimals for us
+              # floats: make sure we chose a value that has an exact binary representation
+              default_value = 42.25
+
+            # apply our mapping
+            v = opcua.ua.uatypes.Variant(value=numpy_type(default_value), varianttype=opcua_type)
+
+            try:
+                # try to convert it to binary to force opcua to parse the value as the type
+                binary = opcua.ua.ua_binary.variant_to_binary(v)
+
+                # reinterpret the resulting binary to obtain what opcua made of our value
+                binary_stream = io.BytesIO(binary)
+                reparsed_v = opcua.ua.ua_binary.variant_from_binary(binary_stream)
+            except Exception as e:
+                raise Exception(f"Conversion {numpy_type} -> {opcua_type} failed.") from e
+
+            # did the value get lost in translation?
+            self.assertEqual(v.Value, reparsed_v.Value, msg=f"Conversion {numpy_type} -> {opcua_type} failed.")
+
+            # does the OPC-UA type have the same datasize (and thus, precision?)
+            if numpy_type not in [str, numpy.str, numpy.str_]:
+                self.assertEqual(numpy_type().itemsize, getattr(opcua.ua.ua_binary.Primitives, opcua_type.name).size, msg=f"Conversion {numpy_type} -> {opcua_type} failed: precision mismatch")
+
+
+
+    def test_write(self):
+        """
+        Test the writing of values by instantiating a ProtocolAttribute attribute, and calling the write function.
+        but the opcua function that writes to the server has been changed to the compare_values function.
+        This allows the code to compare what values we want to write and what values would be given to a server.
+        """
+
+        # for all dimensionalities
+        for j in dimension_tests:
+
+            #for all datatypes
+            for i in attr_test_types:
+
+                # get numpy array of the test value
+                def get_test_value():
+                    return numpy.zeros(j, i.numpy_type)
+
+                # get opcua Varianttype array of the test value
+                def get_mock_value(value):
+                    return opcua.ua.uatypes.Variant(value=value, varianttype=opcua_client.numpy_to_OPCua_dict[i.numpy_type])
+
+                m_node = mock.Mock()
+
+                # create the protocolattribute
+                if len(j) == 1:
+                    test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type])
+                else:
+                    test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type])
+
+                test.node.get_data_value = mock.Mock()
+
+                # comparison function that replaces `set_data_value` inside the attributes write function
+                def compare_values(val):
+                    # test values
+                    val = val.tolist() if type(val) == numpy.ndarray else val
+                    if j != dimension_tests[0]:
+                        comp = val._value == get_mock_value(get_test_value().flatten())._value
+                        self.assertTrue(comp.all(),
+                                        "Array attempting to write unequal to expected array: \n\t got: {} \n\texpected: {}".format(val,get_mock_value(get_test_value())))
+                    else:
+                        comp = val == get_mock_value(get_test_value())
+                        self.assertTrue(comp, "value attempting to write unequal to expected value: \n\tgot: {} \n\texpected: {}".format(val, get_mock_value(get_test_value())))
+
+                # replace the `set_data_value`, usualy responsible for communicating with the server with the `compare_values` function.
+                m_node.set_data_value = compare_values
+
+                # call the write function with the test values
+                test.write_function(get_test_value())
diff --git a/devices/toolkit/README.md b/devices/toolkit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e3fd6c9af3c0c73ed20dc1558588adf12dd07918
--- /dev/null
+++ b/devices/toolkit/README.md
@@ -0,0 +1,42 @@
+# Tango Archiving Framework
+
+The Archiver class in archiver.py defines the methods to manage the device attributes archiving allowed by Tango.
+
+The main components (and the relative Docker containers) are:
+
+- Configuration Manager (container: hdbpp-cm): Device server that assists in adding, modifying, moving, deleting an Attribute to/from the archiving system
+- Event Subscriber (container: hdbpp-es): The EventSubscriber TANGO device server, is the archiving system engine. On typical usage, it will subscribe to archive events on request by the ConfigurationManager device. The EventSubscriber is designed to start archiving all the already configured Attributes, even if the ConfigurationManager is not running. Moreover, being a TANGO device, the EventSubscriber configuration can be managed with Jive.
+- Archiving DBMS (container: archiver-maria-db): Specific Database devoted to storing attribute values.
+- (Optional) HDB++ Viewer (container: hdbpp-viewer): Standalone JAVA application designed to monitor signals coming from database
+
+## Archiver creation
+When an Archiver object is created, we can define three of its properties:
+- the ConfigurationManager name (Tango namespace)
+- at least one EventSubscriber name (Tango namespace)
+- the default context archiving for the subscribers. This means that a default archiving strategy will be applied to
+all the attributes. Of course this strategy can be tuned individually for each attribute if needed.
+Archiving strategies are ['ALWAYS','RUN','SHUTDOWN','SERVICE']
+- ALWAYS:always stored
+- RUN:stored during run
+- SHUTDOWN:stored during shutdown
+- SERVICE:stored during maintenance activities
+
+## Add an attribute
+When adding an attribute to the archiving framework, we must define the following properties:
+- the EventSubscriber name that will take charge of the attribute
+- the archiving strategy (4 options defined above)
+- the attribute polling period (it should have been already defined in TangoDB)
+- the archive event period (MOST IMPORTANT, it defines the frequency rate at which an attribute is archived in the DBMS)
+
+It is important to understand that, when an attribute is successfully added to the EventSubscriber list, the archiving begins without an explicit 'Start' command, rather it follows the archiving strategy already defined.
+
+The 'Start' command is used instead during a session when an attribute has been paused/stopped for any reason, or it has raised some kind of issue.
+
+## Difference between Stop and Remove an attribute
+When stopping an attribute archiving, the framework does not remove it from the list.
+This means that archiving is stopped for the current session, but if the device is restarted,  the attribute archiving will be restarted as well.
+In order to definitely stop the archiving, the attribute must be removed from the attribute list.
+
+## Update an attribute
+If we want to update the archiving properties of an attribute (e.g. the archive event period), there is a relative method.
+It must be noted that the updating is not istantaneous because, following the framework architecture, an attribute must be first removed from the EventSubscriber list and then re-added with the new properties.
diff --git a/devices/toolkit/archiver.py b/devices/toolkit/archiver.py
index 94ce98ce41cc5983834059cf30e08ff7ebf3a8b5..aa67d66a1da78ca2d9420f52d9dd97816ff9d6a9 100644
--- a/devices/toolkit/archiver.py
+++ b/devices/toolkit/archiver.py
@@ -1,9 +1,12 @@
 #! /usr/bin/env python3
 
+from logging import raiseExceptions
+import traceback
 from clients.attribute_wrapper import attribute_wrapper
-from tango import DeviceProxy
+from tango import DeviceProxy, AttributeProxy
 from datetime import datetime, timedelta
 
+import time
 from sqlalchemy import create_engine, and_
 from sqlalchemy.orm import sessionmaker
 from .archiver_base import *
@@ -12,32 +15,216 @@ class Archiver():
     """
     The Archiver class implements the basic operations to perform attributes archiving
     """
-    def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01', es_name: str = 'archiving/hdbpp/eventsubscriber01'):
+    def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01', es_name: str = 'archiving/hdbpp/eventsubscriber01', context: str = 'RUN'):
         self.cm_name = cm_name
         self.cm = DeviceProxy(cm_name)
+        try: 
+            cm_state = self.cm.state() # ping the device server
+            if cm_state is 'FAULT':
+                print('Configuration Manager is in FAULT state')
+                print(self.cm.status())
+                return
+        except:
+            print(traceback.format_exc())
+            return
         self.es_name = es_name
         self.es = DeviceProxy(es_name)
+        self.cm.write_attribute('Context',context)    # Set default Context Archiving for all the subscribers
 
-    def add_attribute_to_archiver(self, attribute: str, polling_period: float = 1000, event_period: float = 1000, strategy: str = 'ALWAYS'):
+    def add_attribute_to_archiver(self, attribute_name: str, polling_period: int = 1000, event_period: int = 1000, strategy: str = 'RUN'):
         """
         Takes as input the attribute name, polling period (ms), event period (ms) and archiving strategy, 
         and adds the selected attribute to the subscriber's list of archiving attributes.
         The ConfigurationManager and EventSubscriber devices must be already up and running.
         The archiving-DBMS must be already properly configured.
         """
-        self.cm.write_attribute('SetAttributeName', attribute)
-        self.cm.write_attribute('SetArchiver', self.es_name)
-        self.cm.write_attribute('SetStrategy', strategy)
-        self.cm.write_attribute('SetPollingPeriod', int(polling_period))
-        self.cm.write_attribute('SetPeriodEvent', int(event_period))
-        self.cm.AttributeAdd()
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException 
+        try:
+            self.cm.write_attribute('SetAttributeName', attribute_name)
+            self.cm.write_attribute('SetArchiver', self.es_name)
+            self.cm.write_attribute('SetStrategy', strategy)
+            self.cm.write_attribute('SetPollingPeriod', polling_period)
+            self.cm.write_attribute('SetPeriodEvent', event_period)
+            self.cm.AttributeAdd()
+            print('Attribute %s added to archiving list!' % attribute_name)
+        except Exception as e:
+            if 'already archived' not in str(e).lower():
+                traceback.format_exc()
+            else: 
+                print('Attribute %s already in archiving list!' % attribute_name)
 
-    def remove_attribute_from_archiver(self, attribute: str):
+    def add_attributes_to_archiver(self,device_name,global_archive_period:int = None, exclude:list = ['Status','State']):
+        """
+        Add sequentially all the attributes of the selected device in the event subscriber list, if not already present
+        """
+        d = DeviceProxy(device_name)
+        attrs_list = list(d.get_attribute_list()) # cast to list otherwise removal is not allowed
+        try: 
+            for a in exclude: attrs_list.remove(a)
+        except:
+            pass
+        for a in attrs_list:
+            attr_fullname = str(device_name+'/'+a).lower()
+            attr_proxy = AttributeProxy(attr_fullname)
+            if attr_proxy.is_polled() is True:   # if not polled attribute is also not archived
+                try:
+                    if self.es.AttributeList is None or not(self.cm.AttributeSearch(a)):
+                        polling_period = attr_proxy.get_poll_period()  
+                        archive_period = global_archive_period or int(attr_proxy.get_property('archive_period')['archive_period'][0])                 
+                        self.add_attribute_to_archiver(attr_fullname,polling_period=polling_period,
+                            event_period=archive_period)
+                        #time.sleep(0.5)
+                except:
+                    print(traceback.format_exc())
+    
+    def remove_attribute_from_archiver(self, attribute_name:str):
         """
         Stops the data archiving of the attribute passed as input, and remove it from the subscriber's list. 
         """
-        self.cm.AttributeStop(attribute)
-        self.cm.AttributeRemove(attribute)       
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException 
+        try:
+            self.cm.AttributeStop(attribute_name)
+            self.cm.AttributeRemove(attribute_name)
+            print('Attribute %s removed!' % attribute_name)
+        except Exception as e:
+            if 'attribute not found' not in str(e).lower():
+                traceback.format_exc()
+            else: 
+                print('Attribute %s not found!' % attribute_name)      
+    
+    def remove_attributes_by_device(self,device_name:str):
+        """
+        Stops the data archiving of all the attributes of the selected device, and remove them from the
+        subscriber's list
+        """
+        d = DeviceProxy(device_name)
+        attrs_list = d.get_attribute_list()
+        for a in attrs_list:
+            try:
+                attr_fullname = str(device_name+'/'+a).lower()
+                self.remove_attribute_from_archiver(attr_fullname)
+            except:
+                print(traceback.format_exc())
+
+    def start_archiving_attribute(self, attribute_name:str):
+        """
+        Starts the archiving of the attribute passed as input.
+        The attribute must be already present in the subscriber's list
+        """
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException 
+        try:
+            self.cm.AttributeStart(attribute_name)
+        except Exception as e:
+            if 'attribute not found' not in str(e).lower():
+                traceback.format_exc()
+            else: 
+                print('Attribute %s not found!' % attribute_name)
+    
+    def stop_archiving_attribute(self, attribute_name:str):
+        """
+        Stops the archiving of the attribute passed as input.
+        The attribute must be already present in the subscriber's list
+        """
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException
+        try:
+            self.cm.AttributeStop(attribute_name)
+        except Exception as e:
+            if 'attribute not found' not in str(e).lower():
+                traceback.format_exc()
+            else: 
+                print('Attribute %s not found!' % attribute_name)
+    
+    def check_and_add_attribute_in_archiving_list(self, attribute_name:str):
+        """
+        Check if an attribute is in the archiving list
+        """
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException
+        # Add attribute if not present in event subscriber list
+        try:
+            if self.es.AttributeList is None or not(self.cm.AttributeSearch(attribute_name)):
+                self.add_attribute_to_archiver(attribute_name)
+        except:
+            print(traceback.format_exc())
+        return attribute_name
+    
+    def update_archiving_attribute(self, attribute_name: str, polling_period: int = 1000, event_period: int = 1000, strategy: str = 'RUN'):
+        """
+        Update the archiving properties of an attribute already in a subscriber list
+        """
+        try:
+            self.remove_attribute_from_archiver(attribute_name)
+            time.sleep(1)
+            self.add_attribute_to_archiver(attribute_name,polling_period,event_period,strategy)
+            time.sleep(1)
+            self.start_archiving_attribute(attribute_name)
+        except:
+            print(traceback.format_exc())
+    
+    def get_subscriber_attributes(self,es_name:str = None):
+        """
+        Return the list of attributes managed by the event subscriber
+        """
+        if es_name is not None:
+            es = DeviceProxy(es_name)
+        else: 
+            es = self.es
+        attrs = es.AttributeList or []
+        return attrs
+    
+    def get_subscriber_errors(self,es_name:str = None):
+        """
+        Return a dictionary of the attributes currently in error, defined as AttributeName -> AttributeError
+        """
+        if es_name is not None:
+            es = DeviceProxy(es_name)
+        else: 
+            es = self.es
+        try:
+            attrs = es.AttributeList or []
+            errs = es.AttributeErrorList or []
+            return dict((a,e) for a,e in zip(attrs,errs) if e)
+        except:
+            print('No attribute errors in the subscriber')
+            return {}
+    
+    def get_attribute_errors(self,attribute_name:str):
+        """
+        Return the error related to the attribute
+        """
+        if (len(attribute_name.split('/'))!=4): 
+            raise AttributeFormatException
+        errs_dict = self.get_subscriber_errors()
+        for e in errs_dict:
+            if attribute_name in e:
+                return errs_dict.get(e)
+        return None
+    
+    def get_subscriber_load(self,use_freq:bool=True,es_name:str = None):
+        """
+        Return the estimated load of an archiver, in frequency of records or number
+        of attributes
+        """
+        if es_name is not None:
+            es = DeviceProxy(es_name)
+        else: 
+            es = self.es
+        if use_freq:
+            return str(es.AttributeRecordFreq)+(' events/period' )
+        else:
+            return len(es.AttributeList or []) 
+    
+class AttributeFormatException(Exception):
+    """
+    Exception that handles wrong attribute naming
+    """
+    def __init__(self, message="Wrong Tango attribute format! Try: domain/family/member/attribute (e.g. LTS/PCC/1/temperature)"):
+        self.message = message
+        super().__init__(self.message)
 
 class Retriever():
     """
diff --git a/devices/tox.ini b/devices/tox.ini
index 82bee5e4ade0cdc4763ba34515c5fafef2437e56..59d2347f3ff42ccb084033aea67d478fd63513cb 100644
--- a/devices/tox.ini
+++ b/devices/tox.ini
@@ -24,7 +24,7 @@ commands = stestr run {posargs}
 passenv = TANGO_HOST
 setenv = TESTS_DIR=./integration_test
 commands =
-    stestr run --serial
+    stestr run --serial {posargs}
 
 ; TODO(Corne): Integrate Hacking to customize pep8 rules
 [testenv:pep8]
@@ -39,9 +39,9 @@ commands =
 ;             It thus matters what interfaces Docker will bind our
 ;             containers to, not what our containers listen on.
 commands =
-    bandit -r devices/ clients/ common/ examples/ util/ -n5 -ll -s B104
+    bandit -r devices/ -n5 -ll -s B104
 
 [flake8]
 filename = *.py,.stestr.conf,.txt
-select = W292
+select = W292,B601,B602,T100,M001
 exclude=.tox,.egg-info
diff --git a/docker-compose/Makefile b/docker-compose/Makefile
index 09eb760123bc4687207609c3ad94c740a72c317c..686f88f9e4887039cbe5206a5a88ecb8df9aed8c 100644
--- a/docker-compose/Makefile
+++ b/docker-compose/Makefile
@@ -29,6 +29,12 @@ ifeq (start,$(firstword $(MAKECMDGOALS)))
     SERVICE_TARGET = true
 else ifeq (stop,$(firstword $(MAKECMDGOALS)))
     SERVICE_TARGET = true
+else ifeq (restart,$(firstword $(MAKECMDGOALS)))
+    SERVICE_TARGET = true
+else ifeq (build,$(firstword $(MAKECMDGOALS)))
+    SERVICE_TARGET = true
+else ifeq (build-nocache,$(firstword $(MAKECMDGOALS)))
+    SERVICE_TARGET = true
 else ifeq (attach,$(firstword $(MAKECMDGOALS)))
     SERVICE_TARGET = true
     ifndef NETWORK_MODE
@@ -118,7 +124,7 @@ DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) \
     CONTAINER_EXECUTION_UID=$(shell id -u)
 
 
-.PHONY: up down minimal start stop status clean pull help
+.PHONY: up down minimal start stop restart build build-nocache status clean pull help
 .DEFAULT_GOAL := help
 
 pull: ## pull the images from the Docker hub
@@ -127,7 +133,12 @@ pull: ## pull the images from the Docker hub
 build: ## rebuild images
 	# docker-compose does not support build dependencies, so manage those here
 	$(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml -f networks.yml build
-	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build
+	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build $(SERVICE)
+
+build-nocache: ## rebuild images from scratch
+	# docker-compose does not support build dependencies, so manage those here
+	$(DOCKER_COMPOSE_ARGS) docker-compose -f lofar-device-base.yml -f networks.yml build
+	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) build --no-cache $(SERVICE)
 
 up: minimal  ## start the base TANGO system and prepare all services
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) up --no-start
@@ -152,6 +163,11 @@ start: up ## start a service (usage: make start <servicename>)
 
 stop:  ## stop a service (usage: make stop <servicename>)
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) stop $(SERVICE)
+	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) rm -f $(SERVICE)
+
+restart: ## restart a service (usage: make restart <servicename>)
+	make stop $(SERVICE) # cannot use dependencies, as that would allow start and stop to run in parallel..
+	make start $(SERVICE)
 
 attach:  ## attach a service to an existing Tango network
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(ATTACH_COMPOSE_FILE_ARGS) up -d $(SERVICE)
@@ -162,8 +178,9 @@ status:  ## show the container status
 images:  ## show the container images
 	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) images
 
-clean: down  ## clear all TANGO database entries
+clean: down  ## clear all TANGO database entries, and all containers
 	docker volume rm $(BASEDIR)_tangodb
+	$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) rm -f
 
 help:   ## show this help.
 	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/docker-compose/device-pcc.yml b/docker-compose/device-pcc.yml
index ebf71352df76969e879a5d73f022705a202ab925..4bb079f37b5eb64ffabe0eb62fbee8cc1d1e7bcb 100644
--- a/docker-compose/device-pcc.yml
+++ b/docker-compose/device-pcc.yml
@@ -25,7 +25,7 @@ services:
     networks:
       - control
     ports:
-      - "5700:5700" # unique port for this DS
+      - "5707:5707" # unique port for this DS
     volumes:
         - ${TANGO_LOFAR_CONTAINER_MOUNT}
     environment:
@@ -38,5 +38,5 @@ services:
       - --
       # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
       # can't know about our Docker port forwarding
-      - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/pcc.py LTS -v -ORBendPoint giop:tcp:0:5700 -ORBendPointPublish giop:tcp:${HOSTNAME}:5700
+      - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/pcc.py LTS -v -ORBendPoint giop:tcp:0:5707 -ORBendPointPublish giop:tcp:${HOSTNAME}:5707
     restart: on-failure
diff --git a/docker-compose/device-unb2.yml b/docker-compose/device-unb2.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f41651c95c4bb0677b81fa9a8f4b717144bc0828
--- /dev/null
+++ b/docker-compose/device-unb2.yml
@@ -0,0 +1,42 @@
+#
+# Docker compose file that launches an interactive iTango session.
+#
+# Connect to the interactive session with 'docker attach itango'.
+# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q>
+#
+# Defines:
+#   - itango: iTango interactive session
+#
+# Requires:
+#   - lofar-device-base.yml
+#
+version: '2'
+
+services:
+  device-unb2:
+    image: device-unb2
+    # build explicitly, as docker-compose does not understand a local image
+    # being shared among services.
+    build:
+        context: lofar-device-base
+        args:
+            SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION}
+    container_name: ${CONTAINER_NAME_PREFIX}device-unb2
+    networks:
+      - control
+    ports:
+      - "5704:5704" # unique port for this DS
+    volumes:
+        - ${TANGO_LOFAR_CONTAINER_MOUNT}
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+    entrypoint:
+      - /usr/local/bin/wait-for-it.sh
+      - ${TANGO_HOST}
+      - --timeout=30
+      - --strict
+      - --
+      # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
+      # can't know about our Docker port forwarding
+      - python3 -u ${TANGO_LOFAR_CONTAINER_DIR}/devices/devices/unb2.py LTS -v -ORBendPoint giop:tcp:0:5704 -ORBendPointPublish giop:tcp:${HOSTNAME}:5704
+    restart: on-failure
diff --git a/docker-compose/grafana.yml b/docker-compose/grafana.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1a9b3ee77aa53fcef367e1159c1b8623971ad5d7
--- /dev/null
+++ b/docker-compose/grafana.yml
@@ -0,0 +1,26 @@
+#
+# Docker compose file that launches Grafana
+#
+# Defines:
+#   - grafana: Grafana
+#
+version: '2'
+
+volumes:
+  grafana-data: {}
+  grafana-configs: {}
+
+services:
+  grafana:
+    image: grafana
+    build:
+        context: grafana
+    container_name: ${CONTAINER_NAME_PREFIX}grafana
+    networks:
+      - control
+    volumes:
+      - grafana-data:/var/lib/grafana
+      - grafana-configs:/etc/grafana
+    ports:
+      - "3000:3000"
+    restart: unless-stopped
diff --git a/docker-compose/grafana/Dockerfile b/docker-compose/grafana/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..83bc4448c660717c7f36655b14e21ee3c7137425
--- /dev/null
+++ b/docker-compose/grafana/Dockerfile
@@ -0,0 +1,8 @@
+FROM grafana/grafana
+
+# Add default configuration through provisioning (see https://grafana.com/docs/grafana/latest/administration/provisioning)
+#
+# Note: for changes to take effect, make sure you remove the grafana-data and grafana-configs docker volumes
+COPY datasources /etc/grafana/provisioning/datasources/
+COPY dashboards /var/lib/grafana/dashboards/
+COPY stationcontrol-dashboards.yaml /etc/grafana/provisioning/dashboards/
diff --git a/docker-compose/grafana/dashboards/lofar2.0-station.json b/docker-compose/grafana/dashboards/lofar2.0-station.json
new file mode 100644
index 0000000000000000000000000000000000000000..65a36407dc17e037fdaf377da92e82854b9e7554
--- /dev/null
+++ b/docker-compose/grafana/dashboards/lofar2.0-station.json
@@ -0,0 +1,1258 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "links": [],
+  "panels": [
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 15,
+      "panels": [],
+      "title": "Devices",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [
+            {
+              "options": {
+                "0": {
+                  "color": "green",
+                  "index": 1,
+                  "text": "ON"
+                },
+                "1": {
+                  "color": "red",
+                  "index": 3,
+                  "text": "OFF"
+                },
+                "7": {
+                  "color": "yellow",
+                  "index": 2,
+                  "text": "STANDBY"
+                },
+                "8": {
+                  "color": "red",
+                  "index": 0,
+                  "text": "FAULT"
+                },
+                "11": {
+                  "color": "red",
+                  "index": 4,
+                  "text": "ALARM"
+                }
+              },
+              "type": "value"
+            }
+          ],
+          "noValue": "???",
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "string"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 0,
+        "y": 1
+      },
+      "id": 4,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "value_and_name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{label=\"State\"}",
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{device}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Device States",
+      "type": "stat"
+    },
+    {
+      "datasource": "ELK logs",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 1
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 9,
+        "w": 12,
+        "x": 12,
+        "y": 1
+      },
+      "id": 32,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "list",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "targets": [
+        {
+          "alias": "",
+          "bucketAggs": [
+            {
+              "field": "extra.tango_device.keyword",
+              "id": "2",
+              "settings": {
+                "min_doc_count": "0",
+                "order": "desc",
+                "orderBy": "_term",
+                "size": "10"
+              },
+              "type": "terms"
+            },
+            {
+              "field": "@timestamp",
+              "id": "3",
+              "settings": {
+                "interval": "auto",
+                "min_doc_count": "0",
+                "trimEdges": "0"
+              },
+              "type": "date_histogram"
+            }
+          ],
+          "metrics": [
+            {
+              "id": "1",
+              "type": "count"
+            }
+          ],
+          "query": "level:(ERROR or FATAL)",
+          "refId": "A",
+          "timeField": "@timestamp"
+        }
+      ],
+      "title": "Errors",
+      "type": "timeseries"
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 10
+      },
+      "id": 17,
+      "panels": [],
+      "title": "PCC",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "min": 0,
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "celsius"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 0,
+        "y": 11
+      },
+      "id": 22,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "hidden",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/pcc/1\",name=\"RCU_temperature_R\"} - 273.15",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "RCU temperatures",
+      "transformations": [],
+      "type": "timeseries"
+    },
+    {
+      "datasource": "Prometheus",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 0
+              },
+              {
+                "color": "green",
+                "value": 3
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 5,
+        "y": 11
+      },
+      "id": 21,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "sum by (x)(1 + (device_attribute{device=\"lts/pcc/1\",name=\"RCU_ADC_lock_R\"} == bool 129)) * on(x) device_attribute{device=\"lts/pcc/1\",name=\"RCU_mask_RW\"} - 3",
+          "interval": "",
+          "legendFormat": "{{y}}",
+          "refId": "A"
+        }
+      ],
+      "title": "RCU ADC lock",
+      "type": "stat"
+    },
+    {
+      "datasource": "Prometheus",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 1
+              },
+              {
+                "color": "green",
+                "value": 2
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 11,
+        "y": 11
+      },
+      "id": 25,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "(2 - device_attribute{device=\"lts/pcc/1\",name=\"RCU_I2C_STATUS_R\"}) * on(x) device_attribute{device=\"lts/pcc/1\",name=\"RCU_mask_RW\"}",
+          "interval": "",
+          "legendFormat": "{{y}}",
+          "refId": "A"
+        }
+      ],
+      "title": "RCU I2C status",
+      "type": "stat"
+    },
+    {
+      "datasource": "Prometheus",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 1
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 5,
+        "w": 3,
+        "x": 17,
+        "y": 11
+      },
+      "id": 24,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "none",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "1-device_attribute{device=\"lts/pcc/1\",name=\"CLK_Enable_PWR_R\"}",
+          "interval": "",
+          "legendFormat": "Power",
+          "refId": "A"
+        },
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/pcc/1\",name=\"CLK_I2C_STATUS_R\"}",
+          "hide": false,
+          "interval": "",
+          "legendFormat": "I2C",
+          "refId": "B"
+        },
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/pcc/1\",name=\"CLK_PLL_error_R\"}",
+          "hide": false,
+          "interval": "",
+          "legendFormat": "PLL",
+          "refId": "C"
+        },
+        {
+          "exemplar": true,
+          "expr": "1-device_attribute{device=\"lts/pcc/1\",name=\"CLK_PLL_locked_R\"}",
+          "hide": false,
+          "interval": "",
+          "legendFormat": "PLL Lock",
+          "refId": "D"
+        }
+      ],
+      "title": "Clock",
+      "type": "stat"
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 19
+      },
+      "id": 19,
+      "panels": [],
+      "title": "SDP",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          },
+          "unit": "celsius"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 0,
+        "y": 20
+      },
+      "id": 5,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "hidden",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/sdp/1\",name=\"FPGA_temp_R\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "FPGA temperatures",
+      "transformations": [],
+      "type": "timeseries"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 50
+              },
+              {
+                "color": "red",
+                "value": 100
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 5,
+        "y": 20
+      },
+      "id": 11,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "(50+50*device_attribute{device=\"lts/sdp/1\",name=\"TR_fpga_communication_error_R\"}) * on(x) device_attribute{device=\"lts/sdp/1\",name=\"TR_fpga_mask_R\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "FPGA communication",
+      "transformations": [],
+      "type": "stat"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 50
+              },
+              {
+                "color": "red",
+                "value": 100
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 10,
+        "y": 20
+      },
+      "id": 9,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "(100-50*device_attribute{device=\"lts/sdp/1\",name=\"FPGA_processing_enable_R\"}) * on(x) device_attribute{device=\"lts/sdp/1\",name=\"TR_fpga_mask_R\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "FPGA processing enabled",
+      "transformations": [],
+      "type": "stat"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "Measured difference between PTP and PPS",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": 60000,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "decimals": 2,
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "red",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 0.001
+              },
+              {
+                "color": "red",
+                "value": 0.1
+              }
+            ]
+          },
+          "unit": "s"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 15,
+        "y": 20
+      },
+      "id": 13,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "hidden",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/sdp/1\",name=\"TR_tod_pps_delta_R\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "FPGA Clock offset",
+      "transformations": [],
+      "type": "timeseries"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "Number of inputs that are fed from the SDP wave-form generator",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [
+            {
+              "options": {
+                "0": {
+                  "index": 0,
+                  "text": "OFF"
+                }
+              },
+              "type": "value"
+            }
+          ],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 1
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 4,
+        "w": 3,
+        "x": 20,
+        "y": 20
+      },
+      "id": 12,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "value"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "sum(sum by(x) (device_attribute{device=\"lts/sdp/1\",name=\"FPGA_wg_enable_RW\"}) * on(x) device_attribute{device=\"lts/sdp/1\",name=\"TR_fpga_mask_R\"})",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "Waveform generator",
+      "transformations": [],
+      "type": "stat"
+    },
+    {
+      "collapsed": true,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 28
+      },
+      "id": 27,
+      "panels": [],
+      "title": "SST",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 50
+              },
+              {
+                "color": "red",
+                "value": 100
+              }
+            ]
+          }
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 0,
+        "y": 29
+      },
+      "id": 28,
+      "options": {
+        "colorMode": "background",
+        "graphMode": "area",
+        "justifyMode": "auto",
+        "orientation": "auto",
+        "reduceOptions": {
+          "calcs": [
+            "lastNotNull"
+          ],
+          "fields": "",
+          "values": false
+        },
+        "text": {},
+        "textMode": "name"
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "(100-50*device_attribute{device=\"lts/sst/1\",name=\"FPGA_sst_offload_enable_R\"}) * on(x) device_attribute{device=\"lts/sdp/1\",name=\"TR_fpga_mask_R\"}",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "SST offloading enabled",
+      "transformations": [],
+      "type": "stat"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 50
+              },
+              {
+                "color": "red",
+                "value": 100
+              }
+            ]
+          },
+          "unit": "pps"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 5,
+        "y": 29
+      },
+      "id": 29,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "hidden",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "rate(device_attribute{device=\"lts/sst/1\",name=\"nof_invalid_packets_R\"}[1m])",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "invalid",
+          "refId": "A"
+        },
+        {
+          "exemplar": true,
+          "expr": "rate(device_attribute{device=\"lts/sst/1\",name=\"nof_packets_dropped_R\"}[1m])",
+          "hide": false,
+          "interval": "",
+          "legendFormat": "dropped",
+          "refId": "B"
+        },
+        {
+          "exemplar": true,
+          "expr": "rate(device_attribute{device=\"lts/sst/1\",name=\"nof_payload_errors_R\"}[1m])",
+          "hide": false,
+          "interval": "",
+          "legendFormat": "payload errors {{x}}",
+          "refId": "C"
+        }
+      ],
+      "title": "SST packet errors",
+      "transformations": [],
+      "type": "timeseries"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "palette-classic"
+          },
+          "custom": {
+            "axisLabel": "",
+            "axisPlacement": "auto",
+            "barAlignment": 0,
+            "drawStyle": "line",
+            "fillOpacity": 0,
+            "gradientMode": "none",
+            "hideFrom": {
+              "legend": false,
+              "tooltip": false,
+              "viz": false
+            },
+            "lineInterpolation": "linear",
+            "lineWidth": 1,
+            "pointSize": 5,
+            "scaleDistribution": {
+              "type": "linear"
+            },
+            "showPoints": "auto",
+            "spanNulls": false,
+            "stacking": {
+              "group": "A",
+              "mode": "none"
+            },
+            "thresholdsStyle": {
+              "mode": "off"
+            }
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "transparent",
+                "value": null
+              },
+              {
+                "color": "green",
+                "value": 50
+              },
+              {
+                "color": "red",
+                "value": 100
+              }
+            ]
+          },
+          "unit": "pps"
+        },
+        "overrides": []
+      },
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 10,
+        "y": 29
+      },
+      "id": 30,
+      "options": {
+        "legend": {
+          "calcs": [],
+          "displayMode": "hidden",
+          "placement": "bottom"
+        },
+        "tooltip": {
+          "mode": "single"
+        }
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "rate(device_attribute{device=\"lts/sst/1\",name=\"nof_valid_payloads_R\"}[1m])",
+          "format": "time_series",
+          "hide": false,
+          "instant": false,
+          "interval": "",
+          "legendFormat": "{{x}}",
+          "refId": "A"
+        }
+      ],
+      "title": "SST packets",
+      "transformations": [],
+      "type": "timeseries"
+    }
+  ],
+  "refresh": false,
+  "schemaVersion": 30,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-30m",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "LOFAR2.0 Station",
+  "uid": "6f7Pv8Vnz",
+  "version": 1
+}
diff --git a/docker-compose/grafana/dashboards/version-information.json b/docker-compose/grafana/dashboards/version-information.json
new file mode 100644
index 0000000000000000000000000000000000000000..a8e95f2a3af3d9eb5feb2e0eeb29ec0f8059ce31
--- /dev/null
+++ b/docker-compose/grafana/dashboards/version-information.json
@@ -0,0 +1,685 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "id": 2,
+  "links": [],
+  "panels": [
+    {
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 7,
+      "title": "SC",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "align": "auto",
+            "displayMode": "auto"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "version"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 1533
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "device"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 136
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 6,
+        "w": 8,
+        "x": 0,
+        "y": 1
+      },
+      "id": 9,
+      "options": {
+        "showHeader": true,
+        "sortBy": []
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{name=\"version_R\"}",
+          "instant": true,
+          "interval": "",
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Device software versions",
+      "transformations": [
+        {
+          "id": "labelsToFields",
+          "options": {}
+        },
+        {
+          "id": "organize",
+          "options": {
+            "excludeByName": {
+              "Time": true,
+              "Value": true,
+              "device": false,
+              "dim_x": true,
+              "dim_y": true,
+              "instance": true,
+              "job": true,
+              "label": true,
+              "name": true,
+              "type": true,
+              "x": true,
+              "y": true
+            },
+            "indexByName": {},
+            "renameByName": {
+              "Time": "time",
+              "Value": "count",
+              "str_value": "version"
+            }
+          }
+        }
+      ],
+      "type": "table"
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 7
+      },
+      "id": 5,
+      "panels": [],
+      "title": "SDP",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "align": "auto",
+            "displayMode": "auto"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "version"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 1907
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "x"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 114
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 17,
+        "w": 8,
+        "x": 0,
+        "y": 8
+      },
+      "id": 2,
+      "options": {
+        "showHeader": true,
+        "sortBy": []
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/sdp/1\",name=\"FPGA_firmware_version_R\"}",
+          "instant": true,
+          "interval": "",
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Firmware versions",
+      "transformations": [
+        {
+          "id": "labelsToFields",
+          "options": {}
+        },
+        {
+          "id": "organize",
+          "options": {
+            "excludeByName": {
+              "Time": true,
+              "Value": true,
+              "device": true,
+              "dim_x": true,
+              "dim_y": true,
+              "instance": true,
+              "job": true,
+              "label": true,
+              "name": true,
+              "str_value": false,
+              "type": true,
+              "y": true
+            },
+            "indexByName": {
+              "Time": 1,
+              "Value": 12,
+              "device": 2,
+              "dim_x": 3,
+              "dim_y": 4,
+              "instance": 5,
+              "job": 6,
+              "label": 7,
+              "name": 8,
+              "str_value": 9,
+              "type": 10,
+              "x": 0,
+              "y": 11
+            },
+            "renameByName": {
+              "Time": "time",
+              "Value": "count",
+              "str_value": "version"
+            }
+          }
+        },
+        {
+          "id": "sortBy",
+          "options": {
+            "fields": {},
+            "sort": [
+              {
+                "field": "x"
+              }
+            ]
+          }
+        }
+      ],
+      "type": "table"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "align": "auto",
+            "displayMode": "auto"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "version"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 1907
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "x"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 114
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 17,
+        "w": 8,
+        "x": 8,
+        "y": 8
+      },
+      "id": 13,
+      "options": {
+        "showHeader": true,
+        "sortBy": []
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/sdp/1\",name=\"FPGA_hardware_version_R\"}",
+          "instant": true,
+          "interval": "",
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Hardware versions",
+      "transformations": [
+        {
+          "id": "labelsToFields",
+          "options": {}
+        },
+        {
+          "id": "organize",
+          "options": {
+            "excludeByName": {
+              "Time": true,
+              "Value": true,
+              "device": true,
+              "dim_x": true,
+              "dim_y": true,
+              "instance": true,
+              "job": true,
+              "label": true,
+              "name": true,
+              "str_value": false,
+              "type": true,
+              "y": true
+            },
+            "indexByName": {
+              "Time": 1,
+              "Value": 12,
+              "device": 2,
+              "dim_x": 3,
+              "dim_y": 4,
+              "instance": 5,
+              "job": 6,
+              "label": 7,
+              "name": 8,
+              "str_value": 9,
+              "type": 10,
+              "x": 0,
+              "y": 11
+            },
+            "renameByName": {
+              "Time": "time",
+              "Value": "count",
+              "str_value": "version"
+            }
+          }
+        },
+        {
+          "id": "sortBy",
+          "options": {
+            "fields": {},
+            "sort": [
+              {
+                "field": "x"
+              }
+            ]
+          }
+        }
+      ],
+      "type": "table"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "align": "auto",
+            "displayMode": "auto"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "version"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 497
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 3,
+        "w": 5,
+        "x": 16,
+        "y": 8
+      },
+      "id": 8,
+      "options": {
+        "showHeader": true,
+        "sortBy": []
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "count(device_attribute{device=\"lts/sdp/1\",name=\"TR_software_version_R\"}) by (str_value)",
+          "instant": true,
+          "interval": "",
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "Translator software versions",
+      "transformations": [
+        {
+          "id": "labelsToFields",
+          "options": {}
+        },
+        {
+          "id": "organize",
+          "options": {
+            "excludeByName": {
+              "Time": true,
+              "Value": true
+            },
+            "indexByName": {},
+            "renameByName": {
+              "Time": "time",
+              "Value": "count",
+              "str_value": "version"
+            }
+          }
+        }
+      ],
+      "type": "table"
+    },
+    {
+      "collapsed": false,
+      "datasource": null,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 25
+      },
+      "id": 11,
+      "panels": [],
+      "title": "PCC",
+      "type": "row"
+    },
+    {
+      "datasource": "Prometheus",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "color": {
+            "mode": "thresholds"
+          },
+          "custom": {
+            "align": "auto",
+            "displayMode": "auto"
+          },
+          "mappings": [],
+          "thresholds": {
+            "mode": "absolute",
+            "steps": [
+              {
+                "color": "green",
+                "value": null
+              },
+              {
+                "color": "red",
+                "value": 80
+              }
+            ]
+          }
+        },
+        "overrides": [
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "version"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 497
+              }
+            ]
+          },
+          {
+            "matcher": {
+              "id": "byName",
+              "options": "x"
+            },
+            "properties": [
+              {
+                "id": "custom.width",
+                "value": 81
+              }
+            ]
+          }
+        ]
+      },
+      "gridPos": {
+        "h": 32,
+        "w": 7,
+        "x": 0,
+        "y": 26
+      },
+      "id": 12,
+      "options": {
+        "showHeader": true,
+        "sortBy": []
+      },
+      "pluginVersion": "8.1.2",
+      "targets": [
+        {
+          "exemplar": true,
+          "expr": "device_attribute{device=\"lts/pcc/1\",name=\"RCU_version_R\"}",
+          "instant": true,
+          "interval": "",
+          "legendFormat": "",
+          "refId": "A"
+        }
+      ],
+      "title": "RCU versions",
+      "transformations": [
+        {
+          "id": "labelsToFields",
+          "options": {}
+        },
+        {
+          "id": "organize",
+          "options": {
+            "excludeByName": {
+              "Time": true,
+              "Value": true,
+              "device": true,
+              "dim_x": true,
+              "dim_y": true,
+              "instance": true,
+              "job": true,
+              "label": true,
+              "name": true,
+              "type": true,
+              "y": true
+            },
+            "indexByName": {
+              "Time": 1,
+              "Value": 12,
+              "device": 2,
+              "dim_x": 3,
+              "dim_y": 4,
+              "instance": 5,
+              "job": 6,
+              "label": 7,
+              "name": 8,
+              "str_value": 9,
+              "type": 10,
+              "x": 0,
+              "y": 11
+            },
+            "renameByName": {
+              "Time": "time",
+              "Value": "count",
+              "str_value": "version"
+            }
+          }
+        },
+        {
+          "id": "sortBy",
+          "options": {
+            "fields": {},
+            "sort": [
+              {
+                "field": "x"
+              }
+            ]
+          }
+        }
+      ],
+      "type": "table"
+    }
+  ],
+  "schemaVersion": 30,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Version information",
+  "uid": "eR9posS7z",
+  "version": 1
+}
diff --git a/docker-compose/grafana/datasources/archiver-maria-db.yaml b/docker-compose/grafana/datasources/archiver-maria-db.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c809d294269683f12ca82a9f28d6019c85f96723
--- /dev/null
+++ b/docker-compose/grafana/datasources/archiver-maria-db.yaml
@@ -0,0 +1,40 @@
+apiVersion: 1
+
+datasources:
+  # <string, required> name of the datasource. Required
+  - name: Archiver
+    # <string, required> datasource type. Required
+    type: mysql
+    # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required
+    access: proxy
+    # <int> org id. will default to orgId 1 if not specified
+    orgId: 1
+    # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically
+    uid: ZqAMHGN7z
+    # <string> url
+    url: archiver-maria-db
+    # <string> Deprecated, use secureJsonData.password
+    password:
+    # <string> database user, if used
+    user: tango
+    # <string> database name, if used
+    database: hdbpp
+    # <bool> enable/disable basic auth
+    basicAuth: false
+    # <string> basic auth username
+    basicAuthUser:
+    # <string> Deprecated, use secureJsonData.basicAuthPassword
+    basicAuthPassword:
+    # <bool> enable/disable with credentials headers
+    withCredentials:
+    # <bool> mark as default datasource. Max one per org
+    isDefault: true
+    # <map> fields that will be converted to json and stored in jsonData
+    jsonData:
+    # <string> json object of data that will be encrypted.
+    secureJsonData:
+      # <string> database password, if used
+      password: tango
+    version: 1
+    # <bool> allow users to edit datasources from the UI.
+    editable: false
diff --git a/docker-compose/grafana/datasources/elk.yaml b/docker-compose/grafana/datasources/elk.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7dc0535bf5bfcfd9446836d8425dd74a320918e6
--- /dev/null
+++ b/docker-compose/grafana/datasources/elk.yaml
@@ -0,0 +1,44 @@
+apiVersion: 1
+
+datasources:
+  # <string, required> name of the datasource. Required
+  - name: ELK logs
+    # <string, required> datasource type. Required
+    type: elasticsearch
+    # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required
+    access: proxy
+    # <int> org id. will default to orgId 1 if not specified
+    orgId: 1
+    # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically
+    uid: RuQjz8V7z
+    # <string> url
+    url: elk:9200
+    # <string> Deprecated, use secureJsonData.password
+    password:
+    # <string> database user, if used
+    user:
+    # <string> database name, if used
+    database: logstash-*
+    # <bool> enable/disable basic auth
+    basicAuth: false
+    # <string> basic auth username
+    basicAuthUser:
+    # <string> Deprecated, use secureJsonData.basicAuthPassword
+    basicAuthPassword:
+    # <bool> enable/disable with credentials headers
+    withCredentials:
+    # <bool> mark as default datasource. Max one per org
+    isDefault: false
+    # <map> fields that will be converted to json and stored in jsonData
+    jsonData:
+      esVersion:  7.10.0
+      includeFrozen: false
+      logLevelField:
+      logMessageField:
+      maxConcurrentShardRequests: 5
+      timeField: "@timestamp"
+    # <string> json object of data that will be encrypted.
+    secureJsonData:
+    version: 1
+    # <bool> allow users to edit datasources from the UI.
+    editable: false
diff --git a/docker-compose/grafana/datasources/prometheus.yaml b/docker-compose/grafana/datasources/prometheus.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e271f4a9c609a4e11b36bb688bed6f01faae0d74
--- /dev/null
+++ b/docker-compose/grafana/datasources/prometheus.yaml
@@ -0,0 +1,39 @@
+apiVersion: 1
+
+datasources:
+  # <string, required> name of the datasource. Required
+  - name: Prometheus
+    # <string, required> datasource type. Required
+    type: prometheus
+    # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required
+    access: proxy
+    # <int> org id. will default to orgId 1 if not specified
+    orgId: 1
+    # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically
+    uid: 6W2nM-Vnz
+    # <string> url
+    url: prometheus:9090
+    # <string> Deprecated, use secureJsonData.password
+    password:
+    # <string> database user, if used
+    user:
+    # <string> database name, if used
+    database:
+    # <bool> enable/disable basic auth
+    basicAuth: false
+    # <string> basic auth username
+    basicAuthUser:
+    # <string> Deprecated, use secureJsonData.basicAuthPassword
+    basicAuthPassword:
+    # <bool> enable/disable with credentials headers
+    withCredentials:
+    # <bool> mark as default datasource. Max one per org
+    isDefault: false
+    # <map> fields that will be converted to json and stored in jsonData
+    jsonData:
+      httpMethod: POST
+    # <string> json object of data that will be encrypted.
+    secureJsonData:
+    version: 1
+    # <bool> allow users to edit datasources from the UI.
+    editable: false
diff --git a/docker-compose/grafana/datasources/tangodb.yaml b/docker-compose/grafana/datasources/tangodb.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..9a962a2417f0c963249b53fde925d8c11fcdc996
--- /dev/null
+++ b/docker-compose/grafana/datasources/tangodb.yaml
@@ -0,0 +1,40 @@
+apiVersion: 1
+
+datasources:
+  # <string, required> name of the datasource. Required
+  - name: TangoDB
+    # <string, required> datasource type. Required
+    type: mysql
+    # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required
+    access: proxy
+    # <int> org id. will default to orgId 1 if not specified
+    orgId: 1
+    # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically
+    uid: d5_heb47k
+    # <string> url
+    url: tangodb
+    # <string> Deprecated, use secureJsonData.password
+    password:
+    # <string> database user, if used
+    user: tango
+    # <string> database name, if used
+    database: hdbpp
+    # <bool> enable/disable basic auth
+    basicAuth: false
+    # <string> basic auth username
+    basicAuthUser:
+    # <string> Deprecated, use secureJsonData.basicAuthPassword
+    basicAuthPassword:
+    # <bool> enable/disable with credentials headers
+    withCredentials:
+    # <bool> mark as default datasource. Max one per org
+    isDefault: false
+    # <map> fields that will be converted to json and stored in jsonData
+    jsonData:
+    # <string> json object of data that will be encrypted.
+    secureJsonData:
+      # <string> database password, if used
+      password: tango
+    version: 1
+    # <bool> allow users to edit datasources from the UI.
+    editable: false
diff --git a/docker-compose/grafana/stationcontrol-dashboards.yaml b/docker-compose/grafana/stationcontrol-dashboards.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..50d300483241f1c5c4b1c992d834bfa4d71014f6
--- /dev/null
+++ b/docker-compose/grafana/stationcontrol-dashboards.yaml
@@ -0,0 +1,24 @@
+apiVersion: 1
+
+providers:
+  # <string> an unique provider name. Required
+  - name: 'StationControl'
+    # <int> Org id. Default to 1
+    orgId: 1
+    # <string> name of the dashboard folder.
+    folder: ''
+    # <string> folder UID. will be automatically generated if not specified
+    folderUid: ''
+    # <string> provider type. Default to 'file'
+    type: file
+    # <bool> disable dashboard deletion
+    disableDeletion: true
+    # <int> how often Grafana will scan for changed dashboards
+    updateIntervalSeconds: 60
+    # <bool> allow updating provisioned dashboards from the UI
+    allowUiUpdates: false
+    options:
+      # <string, required> path to dashboard files on disk. Required when using the 'file' type
+      path: /var/lib/grafana/dashboards
+      # <bool> use folder names from filesystem to create folders in Grafana
+      foldersFromFilesStructure: true
diff --git a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
index bc56f8b05de5e90804562bcf77378ae8798100a2..22be4e90bb9a5ec927b9a7b3ac9b542e1bb9166f 100644
--- a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
+++ b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
@@ -2,6 +2,7 @@
 pcc = DeviceProxy("LTS/PCC/1")
 sdp = DeviceProxy("LTS/SDP/1")
 sst = DeviceProxy("LTS/SST/1")
+unb2 = DeviceProxy("LTS/UNB2/1")
 
 # Put them in a list in case one wants to iterate
-devices = [pcc, sdp, sst]
+devices = [pcc, sdp, sst, unb2]
diff --git a/docker-compose/lofar-device-base/lofar-requirements.txt b/docker-compose/lofar-device-base/lofar-requirements.txt
index 69d52984a264c3a53bbcfece15be810ccaa32e7b..57ab2a14fbc6012c52e49c05f3e2119a3a886dd9 100644
--- a/docker-compose/lofar-device-base/lofar-requirements.txt
+++ b/docker-compose/lofar-device-base/lofar-requirements.txt
@@ -2,3 +2,5 @@ opcua >= 0.98.9
 astropy
 python-logstash-async
 gitpython
+PyMySQL[rsa]
+sqlalchemy
diff --git a/docker-compose/prometheus.yml b/docker-compose/prometheus.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a0971c48fde4551809a936594aadcb6a79076712
--- /dev/null
+++ b/docker-compose/prometheus.yml
@@ -0,0 +1,19 @@
+#
+# Docker compose file that launches Prometheus
+#
+# Defines:
+#   - prometheus: Prometheus
+#
+version: '2'
+
+services:
+  prometheus:
+    image: prometheus
+    build:
+        context: prometheus
+    container_name: ${CONTAINER_NAME_PREFIX}prometheus
+    networks:
+      - control
+    ports:
+      - "9090:9090"
+    restart: unless-stopped
diff --git a/docker-compose/prometheus/Dockerfile b/docker-compose/prometheus/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..cc1494f98dbce6c66e437b001af2a88320ca0ffa
--- /dev/null
+++ b/docker-compose/prometheus/Dockerfile
@@ -0,0 +1,3 @@
+FROM prom/prometheus
+
+COPY prometheus.yml /etc/prometheus/prometheus.yml
diff --git a/docker-compose/prometheus/prometheus.yml b/docker-compose/prometheus/prometheus.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac9c549be45d6aab48f585dd6ab234cfc1f15449
--- /dev/null
+++ b/docker-compose/prometheus/prometheus.yml
@@ -0,0 +1,11 @@
+global:
+  evaluation_interval: 10s
+  scrape_interval: 10s
+  scrape_timeout: 10s
+
+scrape_configs:
+  - job_name: tango
+    static_configs:
+      - targets:
+        - "tango-prometheus-exporter:8000"
+
diff --git a/docker-compose/tango-prometheus-exporter.yml b/docker-compose/tango-prometheus-exporter.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc43a6777b5595a9d94c13e55322a7adc0a8d84f
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter.yml
@@ -0,0 +1,19 @@
+#
+# Docker compose file that launches the Tango -> Prometheus adapter
+#
+version: '2'
+
+services:
+  tango-prometheus-exporter:
+    build:
+        context: tango-prometheus-exporter
+    container_name: ${CONTAINER_NAME_PREFIX}tango-prometheus-exporter
+    networks:
+      - control
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+    ports:
+      - "8000:8000"
+    depends_on:
+      - databaseds
+    restart: unless-stopped
diff --git a/docker-compose/tango-prometheus-exporter/Dockerfile b/docker-compose/tango-prometheus-exporter/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..4f548bbc1a7ff9eebb906fe912cd7a74992bd558
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/Dockerfile
@@ -0,0 +1,15 @@
+FROM tangocs/tango-pytango
+
+USER root
+
+RUN apt-get update && apt-get install curl -y
+
+USER tango
+
+ADD ska-tango-grafana-exporter/exporter/code /code
+RUN pip install -r /code/pip-requirements.txt
+
+WORKDIR /code
+ENV PYTHONPATH '/code/'
+
+CMD ["python" , "/code/collector.py"]
diff --git a/docker-compose/tango-prometheus-exporter/LICENSE b/docker-compose/tango-prometheus-exporter/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..5e3270dc828a392391e2e6e8fac4e1a760d34b6a
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2020 INAF Matteo Di Carlo
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/docker-compose/tango-prometheus-exporter/Makefile b/docker-compose/tango-prometheus-exporter/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6f318981c2f1f3e28ec2dbcd856dd15cffe21116
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/Makefile
@@ -0,0 +1,6 @@
+NAME:=tango-exporter
+
+VERSION:=1.0.2
+TAG:=$(VERSION)
+
+include ../make/Makefile.mk
\ No newline at end of file
diff --git a/docker-compose/tango-prometheus-exporter/README b/docker-compose/tango-prometheus-exporter/README
new file mode 100644
index 0000000000000000000000000000000000000000..62ee6fc30cc4f0cac48f29ddd0d36e5ebea3ca8b
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/README
@@ -0,0 +1 @@
+Source: https://gitlab.com/ska-telescope/TANGO-grafana/-/tree/master/
diff --git a/docker-compose/tango-prometheus-exporter/get_metrics.sh b/docker-compose/tango-prometheus-exporter/get_metrics.sh
new file mode 100755
index 0000000000000000000000000000000000000000..0401a2564fbaf5e71c4b8c8ff971ea2f08fe62d2
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/get_metrics.sh
@@ -0,0 +1 @@
+curl $(kubectl get svc -n tango-grafana -o jsonpath='{.items[?(@.metadata.name=="tango-exporter-service-0")].spec.clusterIP}')/metrics
diff --git a/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter b/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter
new file mode 160000
index 0000000000000000000000000000000000000000..774d39a40ca19c9d979ad22565e57b4af3e9a831
--- /dev/null
+++ b/docker-compose/tango-prometheus-exporter/ska-tango-grafana-exporter
@@ -0,0 +1 @@
+Subproject commit 774d39a40ca19c9d979ad22565e57b4af3e9a831
diff --git a/jupyter-notebooks/PCC_archive_all_attributes.ipynb b/jupyter-notebooks/PCC_archive_all_attributes.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..df4b304d9d962adfc7eaeffd0afb073660c8829c
--- /dev/null
+++ b/jupyter-notebooks/PCC_archive_all_attributes.ipynb
@@ -0,0 +1,329 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "3191bdf1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys, time\n",
+    "import numpy as np\n",
+    "sys.path.append('/hosthome/tango/devices')\n",
+    "from toolkit.archiver import Archiver,Retriever\n",
+    "from toolkit.archiver_base import *\n",
+    "from matplotlib import pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "e2d12232",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "True\n"
+     ]
+    }
+   ],
+   "source": [
+    "from common.lofar_environment import isProduction\n",
+    "print(isProduction())"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "81e08b9f",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[]"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "archiver = Archiver()\n",
+    "archiver.get_subscriber_attributes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "884ff1ff",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "OFF\n"
+     ]
+    }
+   ],
+   "source": [
+    "device_name = 'LTS/PCC/1'\n",
+    "d=DeviceProxy(device_name) \n",
+    "state = str(d.state())\n",
+    "print(state)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "0f6e65b0",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Attribute lts/pcc/1/ant_mask_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_enable_pwr_r added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_i2c_status_r added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_pll_error_r added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_pll_locked_r added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_monitor_rate_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/clk_translator_busy_r added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_beamformer_delays_r added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_beamformer_delays_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_led_r added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_led_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_lna_pwr_r added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_lna_pwr_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_pwr_r added to archiving list!\n",
+      "Attribute lts/pcc/1/hba_element_pwr_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_adc_lock_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_attenuator_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_attenuator_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_band_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_band_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_i2c_status_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_id_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_led0_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_led0_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_led1_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_led1_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_mask_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_monitor_rate_rw added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_pwr_dig_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_temperature_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_translator_busy_r added to archiving list!\n",
+      "Attribute lts/pcc/1/rcu_version_r added to archiving list!\n",
+      "Device is now in ON state\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Start the device\n",
+    "if state == \"OFF\":\n",
+    "    if isProduction():\n",
+    "        archiver.add_attributes_to_archiver(device_name,global_archive_period=1000)\n",
+    "    else:\n",
+    "        archiver.remove_attributes_by_device(device_name)\n",
+    "    time.sleep(1)\n",
+    "    d.initialise()\n",
+    "    time.sleep(1)\n",
+    "state = str(d.state())\n",
+    "if state == \"STANDBY\":\n",
+    "    d.on()\n",
+    "state = str(d.state())\n",
+    "if state == \"ON\":\n",
+    "    print(\"Device is now in ON state\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "8efd3dc1",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "('tango://databaseds:10000/lts/pcc/1/ant_mask_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_enable_pwr_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_i2c_status_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_pll_error_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_pll_locked_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_monitor_rate_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_translator_busy_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_beamformer_delays_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_beamformer_delays_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_led_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_led_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_lna_pwr_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_lna_pwr_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_pwr_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/hba_element_pwr_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_adc_lock_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_attenuator_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_attenuator_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_band_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_band_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_i2c_status_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_id_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_led0_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_led0_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_led1_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_led1_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_mask_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_monitor_rate_rw',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_pwr_dig_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_temperature_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_translator_busy_r',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_version_r')"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "archiver.get_subscriber_attributes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "a1222d19",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'tango://databaseds:10000/lts/pcc/1/clk_enable_pwr_r': 'Read value for attribute CLK_Enable_PWR_R has not been updated',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_i2c_status_r': 'Read value for attribute CLK_I2C_STATUS_R has not been updated',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_pll_error_r': 'Read value for attribute CLK_PLL_error_R has not been updated',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_pll_locked_r': 'Read value for attribute CLK_PLL_locked_R has not been updated',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/clk_translator_busy_r': 'Read value for attribute CLK_translator_busy_R has not been updated',\n",
+       " 'tango://databaseds:10000/lts/pcc/1/rcu_version_r': 'Storing Error: mysql_stmt_bind_param() failed, err=Buffer type is not supported'}"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# Archiver managing methods\n",
+    "archiver.get_subscriber_errors()\n",
+    "\n",
+    "#e = archiver.get_attribute_errors('lts/pcc/1/rcu_temperature_r')\n",
+    "#print(e)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "174bbcdb",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1586.0 events/period  -> Number of archiving events per minute\n"
+     ]
+    }
+   ],
+   "source": [
+    "l = archiver.get_subscriber_load()\n",
+    "print(l,\" -> Number of archiving events per minute\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f060b0b6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#archiver.update_archiving_attribute('lts/pcc/1/rcu_pwr_dig_r')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f626d029",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Turn off the device\n",
+    "d.off()\n",
+    "\n",
+    "# Leave commented by default\n",
+    "archiver.remove_attributes_by_device(device_name)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "13c3b97d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Initialise the retriever object and print the archived attributes in the database\n",
+    "retriever = Retriever()\n",
+    "#retriever.get_all_archived_attributes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f176c20e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Retrieve records in the last n hours (works even with decimals)\n",
+    "\n",
+    "# Use alternatively one of the following two methods to retrieve data (last n hours or interval)\n",
+    "records= retriever.get_attribute_value_by_hours(attr_fq_name,hours=0.1)\n",
+    "#records = retriever.get_attribute_value_by_interval(attr_fq_name,'2021-09-01 16:00:00', '2021-09-01 16:03:00')\n",
+    "\n",
+    "if not records:\n",
+    "    print('Empty result!')\n",
+    "else:\n",
+    "    # Convert DB Array records into Python lists\n",
+    "    data = build_array_from_record(records,records[0].dim_x_r)\n",
+    "    # Extract only the value from the array \n",
+    "    array_values = get_values_from_record(data)\n",
+    "\n",
+    "#records\n",
+    "#data\n",
+    "#array_values"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "StationControl",
+   "language": "python",
+   "name": "stationcontrol"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.7.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/jupyter-notebooks/PCC_archive_attribute.ipynb b/jupyter-notebooks/PCC_archive_attribute.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..46ce0fafc3e4de5695bac0eda6d381330e99bb85
--- /dev/null
+++ b/jupyter-notebooks/PCC_archive_attribute.ipynb
@@ -0,0 +1,273 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "42e7f25a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys, time\n",
+    "import numpy as np\n",
+    "sys.path.append('/hosthome/tango/devices')\n",
+    "from toolkit.archiver import Archiver,Retriever\n",
+    "from toolkit.archiver_base import *\n",
+    "from matplotlib import pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1f025912",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from common.lofar_environment import isProduction\n",
+    "print(isProduction())"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e0656e2d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Define an attribute for archiving\n",
+    "device_name = 'LTS/PCC/1'\n",
+    "d=DeviceProxy(device_name) \n",
+    "state = str(d.state())\n",
+    "print(device_name,'is',state)\n",
+    "\n",
+    "archiver = Archiver()\n",
+    "\n",
+    "# Attribute chosen to be archived\n",
+    "attr_name = 'rcu_temperature_r'\n",
+    "attr_fq_name = str(device_name+'/'+attr_name).lower()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "153d9420",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Print the list of the attributes in the event subscriber\n",
+    "# If any attribute is present, its archiving will begin when device will reach ON state,\n",
+    "# Otherwise, attribute will be added to the list at the device initializing phase only in PRODUCTION mode\n",
+    "archiver.get_subscriber_attributes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2ebb00f8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Start the device\n",
+    "if state == \"OFF\":\n",
+    "    if isProduction():\n",
+    "        archiver.check_and_add_attribute_in_archiving_list(attr_fq_name)\n",
+    "    else:\n",
+    "        archiver.remove_attribute_from_archiver(attr_fq_name)\n",
+    "    time.sleep(1)\n",
+    "    d.initialise()\n",
+    "    time.sleep(1)\n",
+    "state = str(d.state())\n",
+    "if state == \"STANDBY\":\n",
+    "    d.on()\n",
+    "state = str(d.state())\n",
+    "if state == \"ON\":\n",
+    "    print(\"Device is now in ON state\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "75163627",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Modify attribute archiving features\n",
+    "archiver.update_archiving_attribute(attr_fq_name,polling_period=1000,event_period=5000,strategy='RUN')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7814715e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Add attribute to the archiving list (starts the archiving if device is running)\n",
+    "\n",
+    "# Archiving strategies are ['ALWAYS','RUN','SHUTDOWN','SERVICE']\n",
+    "#Read [0]\tALWAYS:always stored\n",
+    "#Read [1]\tRUN:stored during run\n",
+    "#Read [2]\tSHUTDOWN:stored during shutdown\n",
+    "#Read [3]\tSERVICE:stored during maintenance activities\n",
+    "\n",
+    "archiver.add_attribute_to_archiver(attr_fq_name, polling_period=1000, event_period=1000, strategy='RUN')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "52a27abb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Stop the attribute archiving but do not remove it from the list\n",
+    "# This means that archiving is stopped for the current session, but if the device is restarted, \n",
+    "# the attribute archiving will be restarted as well\n",
+    "# In order to definitely stop the archiving, the attribute must be removed from the attribute list (go to last cell)\n",
+    "archiver.stop_archiving_attribute(attr_fq_name)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c064e337",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Starts the attribute archiving if it was stopped\n",
+    "archiver.start_archiving_attribute(attr_fq_name)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d199916c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Initialise the retriever object and print the archived attributes in the database\n",
+    "retriever = Retriever()\n",
+    "retriever.get_all_archived_attributes()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "80e2a560",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Retrieve records in the last n hours (works even with decimals)\n",
+    "\n",
+    "# Use alternatively one of the following two methods to retrieve data (last n hours or interval)\n",
+    "records= retriever.get_attribute_value_by_hours(attr_fq_name,hours=0.1)\n",
+    "#records = retriever.get_attribute_value_by_interval(attr_fq_name,'2021-09-01 16:00:00', '2021-09-01 16:03:00')\n",
+    "\n",
+    "if not records:\n",
+    "    print('Empty result!')\n",
+    "else:\n",
+    "    # Convert DB Array records into Python lists\n",
+    "    data = build_array_from_record(records,records[0].dim_x_r)\n",
+    "    # Extract only the value from the array \n",
+    "    array_values = get_values_from_record(data)\n",
+    "\n",
+    "#records\n",
+    "#data\n",
+    "#array_values"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "64c8e060",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Extract and process timestamps for plotting purposes\n",
+    "def get_timestamps(data,strformat):\n",
+    "    timestamps = []\n",
+    "    for i in range(len(data)):\n",
+    "        timestamps.append(data[i][0].recv_time.strftime(strformat))\n",
+    "    return timestamps\n",
+    "timestamps = get_timestamps(data,\"%Y-%m-%d %X\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "59a0c05c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Plot of array values\n",
+    "\n",
+    "heatmap = np.array(array_values,dtype=np.float)\n",
+    "fig = plt.figure()\n",
+    "plt.rcParams['figure.figsize'] = [128, 64]\n",
+    "#plt.rcParams['figure.dpi'] = 128\n",
+    "ax = fig.add_subplot(111)\n",
+    "im = ax.imshow(heatmap, interpolation='nearest',cmap='coolwarm')\n",
+    "ax.set_xlabel('Array index')\n",
+    "ax.set_ylabel('Timestamp')\n",
+    "ax.set_xlim([0,(records[0].dim_x_r)-1])\n",
+    "ax.set_xticks(np.arange(0,records[0].dim_x_r))\n",
+    "\n",
+    "ax.set_yticks(range(0,len(timestamps)))\n",
+    "ax.set_yticklabels(timestamps,fontsize=4)\n",
+    "\n",
+    "# Comment the previous two lines and uncomment the following line if there are too many timestamp labels\n",
+    "#ax.set_yticks(range(0,len(timestamps),10))\n",
+    "\n",
+    "ax.set_title('Archived data for '+ attr_fq_name)\n",
+    "ax.grid()\n",
+    "cbar = fig.colorbar(ax=ax, mappable=im, orientation='horizontal')\n",
+    "plt.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1c753ed9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Count number of archive events per minute\n",
+    "archiver.get_subscriber_load()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a0e8dcab",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Turn off the device\n",
+    "d.off()\n",
+    "# Remove attribute from archiving list\n",
+    "#archiver.remove_attribute_from_archiver(attr_fq_name)\n",
+    "#archiver.remove_attributes_by_device(device_name)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "StationControl",
+   "language": "python",
+   "name": "stationcontrol"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.7.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/jupyter-notebooks/PCC_notebook.ipynb b/jupyter-notebooks/PCC_notebook.ipynb
index f0dd0f9bedae6261c0524b03898491b72eeac1b2..29b0744a5f3f7c692ef7cd6b148c1e5192e2e026 100644
--- a/jupyter-notebooks/PCC_notebook.ipynb
+++ b/jupyter-notebooks/PCC_notebook.ipynb
@@ -25,7 +25,15 @@
    "execution_count": 3,
    "id": "subjective-conference",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Device is now in on state\n"
+     ]
+    }
+   ],
    "source": [
     "state = str(d.state())\n",
     "\n",
@@ -47,20 +55,16 @@
    "metadata": {},
    "outputs": [
     {
-     "ename": "DevFailed",
-     "evalue": "DevFailed[\nDevError[\n    desc = Read value for attribute RCU_mask_RW has not been updated\n  origin = Device_3Impl::read_attributes_no_except\n  reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n    desc = Failed to read_attribute on device lts/pcc/1, attribute RCU_mask_RW\n  origin = DeviceProxy::read_attribute()\n  reason = API_AttributeFailed\nseverity = ERR]\n]",
+     "ename": "AttributeError",
+     "evalue": "RCU_ADC_SYNC_R",
      "output_type": "error",
      "traceback": [
       "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mDevFailed\u001b[0m                                 Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-4-aafae2adcd98>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m values = [[d.RCU_mask_RW, \"RCU_mask_RW\"],\n\u001b[0m\u001b[1;32m      2\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAnt_mask_RW\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"Ant_mask_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_attenuator_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_attenuator_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_attenuator_RW\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_attenuator_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_band_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_band_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__getattr\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m    342\u001b[0m     \u001b[0mattr_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_attr_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname_l\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    343\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 344\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    345\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    346\u001b[0m     \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__get_attribute_value\u001b[0;34m(self, attr_info, name)\u001b[0m\n\u001b[1;32m    281\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    282\u001b[0m     \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menum_class\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 283\u001b[0;31m     \u001b[0mattr_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    284\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    285\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mattr_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mgreener\u001b[0;34m(obj, *args, **kwargs)\u001b[0m\n\u001b[1;32m    193\u001b[0m             \u001b[0mgreen_mode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0maccess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'green_mode'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    194\u001b[0m             \u001b[0mexecutor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_object_executor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgreen_mode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 195\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mexecutor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    196\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    197\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mgreener\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, fn, args, kwargs, wait, timeout)\u001b[0m\n\u001b[1;32m    107\u001b[0m         \u001b[0;31m# Sychronous (no delegation)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    108\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0masynchronous\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0min_executor_context\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 109\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    110\u001b[0m         \u001b[0;31m# Asynchronous delegation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    111\u001b[0m         \u001b[0maccessor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__read_attribute\u001b[0;34m(self, value, extract_as)\u001b[0m\n\u001b[1;32m    439\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    440\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__DeviceProxy__read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mExtractAs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumpy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 441\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    442\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    443\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__check_read_attribute\u001b[0;34m(dev_attr)\u001b[0m\n\u001b[1;32m    155\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    156\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhas_failed\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 157\u001b[0;31m         \u001b[0;32mraise\u001b[0m \u001b[0mDevFailed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_err_stack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    158\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mDevFailed\u001b[0m: DevFailed[\nDevError[\n    desc = Read value for attribute RCU_mask_RW has not been updated\n  origin = Device_3Impl::read_attributes_no_except\n  reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n    desc = Failed to read_attribute on device lts/pcc/1, attribute RCU_mask_RW\n  origin = DeviceProxy::read_attribute()\n  reason = API_AttributeFailed\nseverity = ERR]\n]"
+      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
+      "\u001b[0;32m<ipython-input-4-aafae2adcd98>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m     10\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_LED0_RW\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_LED0_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_ADC_lock_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_ADC_lock_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 12\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_ADC_SYNC_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_ADC_SYNC_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     13\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_ADC_JESD_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_ADC_JESD_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     14\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mRCU_ADC_CML_R\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\"RCU_ADC_CML_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__getattr\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m    353\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_pipe\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    354\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 355\u001b[0;31m     \u001b[0msix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mraise_from\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mAttributeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcause\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    356\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    357\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/six.py\u001b[0m in \u001b[0;36mraise_from\u001b[0;34m(value, from_value)\u001b[0m\n",
+      "\u001b[0;31mAttributeError\u001b[0m: RCU_ADC_SYNC_R"
      ]
     }
    ],
diff --git a/jupyter-notebooks/UNB2_notebook.ipynb b/jupyter-notebooks/UNB2_notebook.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..3e87179f3a0f0ef951da6a078ba5df3610a6696d
--- /dev/null
+++ b/jupyter-notebooks/UNB2_notebook.ipynb
@@ -0,0 +1,483 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "waiting-chance",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import time"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "moving-alexandria",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "d=DeviceProxy(\"LTS/UNB2/1\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "ranking-aluminum",
+   "metadata": {
+    "scrolled": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Device is now in on state\n"
+     ]
+    }
+   ],
+   "source": [
+    "state = str(d.state())\n",
+    "\n",
+    "\n",
+    "if state == \"OFF\" or state == \"FAULT\":\n",
+    "    d.initialise()\n",
+    "    time.sleep(1)\n",
+    "state = str(d.state())\n",
+    "if state == \"STANDBY\":\n",
+    "    d.on()\n",
+    "state = str(d.state())\n",
+    "if state == \"ON\":\n",
+    "    print(\"Device is now in on state\")\n",
+    "else:\n",
+    "    print(\"warning, expected device to be in on state, is: \", state)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "0caa8146",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "version_R *L2SS-268-LR1_2_Read_hardware_status_of_UB2c_from_SDPHW [1007a5c5462b1aa3e8f81268f890f6c058413218]\n",
+      "UNB2_Power_ON_OFF_RW [False False]\n",
+      "UNB2_Front_Panel_LED_RW [0 0]\n",
+      "UNB2_Front_Panel_LED_R [0 0]\n",
+      "UNB2_mask_RW [False False]\n",
+      "UNB2_I2C_bus_STATUS_R [False False]\n",
+      "UNB2_EEPROM_Unique_ID_R [5947666 5947666]\n",
+      "UNB2_DC_DC_48V_12V_VIN_R [29.18505859 29.18505859]\n",
+      "UNB2_DC_DC_48V_12V_VOUT_R [12.00146484 11.98486328]\n",
+      "UNB2_DC_DC_48V_12V_IOUT_R [3.625 3.625]\n",
+      "UNB2_DC_DC_48V_12V_TEMP_R [37. 37.]\n",
+      "UNB2_POL_QSFP_N01_VOUT_R [3.28686523 3.28686523]\n",
+      "UNB2_POL_QSFP_N01_IOUT_R [1.55078125 1.55078125]\n",
+      "UNB2_POL_QSFP_N01_TEMP_R [33.75 33.75]\n",
+      "UNB2_POL_QSFP_N23_VOUT_R [3.28710938 3.28710938]\n",
+      "UNB2_POL_QSFP_N23_IOUT_R [1.25195312 1.25195312]\n",
+      "UNB2_POL_QSFP_N23_TEMP_R [40.625 40.625]\n",
+      "UNB2_POL_SWITCH_1V2_VOUT_R [1.19970703 1.19970703]\n",
+      "UNB2_POL_SWITCH_1V2_IOUT_R [1.73632812 1.73632812]\n",
+      "UNB2_POL_SWITCH_1V2_TEMP_R [45.125 45.125]\n",
+      "UNB2_POL_SWITCH_PHY_VOUT_R [1.00024414 1.00024414]\n",
+      "UNB2_POL_SWITCH_PHY_IOUT_R [0.52050781 0.52050781]\n",
+      "UNB2_POL_SWITCH_PHY_TEMP_R [46.1875 46.1875]\n",
+      "UNB2_POL_CLOCK_VOUT_R [2.49951172 2.49951172]\n",
+      "UNB2_POL_CLOCK_IOUT_R [0.94042969 0.94042969]\n",
+      "UNB2_POL_CLOCK_TEMP_R [42.875 42.875]\n",
+      "UNB2_FPGA_DDR4_SLOT_TEMP_R [[27.5  27.5  29.25 27.75 28.75 29.25 28.5  28.5 ]\n",
+      " [27.5  27.5  29.25 27.75 28.75 29.25 28.5  28.5 ]]\n",
+      "UNB2_FPGA_POL_CORE_IOUT_R [[5.921875   4.109375   3.76171875 3.55859375]\n",
+      " [5.921875   4.1015625  3.76171875 3.55859375]]\n",
+      "UNB2_FPGA_POL_CORE_TEMP_R [[30.84375 31.46875 32.4375  34.75   ]\n",
+      " [30.84375 31.5     32.375   34.6875 ]]\n",
+      "UNB2_FPGA_POL_ERAM_VOUT_R [[0.8996582  0.90014648 0.90014648 0.8996582 ]\n",
+      " [0.8996582  0.8996582  0.90014648 0.8996582 ]]\n",
+      "UNB2_FPGA_POL_ERAM_IOUT_R [[0.08764648 0.0880127  0.18725586 0.08703613]\n",
+      " [0.02593994 0.0880127  0.18725586 0.08703613]]\n",
+      "UNB2_FPGA_POL_ERAM_TEMP_R [[38.75   39.25   41.     41.4375]\n",
+      " [38.75   39.25   41.     41.4375]]\n",
+      "UNB2_FPGA_POL_RXGXB_VOUT_R [[0.90014648 0.89990234 0.90014648 0.90014648]\n",
+      " [0.90014648 0.89990234 0.90014648 0.90014648]]\n",
+      "UNB2_FPGA_POL_RXGXB_IOUT_R [[0.49755859 0.41113281 0.40234375 0.48876953]\n",
+      " [0.49755859 0.41113281 0.40234375 0.48876953]]\n",
+      "UNB2_FPGA_POL_RXGXB_TEMP_R [[34.75   38.0625 36.5    38.1875]\n",
+      " [34.75   38.0625 36.5    38.1875]]\n",
+      "UNB2_FPGA_POL_TXGXB_VOUT_R [[0.89990234 0.90014648 0.90014648 0.89990234]\n",
+      " [0.89990234 0.90014648 0.90014648 0.89990234]]\n",
+      "UNB2_FPGA_POL_TXGXB_IOUT_R [[0.17480469 0.12219238 0.06433105 0.13110352]\n",
+      " [0.17480469 0.12219238 0.06433105 0.13110352]]\n",
+      "UNB2_FPGA_POL_HGXB_VOUT_R [[1.80004883 1.79956055 1.79980469 1.79980469]\n",
+      " [1.80004883 1.79956055 1.79980469 1.79980469]]\n",
+      "UNB2_FPGA_POL_HGXB_IOUT_R [[0.67089844 0.76269531 0.80664062 0.7265625 ]\n",
+      " [0.67089844 0.76269531 0.80664062 0.7265625 ]]\n",
+      "UNB2_FPGA_POL_HGXB_TEMP_R [[40.375  41.8125 44.3125 40.625 ]\n",
+      " [40.375  41.8125 44.3125 40.625 ]]\n",
+      "UNB2_FPGA_POL_PGM_VOUT_R [[1.80029297 1.80004883 1.79931641 1.80029297]\n",
+      " [1.80029297 1.80004883 1.79931641 1.80029297]]\n",
+      "UNB2_FPGA_POL_PGM_IOUT_R [[0.13818359 0.17089844 0.31542969 0.10656738]\n",
+      " [0.1550293  0.17089844 0.31542969 0.10656738]]\n",
+      "UNB2_FPGA_POL_PGM_TEMP_R [[40.0625 42.1875 44.3125 40.3125]\n",
+      " [40.0625 42.1875 44.3125 40.3125]]\n",
+      "State <function __get_command_func.<locals>.f at 0x7f636d295510>\n",
+      "Status <function __get_command_func.<locals>.f at 0x7f636d295510>\n"
+     ]
+    }
+   ],
+   "source": [
+    "attr_names = d.get_attribute_list()\n",
+    "\n",
+    "\n",
+    "for i in attr_names:\n",
+    "    exec(\"value = print(i, d.{})\".format(i))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 78,
+   "id": "929965c2",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Old values:\n",
+      " [0 0]\n",
+      "Values to be set:\n",
+      " [1 3]\n",
+      "Values set in RW:\n",
+      " [1 3]\n",
+      "Values read back after setting:\n",
+      " [0 0]\n"
+     ]
+    }
+   ],
+   "source": [
+    "#Test the LED CP\n",
+    "led = d.UNB2_Front_Panel_LED_R\n",
+    "print(\"Old values:\\n\",  led)\n",
+    "led[0] = 1\n",
+    "led[1] = 3\n",
+    "print(\"Values to be set:\\n\", led)\n",
+    "d.UNB2_Front_Panel_LED_RW = led\n",
+    "print(\"Values set in RW:\\n\",d.UNB2_Front_Panel_LED_RW)\n",
+    "print(\"Values read back after setting:\\n\",d.UNB2_Front_Panel_LED_R)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 82,
+   "id": "6813164e",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Old values:\n",
+      " [False False]\n",
+      "Values to be set:\n",
+      " [False False]\n",
+      "Values set in RW:\n",
+      " [False False]\n"
+     ]
+    }
+   ],
+   "source": [
+    "#Test the ON OFF CP\n",
+    "onoff = d.UNB2_Power_ON_OFF_RW\n",
+    "print(\"Old values:\\n\",  onoff)\n",
+    "onoff[0] = False\n",
+    "onoff[1] = False\n",
+    "print(\"Values to be set:\\n\", onoff)\n",
+    "d.UNB2_Power_ON_OFF_RW = onoff\n",
+    "print(\"Values set in RW:\\n\",d.UNB2_Power_ON_OFF_RW)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 74,
+   "id": "e9b32ec7",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Old values:\n",
+      " [ True  True]\n",
+      "Values to be set:\n",
+      " [False False]\n",
+      "Values read back after setting:\n",
+      " [False False]\n"
+     ]
+    }
+   ],
+   "source": [
+    "#Test the MASK CP\n",
+    "mask = d.UNB2_mask_RW\n",
+    "print(\"Old values:\\n\",  mask)\n",
+    "mask[0] = False\n",
+    "mask[1] = False\n",
+    "print(\"Values to be set:\\n\", mask)\n",
+    "d.UNB2_mask_RW = mask\n",
+    "print(\"Values read back after setting:\\n\",d.UNB2_mask_RW)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "transsexual-battle",
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "DevFailed",
+     "evalue": "DevFailed[\nDevError[\n    desc = Read value for attribute FPGA_mask_RW has not been updated\n  origin = Device_3Impl::read_attributes_no_except\n  reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n    desc = Failed to read_attribute on device lts/sdp/1, attribute FPGA_mask_RW\n  origin = DeviceProxy::read_attribute()\n  reason = API_AttributeFailed\nseverity = ERR]\n]",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mDevFailed\u001b[0m                                 Traceback (most recent call last)",
+      "\u001b[0;32m/tmp/ipykernel_22/2885399456.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m      1\u001b[0m values = [\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFPGA_mask_RW\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"FPGA_mask_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m     \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFPGA_scrap_R\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"FPGA_scrap_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFPGA_scrap_RW\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"FPGA_scrap_RW\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0;34m[\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mFPGA_status_R\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"FPGA_status_R\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__getattr\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m    319\u001b[0m     \u001b[0mattr_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_attr_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname_l\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    320\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 321\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    322\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    323\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mname_l\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__get_pipe_cache\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__get_attribute_value\u001b[0;34m(self, attr_info, name)\u001b[0m\n\u001b[1;32m    281\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__get_attribute_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    282\u001b[0m     \u001b[0m_\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0menum_class\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mattr_info\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 283\u001b[0;31m     \u001b[0mattr_value\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    284\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    285\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0menum_class\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mattr_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mgreener\u001b[0;34m(obj, *args, **kwargs)\u001b[0m\n\u001b[1;32m    193\u001b[0m             \u001b[0mgreen_mode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0maccess\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'green_mode'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    194\u001b[0m             \u001b[0mexecutor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_object_executor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgreen_mode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 195\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mexecutor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwait\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    196\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    197\u001b[0m         \u001b[0;32mreturn\u001b[0m \u001b[0mgreener\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/green.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, fn, args, kwargs, wait, timeout)\u001b[0m\n\u001b[1;32m    107\u001b[0m         \u001b[0;31m# Sychronous (no delegation)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    108\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0masynchronous\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0min_executor_context\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 109\u001b[0;31m             \u001b[0;32mreturn\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    110\u001b[0m         \u001b[0;31m# Asynchronous delegation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    111\u001b[0m         \u001b[0maccessor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__DeviceProxy__read_attribute\u001b[0;34m(self, value, extract_as)\u001b[0m\n\u001b[1;32m    439\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    440\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__DeviceProxy__read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mExtractAs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumpy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 441\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mextract_as\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    442\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    443\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/tango/device_proxy.py\u001b[0m in \u001b[0;36m__check_read_attribute\u001b[0;34m(dev_attr)\u001b[0m\n\u001b[1;32m    155\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__check_read_attribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    156\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhas_failed\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 157\u001b[0;31m         \u001b[0;32mraise\u001b[0m \u001b[0mDevFailed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mdev_attr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_err_stack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    158\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mdev_attr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+      "\u001b[0;31mDevFailed\u001b[0m: DevFailed[\nDevError[\n    desc = Read value for attribute FPGA_mask_RW has not been updated\n  origin = Device_3Impl::read_attributes_no_except\n  reason = API_AttrValueNotSet\nseverity = ERR]\n\nDevError[\n    desc = Failed to read_attribute on device lts/sdp/1, attribute FPGA_mask_RW\n  origin = DeviceProxy::read_attribute()\n  reason = API_AttributeFailed\nseverity = ERR]\n]"
+     ]
+    }
+   ],
+   "source": [
+    "values = [\n",
+    "    [d.FPGA_mask_RW, \"FPGA_mask_RW\"],\n",
+    "    [d.FPGA_scrap_R, \"FPGA_scrap_R\"],\n",
+    "    [d.FPGA_scrap_RW, \"FPGA_scrap_RW\"],\n",
+    "    [d.FPGA_status_R, \"FPGA_status_R\"],\n",
+    "    [d.FPGA_temp_R, \"FPGA_temp_R\"],\n",
+    "    [d.FPGA_version_R, \"FPGA_version_R\"],\n",
+    "    [d.FPGA_weights_R, \"FPGA_weights_R\"],\n",
+    "    [d.FPGA_weights_RW, \"FPGA_weights_RW\"],\n",
+    "    [d.TR_busy_R, \"TR_busy_R\"],\n",
+    "    [d.TR_reload_RW, \"TR_reload_RW\"],\n",
+    "    # [d.TR_tod_R, \"TR_tod_R\"],\n",
+    "    # [d.TR_uptime_R, \"TR_uptime_R\"]\n",
+    "]\n",
+    "\n",
+    "for i in values:\n",
+    "    print(\"🟦🟦🟦\", i[1], \": \", i[0])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "id": "b88868c5",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
+       "       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n",
+       "       [1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n",
+       "       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],\n",
+       "      dtype=float32)"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wgswitches = d.FPGA_wg_enable_R\n",
+    "print(\"Old values:\\n\",  wgswitches)\n",
+    "wgswitches[9][0] = True\n",
+    "wgswitches[10][0] = True\n",
+    "print(\"Values to be set:\\n\", wgswitches)\n",
+    "d.FPGA_wg_enable_RW =wgswitches\n",
+    "time.sleep(7)\n",
+    "print(\"Values read back after setting:\\n\",d.FPGA_wg_enable_R)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "id": "8f3db8c7",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([[119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n",
+       "        119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n",
+       "        119.99817, 119.99817, 119.99817, 119.99817],\n",
+       "       [119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n",
+       "        119.99817, 119.99817, 119.99817, 119.99817, 119.99817, 119.99817,\n",
+       "        119.99817, 119.99817, 119.99817, 119.99817],\n",
+       "       [119.99817, 119.99817, 119.99817, 119.99817,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ],\n",
+       "       [  0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ,   0.     ,   0.     ,\n",
+       "          0.     ,   0.     ,   0.     ,   0.     ]], dtype=float32)"
+      ]
+     },
+     "execution_count": 18,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "phases = d.FPGA_wg_phase_R\n",
+    "print(\"Old values:\\n\",  phases)\n",
+    "phases[9][0] = 1.0334\n",
+    "phases[9][1] = 20.15\n",
+    "phases[10][0] = 130\n",
+    "print(\"Values to be set:\\n\", phases)\n",
+    "d.FPGA_wg_phase_RW = phases\n",
+    "time.sleep(7)\n",
+    "print(\"Values read back after setting:\\n\", d.FPGA_wg_phase_R)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "e45b4874",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([[29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n",
+       "        29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n",
+       "        29921878., 29921878., 29921878., 29921878.],\n",
+       "       [29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n",
+       "        29921878., 29921878., 29921878., 29921878., 29921878., 29921878.,\n",
+       "        29921878., 29921878., 29921878., 29921878.],\n",
+       "       [29921878., 29921878., 29921878., 29921878.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.],\n",
+       "       [       0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.,        0.,        0.,\n",
+       "               0.,        0.,        0.,        0.]], dtype=float32)"
+      ]
+     },
+     "execution_count": 13,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "amplitudes = d.FPGA_wg_amplitude_R\n",
+    "print(\"Old values:\\n\",  amplitudes)\n",
+    "amplitudes[9][0] = 1.0\n",
+    "amplitudes[9][1] = 1.99\n",
+    "amplitudes[10][0] = 0.5\n",
+    "print(\"Values to be set:\\n\", amplitudes)\n",
+    "d.FPGA_wg_amplitude_RW = amplitudes\n",
+    "time.sleep(7)\n",
+    "print(\"Values read back after setting:\\n\", d.FPGA_wg_amplitude_R)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9b1bbd3e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "frequencies = d.FPGA_wg_frequency_R\n",
+    "print(\"Old values:\\n\",  frequencies)\n",
+    "frequencies[9][0] = 19000000\n",
+    "frequencies[9][1] = 20000000\n",
+    "frequencies[10][0] = 22000000\n",
+    "print(\"Values to be set:\\n\", frequencies)\n",
+    "d.FPGA_wg_frequency_RW = frequencies\n",
+    "print(\"Values read back after setting:\\n\", d.FPGA_wg_frequency_R)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "StationControl",
+   "language": "python",
+   "name": "stationcontrol"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.7.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh
index 636e0945abd8b220a842019b250e56871fabc145..073c87fde20611666004539da9f124798c50ceb8 100755
--- a/sbin/run_integration_test.sh
+++ b/sbin/run_integration_test.sh
@@ -34,4 +34,4 @@ cd "$LOFAR20_DIR/docker-compose" || exit 1
 make start integration-test
 
 # Run the integration test with the output displayed on stdout
-docker start -a integration-test
\ No newline at end of file
+docker start -a integration-test