From 855cb03ce7da4287475a3b25f7203d2b7814d307 Mon Sep 17 00:00:00 2001
From: Stefano Di Frischia <stefano.difrischia@inaf.it>
Date: Tue, 30 May 2023 13:48:29 +0000
Subject: [PATCH] Resolve L2SS-1272 "Sdp firmware device"

---
 CDB/LOFAR_ConfigDb.json                       |  11 +
 CDB/hierarchies/power.json                    |  20 +-
 .../digitalbeam_cluster_ConfigDb.json         |  32 +++
 CDB/stations/CS001_ConfigDb.json              |  57 +++--
 CDB/stations/DTS_ConfigDb.json                | 104 +++++----
 CDB/stations/DTS_Outside_ConfigDb.json        |  55 +++--
 CDB/stations/LTS_ConfigDb.json                |  25 ++-
 CDB/stations/README.md                        |   2 +-
 CDB/stations/simulators_ConfigDb.json         |  19 ++
 docker-compose/device-sdpfirmware.yml         |  55 +++++
 .../startup/01-devices.py                     |   4 +
 .../lofar2-fast-policy.json                   |   2 +
 .../lofar2-policy.json                        |   6 +-
 jupyter-notebooks/DEMO_Firmware.ipynb         | 202 ++++++++++++++++++
 sbin/run_integration_test.sh                  |   4 +-
 .../docs/source/configure_station.rst         |   4 +
 .../docs/source/devices/sdp.rst               |  18 +-
 .../docs/source/devices/sdpfirmware.rst       |  34 +++
 tangostationcontrol/docs/source/faq.rst       |   2 +-
 tangostationcontrol/docs/source/index.rst     |   1 +
 tangostationcontrol/setup.cfg                 |   1 +
 .../tangostationcontrol/devices/README.md     |   6 +-
 .../tangostationcontrol/devices/boot.py       |   1 +
 .../tangostationcontrol/devices/docker.py     |   8 +
 .../devices/sdp/beamlet.py                    |   5 +-
 .../tangostationcontrol/devices/sdp/bst.py    |   4 +-
 .../devices/sdp/firmware.py                   | 146 +++++++++++++
 .../tangostationcontrol/devices/sdp/sdp.py    |  96 ++-------
 .../tangostationcontrol/devices/sdp/sst.py    |   4 +-
 .../devices/sdp/statistics.py                 |  21 +-
 .../tangostationcontrol/devices/sdp/xst.py    |   4 +-
 .../configDB/simulators_ConfigDb.json         |  19 ++
 .../interfaces/test_power_hierarchy.py        |  32 ++-
 .../devices/test_device_antennafield.py       |   8 +
 .../devices/test_device_calibration.py        |   8 +
 .../devices/test_device_digitalbeam.py        |  14 ++
 .../devices/test_device_observation.py        |   8 +
 .../test_device_observation_control.py        |   8 +
 .../default/devices/test_device_sdp.py        |  11 -
 .../devices/test_device_sdpfirmware.py        |  21 ++
 .../default/devices/test_device_sst.py        |   9 +
 .../test_device_temperature_manager.py        |  10 +
 .../test_digitalbeam_performance.py           |   8 +-
 43 files changed, 883 insertions(+), 226 deletions(-)
 create mode 100644 docker-compose/device-sdpfirmware.yml
 create mode 100644 jupyter-notebooks/DEMO_Firmware.ipynb
 create mode 100644 tangostationcontrol/docs/source/devices/sdpfirmware.rst
 create mode 100644 tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
 create mode 100644 tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py

diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json
index 13faba11e..a237a7981 100644
--- a/CDB/LOFAR_ConfigDb.json
+++ b/CDB/LOFAR_ConfigDb.json
@@ -143,6 +143,7 @@
                 "RCU2L, RECV_TEMP_error_R"
               ],
               "Shutdown_Device_List": [
+                "STAT/SDPFirmware/1",
                 "STAT/SDP/1",
                 "STAT/UNB2/1",
                 "STAT/RCU2H/1",
@@ -319,6 +320,16 @@
         }
       }
     },
+    "SDPFirmware": {
+      "STAT": {
+        "SDPFirmware": {
+          "STAT/SDPFirmware/1": {
+            "properties": {
+            }
+          }
+        }
+      }
+    },
     "SDP": {
       "STAT": {
         "SDP": {
diff --git a/CDB/hierarchies/power.json b/CDB/hierarchies/power.json
index 240ff71fc..e273364de 100644
--- a/CDB/hierarchies/power.json
+++ b/CDB/hierarchies/power.json
@@ -28,7 +28,7 @@
                 ],
                 "Power_Children": [
                   "STAT/CCD/1",
-                  "STAT/SDP/1"
+                  "STAT/SDPFirmware/1"
                 ]
               }
             }
@@ -136,13 +136,29 @@
           }
         }
       },
+      "SDPFirmware": {
+        "STAT": {
+          "SDPFirmware": {
+            "STAT/SDPFirmware/1": {
+              "properties": {
+                "Power_Parent": [
+                    "STAT/PSOC/1"
+                ],
+                "Power_Children": [
+                    "STAT/SDP/1"
+                ]
+              }
+            }
+          }
+        }
+      },
       "SDP": {
         "STAT": {
           "SDP": {
             "STAT/SDP/1": {
               "properties": {
                 "Power_Parent": [
-                    "STAT/PSOC/1"
+                    "STAT/SDPFirmware/1"
                 ],
                 "Power_Children": [
                     "STAT/Beamlet/1",
diff --git a/CDB/integrations/digitalbeam_cluster_ConfigDb.json b/CDB/integrations/digitalbeam_cluster_ConfigDb.json
index 18f410cef..1ba01d270 100644
--- a/CDB/integrations/digitalbeam_cluster_ConfigDb.json
+++ b/CDB/integrations/digitalbeam_cluster_ConfigDb.json
@@ -1,5 +1,37 @@
 {
     "servers": {
+        "SDPFirmware":{
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1":{
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    },
+                    "STAT/SDPFirmware/2":{
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
diff --git a/CDB/stations/CS001_ConfigDb.json b/CDB/stations/CS001_ConfigDb.json
index 6f22e81df..92ec6ea53 100644
--- a/CDB/stations/CS001_ConfigDb.json
+++ b/CDB/stations/CS001_ConfigDb.json
@@ -242,7 +242,7 @@
                                 "10010",
                                 "10010",
                                 "10010"
-                            ],
+                            ]
                         }
                     }
                 }
@@ -644,6 +644,43 @@
                 }
             }
         },
+        "SDPFirmware":{
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1":{
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "10.99.0.250"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "TR_fpga_mask_RW_default": [
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
@@ -675,24 +712,6 @@
                                 "903",
                                 "903",
                                 "903"
-                            ],
-                            "TR_fpga_mask_RW_default": [
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False"
                             ]
                         }
                     }
diff --git a/CDB/stations/DTS_ConfigDb.json b/CDB/stations/DTS_ConfigDb.json
index 921c3e3a3..2db2c9f1b 100644
--- a/CDB/stations/DTS_ConfigDb.json
+++ b/CDB/stations/DTS_ConfigDb.json
@@ -611,6 +611,74 @@
                 }
             }
         },
+        "SDPFirmware":{
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/LBA":{
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "10.99.0.250"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "TR_fpga_mask_RW_default": [
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True"
+                            ]
+                        }
+                    },
+                    "STAT/SDPFirmware/HBA":{
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "10.99.0.250"
+                            ],
+                            "OPC_Server_Port": [
+                                "4842"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "TR_fpga_mask_RW_default": [
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
@@ -642,24 +710,6 @@
                                 "902",
                                 "902",
                                 "902"
-                            ],
-                            "TR_fpga_mask_RW_default": [
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True"
                             ]
                         }
                     },
@@ -691,24 +741,6 @@
                                 "902",
                                 "902",
                                 "902"
-                            ],
-                            "TR_fpga_mask_RW_default": [
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False"
                             ]
                         }
                     }
diff --git a/CDB/stations/DTS_Outside_ConfigDb.json b/CDB/stations/DTS_Outside_ConfigDb.json
index b709db966..c94f67afd 100644
--- a/CDB/stations/DTS_Outside_ConfigDb.json
+++ b/CDB/stations/DTS_Outside_ConfigDb.json
@@ -373,6 +373,43 @@
                 }
             }
         },
+        "SDPFirmware":{
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "10.99.0.250"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ],
+                            "TR_fpga_mask_RW_default": [
+                                "True",
+                                "True",
+                                "True",
+                                "True",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False",
+                                "False"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
@@ -404,24 +441,6 @@
                                 "903",
                                 "903",
                                 "903"
-                            ],
-                            "TR_fpga_mask_RW_default": [
-                                "True",
-                                "True",
-                                "True",
-                                "True",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False",
-                                "False"
                             ]
                         }
                     }
diff --git a/CDB/stations/LTS_ConfigDb.json b/CDB/stations/LTS_ConfigDb.json
index e23cf5466..3b750f8a5 100644
--- a/CDB/stations/LTS_ConfigDb.json
+++ b/CDB/stations/LTS_ConfigDb.json
@@ -144,10 +144,10 @@
                 }
             }
         },
-        "SDP": {
+        "SDPFirmware":{
             "STAT": {
-                "SDP": {
-                    "STAT/SDP/1": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1": {
                         "properties": {
                             "OPC_Server_Name": [
                                 "dop369.astron.nl"
@@ -163,6 +163,25 @@
                                 "False", "False", "False", "False",
                                 "False", "False", "False",  "True",
                                 "False", "False", "False", "False"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "SDP": {
+            "STAT": {
+                "SDP": {
+                    "STAT/SDP/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "dop369.astron.nl"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
                             ],
                             "FPGA_sdp_info_station_id_RW_default": [
                                 "901",
diff --git a/CDB/stations/README.md b/CDB/stations/README.md
index ff2223891..9bc38762e 100644
--- a/CDB/stations/README.md
+++ b/CDB/stations/README.md
@@ -7,7 +7,7 @@ These are the required changes that need to be made in order to support multiple
 
 	- add all the new devices under the "STAT/DEVICE/2" name with the correct IP addresses, ports and other properties. This includes: APSCT, APSPU, RECV and UNB2.
 	- Put the new devices in the "Device_Names" list for the boot devices
-	- Set the first 8 bools in "STAT/SDP/1" ... "TR_fpga_mask_RW_default" to True (instead of the first 4 with 1 subrack). This enables the new FPGA's
+	- Set the first 8 bools in "STAT/SDPFirmware/1" ... "TR_fpga_mask_RW_default" to True (instead of the first 4 with 1 subrack). This enables the new FPGA's
 	
 	
 
diff --git a/CDB/stations/simulators_ConfigDb.json b/CDB/stations/simulators_ConfigDb.json
index 89aa897e7..f76dd050d 100644
--- a/CDB/stations/simulators_ConfigDb.json
+++ b/CDB/stations/simulators_ConfigDb.json
@@ -183,6 +183,25 @@
                 }
             }
         },
+        "SDPFirmware": {
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
diff --git a/docker-compose/device-sdpfirmware.yml b/docker-compose/device-sdpfirmware.yml
new file mode 100644
index 000000000..b643bb0b0
--- /dev/null
+++ b/docker-compose/device-sdpfirmware.yml
@@ -0,0 +1,55 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+#
+# 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.1'
+
+services:
+  device-sdpfirmware:
+    image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/lofar-device-base
+    hostname: device-sdpfirmware
+    container_name: device-sdpfirmware
+    logging:
+      driver: "json-file"
+      options:
+        max-size: "100m"
+        max-file: "10"
+    networks:
+      - control
+    ports:
+      - "5727:5727" # unique port for this DS
+      - "5827:5827" # ZeroMQ event port
+      - "5927:5927" # ZeroMQ heartbeat port
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    volumes:
+      - ..:/opt/lofar/tango:rw
+    environment:
+      - TANGO_HOST=${TANGO_HOST}
+      - TANGO_ZMQ_EVENT_PORT=5827
+      - TANGO_ZMQ_HEARTBEAT_PORT=5927
+    healthcheck:
+      test: l2ss-health STAT/SDPFirmware/1
+      interval: 1m
+      timeout: 30s
+      retries: 3
+      start_period: 30s
+    working_dir: /opt/lofar/tango
+    entrypoint:
+      - bin/start-ds.sh
+      # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA
+      # can't know about our Docker port forwarding
+      - l2ss-sdpfirmware SDPFirmware STAT -v -ORBendPoint giop:tcp:device-sdpfirmware:5727 -ORBendPointPublish giop:tcp:${HOSTNAME}:5727
+    restart: unless-stopped
+    stop_signal: SIGINT # request a graceful shutdown of Tango
+    stop_grace_period: 2s
diff --git a/docker-compose/jupyterlab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyterlab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
index 065d12a2d..d84d4b201 100644
--- a/docker-compose/jupyterlab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
+++ b/docker-compose/jupyterlab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py
@@ -30,6 +30,7 @@ unb2_l1 = OptionalDeviceProxy("STAT/UNB2/L1")
 unb2_h0 = OptionalDeviceProxy("STAT/UNB2/H0")
 unb2s = [unb2_l0, unb2_l1, unb2_h0]
 
+sdpfirmware_l = OptionalDeviceProxy("STAT/SDPFirmware/LBA")
 sdp_l = OptionalDeviceProxy("STAT/SDP/LBA")
 bst_l = OptionalDeviceProxy("STAT/BST/LBA")
 sst_l = OptionalDeviceProxy("STAT/SST/LBA")
@@ -38,6 +39,7 @@ beamlet_l = OptionalDeviceProxy("STAT/Beamlet/LBA")
 digitalbeam_l = OptionalDeviceProxy("STAT/DigitalBeam/LBA")
 antennafield_l = OptionalDeviceProxy("STAT/AntennaField/LBA")
 
+sdpfirmware_h = OptionalDeviceProxy("STAT/SDPFirmware/HBA")
 sdp_h = OptionalDeviceProxy("STAT/SDP/HBA")
 bst_h = OptionalDeviceProxy("STAT/BST/HBA")
 sst_h = OptionalDeviceProxy("STAT/SST/HBA")
@@ -67,6 +69,7 @@ devices = (
         docker,
         temperaturemanager,
         configuration,
+        sdpfirmware_l,
         sdp_l,
         bst_l,
         sst_l,
@@ -74,6 +77,7 @@ devices = (
         beamlet_l,
         digitalbeam_l,
         antennafield_l,
+        sdpfirmware_h,
         sdp_h,
         bst_h,
         sst_h,
diff --git a/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
index 33b628d4f..36348097d 100644
--- a/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
+++ b/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
@@ -40,6 +40,8 @@
         },
         "stat/rcu2l/1": {
         },
+        "stat/sdpfirmware/1":{
+        },
         "stat/sdp/1": {
             "include": [
                 "Duration_*"
diff --git a/docker-compose/tango-prometheus-exporter/lofar2-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-policy.json
index 81c9a01d7..96edbf29f 100644
--- a/docker-compose/tango-prometheus-exporter/lofar2-policy.json
+++ b/docker-compose/tango-prometheus-exporter/lofar2-policy.json
@@ -78,10 +78,12 @@
                 "*_ITRF_offsets_R"
             ]
         },
-        "stat/sdp/*": {
+        "stat/sdpfirmware/*": {
             "include": [
                 "TR_fpga_mask_RW"
-            ],
+            ]
+        },
+        "stat/sdp/*": {
             "exclude": [
                 "FPGA_subband_weights_*",
                 "FPGA_signal_input_samples_delay_*",
diff --git a/jupyter-notebooks/DEMO_Firmware.ipynb b/jupyter-notebooks/DEMO_Firmware.ipynb
new file mode 100644
index 000000000..f1e1d8880
--- /dev/null
+++ b/jupyter-notebooks/DEMO_Firmware.ipynb
@@ -0,0 +1,202 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "51527517-5dfe-4888-9465-c8672ce8004f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Devices\n",
+    "firmware = DeviceProxy(\"stat/sdpfirmware/1\")\n",
+    "sdp = DeviceProxy(\"stat/sdp/1\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "df645dd1-95cf-4ba7-8c2c-6827e5e005bb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Turn on Firmware\n",
+    "firmware.off()\n",
+    "firmware.warm_boot() # hardware not initialised == factory image\n",
+    "firmware.state()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "41f47d35-a5c4-4a90-bb4b-6d0abfbd8669",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Turn on SDP\n",
+    "sdp.off()\n",
+    "sdp.warm_boot()\n",
+    "sdp.state()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5e8be729-cf0d-44d6-bccc-f337a472fdd6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# FPGA image (0 == factory image, 1 == user image)\n",
+    "firmware.FPGA_boot_image_RW"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0940df82-b9ad-4830-8017-306d1f6a6c0c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# SDP points not available\n",
+    "sdp.FPGA_processing_enable_R"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "83994c93-7310-48db-8a80-13b120976754",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Other Firmware monitor points\n",
+    "print(f\"TR_fpga_mask_R -> {firmware.TR_fpga_mask_R}\")\n",
+    "print()\n",
+    "print(f\"TR_fpga_communication_error_R -> {firmware.TR_fpga_communication_error_R}\")\n",
+    "print()\n",
+    "print(f\"FPGA_firmware_version_R -> {firmware.FPGA_firmware_version_R}\")\n",
+    "print()\n",
+    "print(f\"FPGA_error_R -> {firmware.FPGA_error_R}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a22a5e7a-25f3-41b2-bd88-759da854c3d9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Boot user image\n",
+    "firmware.use_user_image()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2afc3b22-40a1-4a34-aa3d-46019bb6354b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Communication error while booting\n",
+    "firmware.TR_fpga_communication_error_R"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "152aa1e0-6da9-48b2-b36e-190d218350af",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# FPGA image (0 == factory image, 1 == user image)\n",
+    "firmware.FPGA_boot_image_RW "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "bb8f678d-5c5b-44df-8026-0db9e23de8f7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Other Firmware monitor points\n",
+    "print(f\"TR_fpga_mask_R -> {firmware.TR_fpga_mask_R}\")\n",
+    "print()\n",
+    "print(f\"TR_fpga_communication_error_R -> {firmware.TR_fpga_communication_error_R}\")\n",
+    "print()\n",
+    "print(f\"FPGA_firmware_version_R -> {firmware.FPGA_firmware_version_R}\")\n",
+    "print()\n",
+    "print(f\"FPGA_error_R -> {firmware.FPGA_error_R}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "13870ada-0384-4a12-9d2f-7a5c387efe19",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# SDP points now should be available\n",
+    "sdp.FPGA_processing_enable_R"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7be6f84e-1673-4bcf-a331-6363d567294b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Reverse to factory image\n",
+    "sdp.off()\n",
+    "firmware.use_factory_image()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5d9515bc-7a8d-4005-8696-869c426a57d6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# FPGA image (0 == factory image, 1 == user image)\n",
+    "firmware.FPGA_boot_image_RW "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b1eebd2f-3b15-4d9e-b31a-0b189b999985",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ea3f514f-1780-4c7b-aeff-4d3dc704c4c3",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "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.10.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh
index ba60f62ee..48d18866f 100755
--- a/sbin/run_integration_test.sh
+++ b/sbin/run_integration_test.sh
@@ -76,7 +76,7 @@ echo '/usr/local/bin/wait-for-it.sh ${TANGO_HOST} --strict --timeout=300 -- true
 
 # Devices list is used to explitly word split when supplied to commands, must
 # disable shellcheck SC2086 for each case.
-DEVICES=(device-station-manager device-boot device-apsct device-ccd device-apspu device-sdp device-rcu2h device-rcu2l device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-pcon device-antennafield device-temperature-manager device-observation device-observation-control device-configuration device-calibration)
+DEVICES=(device-station-manager device-boot device-apsct device-ccd device-apspu device-sdpfirmware device-sdp device-rcu2h device-rcu2l device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-pcon device-antennafield device-temperature-manager device-observation device-observation-control device-configuration device-calibration)
 
 SIMULATORS=(sdptr-sim rcu2h-sim rcu2l-sim unb2-sim apsct-sim apspu-sim ccd-sim)
 
@@ -124,7 +124,7 @@ integration_test default
 
 integration_test tilebeam_performance "device-rcu2h device-rcu2l device-tilebeam device-antennafield" "${LOFAR20_DIR}/CDB/integrations/tilebeam_cluster_ConfigDb.json"
 
-integration_test digitalbeam_performance "device-sdp device-rcu2h device-rcu2l device-digitalbeam device-beamlet device-antennafield" "${LOFAR20_DIR}/CDB/integrations/digitalbeam_cluster_ConfigDb.json"
+integration_test digitalbeam_performance "device-sdpfirmware device-sdp device-rcu2h device-rcu2l device-digitalbeam device-beamlet device-antennafield" "${LOFAR20_DIR}/CDB/integrations/digitalbeam_cluster_ConfigDb.json"
 
 integration_test configuration "device-configuration"
 
diff --git a/tangostationcontrol/docs/source/configure_station.rst b/tangostationcontrol/docs/source/configure_station.rst
index 0796d0980..fa45b4c34 100644
--- a/tangostationcontrol/docs/source/configure_station.rst
+++ b/tangostationcontrol/docs/source/configure_station.rst
@@ -22,6 +22,10 @@ Without these settings, you will not obtain the associated functionality:
 
   :type: ``string``
 
+:SDPFirmware.OPC_Server_Name: Hostname of SDPTR.
+
+  :type: ``string``
+
 :SDP.OPC_Server_Name: Hostname of SDPTR.
 
   :type: ``string``
diff --git a/tangostationcontrol/docs/source/devices/sdp.rst b/tangostationcontrol/docs/source/devices/sdp.rst
index d3d8f4054..9432c6747 100644
--- a/tangostationcontrol/docs/source/devices/sdp.rst
+++ b/tangostationcontrol/docs/source/devices/sdp.rst
@@ -1,13 +1,7 @@
 SDP
 ---------------------
 
-The ``sdp == DeviceProxy("STAT/SDP/1")``` device controls the digital signal processing in SDP, performed by the firmware on the FPGAs on the Uniboards. Central to its operation is the mask (see also :ref:`attribute-masks`):
-
-:TR_fpga_mask_RW: Controls which FPGAs will actually be configured when attributes referring to FPGAs are written.
-
-  :type: ``bool[N_fpgas]``
-
-Typically, ``N_fpgas == 16``.
+The ``sdp == DeviceProxy("STAT/SDP/1")``` device controls the digital signal processing in SDP, performed by the firmware on the FPGAs on the Uniboards.
 
 See the following links for a full description of the SDP monitoring and control points:
 
@@ -23,10 +17,6 @@ The following points are significant for the operations of this device:
 
   :type: ``bool[N_fpgas]``
 
-:TR_fpga_communication_error_R: Whether the FPGAs can be reached.
-
-  :type: ``bool[N_fpgas]``
-
 Frequency management
 ```````````````````````````
 
@@ -72,10 +62,6 @@ Error information
 
 These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors:
 
-:FPGA_error_R: Whether the FPGAs appear usable.
-
-  :type: ``bool[N_fpgas]``
-
 :FPGA_procesing_error_R: Whether the FPGAs are processing their input from the RCUs. NB: This will also raise an error if the Waveform Generator is enabled.
 
   :type: ``bool[N_fpgas]``
@@ -126,7 +112,7 @@ Usage example
 For example, the following code inserts a wave on LBA subband 102 on FPGAs 8 - 11::
 
   # configure FPGAs to control
-  sdp.TR_fpga_mask_RW = [False] * 8 + [True] * 4 + [False] * 4
+  sdpfirmware.TR_fpga_mask_RW = [False] * 8 + [True] * 4 + [False] * 4
 
   # configure waveform generator
   sdp.FPGA_wg_phase_RW     = [[0] * 12] * 16
diff --git a/tangostationcontrol/docs/source/devices/sdpfirmware.rst b/tangostationcontrol/docs/source/devices/sdpfirmware.rst
new file mode 100644
index 000000000..e7328e752
--- /dev/null
+++ b/tangostationcontrol/docs/source/devices/sdpfirmware.rst
@@ -0,0 +1,34 @@
+SDP Firmware
+---------------------
+
+The ``sdpfirmware == DeviceProxy("STAT/SDPFirmware/1")``` device controls the firmware functionalities related to the digital signal processing in SDP device. Central to its operation is the mask (see also :ref:`attribute-masks`):
+
+:TR_fpga_mask_RW: Controls which FPGAs will actually be configured when attributes referring to FPGAs are written.
+
+  :type: ``bool[N_fpgas]``
+
+Typically, ``N_fpgas == 16``.
+
+See the following links for a full description of the SDP monitoring and control points:
+
+- https://support.astron.nl/confluence/pages/viewpage.action?spaceKey=L2M&title=L2+STAT+Decision%3A+SC+-+SDP+OPC-UA+interface
+- https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD
+
+Basic configuration
+`````````````````````
+
+The following points are significant for the operations of this device:
+
+:TR_fpga_communication_error_R: Whether the FPGAs can be reached.
+
+  :type: ``bool[N_fpgas]``
+
+Error information
+```````````````````````````
+
+These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors:
+
+:FPGA_error_R: Whether the FPGAs appear usable.
+
+  :type: ``bool[N_fpgas]``
+
diff --git a/tangostationcontrol/docs/source/faq.rst b/tangostationcontrol/docs/source/faq.rst
index 4e2264ae0..d02726dc6 100644
--- a/tangostationcontrol/docs/source/faq.rst
+++ b/tangostationcontrol/docs/source/faq.rst
@@ -77,7 +77,7 @@ In general, the settings ought to be correct after the following:
 
 The ``sdp.set_defaults()`` command, followed by ``sst.set_defaults()`` / ``xst.set_defaults()``, should reset that device to its default settings, which should result in a working system again. Also, check the following settings:
 
-- ``sdp.TR_fpga_mask_RW[x] == True``, to make sure we're actually configuring the FPGAs,
+- ``sdpfirmware.TR_fpga_mask_RW[x] == True``, to make sure we're actually configuring the FPGAs,
 - ``sdp.FPGA_communication_error_R[x] == False``, to verify the FPGAs can be reached by SDP.
 - ``sdp.FPGA_processing_enabled_R[x] == True``, to verify that the FPGAs are processing, or the values and timestamps will be zero,
 - ``sdp.FPGA_signal_input_bsn_R`` is increasing, to verify that the FPGA processing is subject to the clock.
diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst
index acb3d1bae..0f731794b 100644
--- a/tangostationcontrol/docs/source/index.rst
+++ b/tangostationcontrol/docs/source/index.rst
@@ -24,6 +24,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st
    devices/tilebeam-digitalbeam
    devices/beamlet
    devices/rcu2h-rcu2l
+   devices/sdpfirmware
    devices/sdp
    devices/bst-sst-xst
    devices/boot
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
index 3894847c2..538b6514d 100644
--- a/tangostationcontrol/setup.cfg
+++ b/tangostationcontrol/setup.cfg
@@ -48,6 +48,7 @@ console_scripts =
     l2ss-observationcontrol = tangostationcontrol.devices.observation_control:main
     l2ss-rcu2h = tangostationcontrol.devices.recv.rcu2h:main
     l2ss-rcu2l = tangostationcontrol.devices.recv.rcu2l:main
+    l2ss-sdpfirmware = tangostationcontrol.devices.sdp.firmware:main
     l2ss-sdp = tangostationcontrol.devices.sdp.sdp:main
     l2ss-bst = tangostationcontrol.devices.sdp.bst:main
     l2ss-sst = tangostationcontrol.devices.sdp.sst:main
diff --git a/tangostationcontrol/tangostationcontrol/devices/README.md b/tangostationcontrol/tangostationcontrol/devices/README.md
index df47d0be1..eb7aa8bc6 100644
--- a/tangostationcontrol/tangostationcontrol/devices/README.md
+++ b/tangostationcontrol/tangostationcontrol/devices/README.md
@@ -10,7 +10,8 @@ If a new device is added, it will (likely) need to be referenced in several plac
 - Add the device hierarchies in `CDB/hierarchies` to define the power, control and clock configuration,
 - Adjust `docker-compose/jupyterlab/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py` to make an alias for it available in Jupyter-Lab,
 - Adjust `tangostationcontrol/tangostationcontrol/devices/boot.py` to add the device to the station initialisation sequence,
-- Add to `docker-compose/` to create a YaML file to start the device in a docker container. NOTE: it needs a unique 57xx port assigned (current _unused_ port value: 5727), a unique 58xx port for ZMQ events, and a unique 59xx port for ZMQ heartbeat
+- Adjust `tangostationcontrol/tangostationcontrol/devices/docker.py` to have the device container available as R and RW attributes,
+- Add to `docker-compose/` to create a YaML file to start the device in a docker container. NOTE: it needs a unique 57xx port assigned (current _unused_ port value: 5728), a unique 58xx port for ZMQ events, and a unique 59xx port for ZMQ heartbeat
 - Adjust `tangostationcontrol/setup.cfg` to add an entry point for the device in the package installation,
 - Add to `tangostationcontrol/tangostationcontrol/integration_test/default/devices/` to add an integration test,
 - Adjust `sbin/run_integration_test.sh` to have the device started when running the integration tests,
@@ -19,5 +20,4 @@ If a new device is added, it will (likely) need to be referenced in several plac
 - Add to `tangostationcontrol/docs/source/devices/` to mention the device in the end-user documentation.
 - Adjust `tangostationcontrol/docs/source/index.rst` to include the newly created file in `docs/source/devices/`.
 - Adjust `docker-compose/tango-prometheus-exporter/lofar2-policy.json` (in all lowercase) to include this device in the prometheus exporter
-- Adjust `docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json` (in all lowercase) to include this device in the prometheus exporter
-- Adjust `tangostationcontrol/tangostationcontrol/devices/docker.py` to have the device container available as R and RW attributes. 
+- Adjust `docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json` (in all lowercase) to include this device in the prometheus exporter.
diff --git a/tangostationcontrol/tangostationcontrol/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py
index 509df3cd8..a401fbb02 100644
--- a/tangostationcontrol/tangostationcontrol/devices/boot.py
+++ b/tangostationcontrol/tangostationcontrol/devices/boot.py
@@ -268,6 +268,7 @@ class Boot(LOFARDevice):
             "STAT/RCU2H/1",
             "STAT/RCU2L/1",
             "STAT/UNB2/1",  # Uniboards host SDP, so initialise them first
+            "STAT/SDPFirmware/1",
             "STAT/SDP/1",
             # SDP controls the mask for SST/XST/BST/Beamlet, so initialise it first
             "STAT/BST/1",
diff --git a/tangostationcontrol/tangostationcontrol/devices/docker.py b/tangostationcontrol/tangostationcontrol/devices/docker.py
index 89dc8e7b9..0af26fce4 100644
--- a/tangostationcontrol/tangostationcontrol/devices/docker.py
+++ b/tangostationcontrol/tangostationcontrol/devices/docker.py
@@ -128,6 +128,14 @@ class Docker(LOFARDevice):
         datatype=bool,
         access=AttrWriteType.READ_WRITE,
     )
+    device_sdpfirmware_R = AttributeWrapper(
+        comms_annotation={"container": "device-sdpfirmware"}, datatype=bool
+    )
+    device_sdpfirmware_RW = AttributeWrapper(
+        comms_annotation={"container": "device-sdpfirmware"},
+        datatype=bool,
+        access=AttrWriteType.READ_WRITE,
+    )
     device_sdp_R = AttributeWrapper(
         comms_annotation={"container": "device-sdp"}, datatype=bool
     )
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
index 7c1356e4e..46d829388 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py
@@ -434,7 +434,7 @@ class Beamlet(OPCUADevice):
     def read_subband_select_RW(self):
         # We can only return a single value, so we assume the FPGA is configured coherently.
         # Which is something that is to be checked by an independent monitoring system anyway.
-        mask = self.sdp_proxy.TR_fpga_mask_RW
+        mask = self.sdpfirmware_proxy.TR_fpga_mask_RW
         subbands = self.read_attribute("FPGA_beamlet_subband_select_RW")
         subbands_in_mask = [s for idx, s in enumerate(subbands) if mask[idx]]
 
@@ -467,6 +467,9 @@ class Beamlet(OPCUADevice):
     def configure_for_initialise(self):
         super().configure_for_initialise()
 
+        self.sdpfirmware_proxy = DeviceProxy("STAT/SDPFirmware/1")
+        self.sdpfirmware_proxy.set_source(DevSource.DEV)
+
         self.sdp_proxy = DeviceProxy(self.SDP_device)
         self.sdp_proxy.set_source(DevSource.DEV)
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
index 7e104c211..9f1a61a49 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py
@@ -23,6 +23,8 @@ __all__ = ["BST", "main"]
 
 
 class BST(Statistics):
+    """BST Device Server for LOFAR2.0"""
+
     STATISTICS_COLLECTOR_CLASS = BSTCollector
 
     # -----------------
@@ -164,7 +166,7 @@ class BST(Statistics):
     FPGA_processing_error_R = attribute(dtype=(bool,), max_dim_x=N_pn)
 
     def read_FPGA_processing_error_R(self):
-        return self.sdp_proxy.TR_fpga_mask_RW & (
+        return self.sdpfirmware_proxy.TR_fpga_mask_RW & (
             ~self.read_attribute("FPGA_bst_offload_enable_R")
         )
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py b/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
new file mode 100644
index 000000000..737765a3c
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/firmware.py
@@ -0,0 +1,146 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+""" SDP Firmware Device Server for LOFAR2.0
+
+"""
+
+import logging
+import numpy
+from attribute_wrapper.attribute_wrapper import AttributeWrapper
+from tango import AttrWriteType, DebugIt
+
+# PyTango imports
+from tango.server import device_property, attribute, command
+
+# Additional import
+from tangostationcontrol.common.constants import N_pn
+from tangostationcontrol.common.entrypoint import entry
+from tangostationcontrol.common.lofar_logging import log_exceptions
+from tangostationcontrol.common.lofar_logging import device_logging_to_python
+from tangostationcontrol.devices.interfaces.opcua_device import OPCUADevice
+from tangostationcontrol.devices.device_decorators import only_in_states
+from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES
+
+logger = logging.getLogger()
+
+__all__ = ["SDPFirmware", "main"]
+
+
+@device_logging_to_python()
+class SDPFirmware(OPCUADevice):
+    """SDP Firmware Device server for LOFAR 2.0"""
+
+    # -----------------
+    # Device Properties
+    # -----------------
+
+    TR_fpga_mask_RW_default = device_property(
+        dtype="DevVarBooleanArray", mandatory=False, default_value=[True] * N_pn
+    )
+
+    TRANSLATOR_DEFAULT_SETTINGS = ["TR_fpga_mask_RW"]
+
+    # ----------
+    # Attributes
+    # ----------
+
+    TR_fpga_mask_R = AttributeWrapper(
+        comms_annotation=["TR_fpga_mask_R"], datatype=bool, dims=(N_pn,)
+    )
+    TR_fpga_mask_RW = AttributeWrapper(
+        comms_annotation=["TR_fpga_mask_RW"],
+        datatype=bool,
+        dims=(N_pn,),
+        access=AttrWriteType.READ_WRITE,
+    )
+    TR_fpga_communication_error_R = AttributeWrapper(
+        comms_annotation=["TR_fpga_communication_error_R"], datatype=bool, dims=(N_pn,)
+    )
+    FPGA_boot_image_R = AttributeWrapper(
+        comms_annotation=["FPGA_boot_image_R"],
+        datatype=numpy.int32,
+        dims=(N_pn,),
+        doc="Active FPGA image (0=factory, 1=user)",
+    )
+    FPGA_boot_image_RW = AttributeWrapper(
+        comms_annotation=["FPGA_boot_image_RW"],
+        datatype=numpy.int32,
+        dims=(N_pn,),
+        access=AttrWriteType.READ_WRITE,
+    )
+    FPGA_firmware_version_R = AttributeWrapper(
+        comms_annotation=["FPGA_firmware_version_R"], datatype=str, dims=(N_pn,)
+    )
+
+    # ----------
+    # Summarising Attributes
+    # ----------
+
+    FPGA_error_R = attribute(
+        dtype=(bool,), max_dim_x=N_pn, fisallowed="is_attribute_access_allowed"
+    )
+
+    def read_FPGA_error_R(self):
+        return self.read_attribute("TR_fpga_mask_R") & (
+            self.read_attribute("TR_fpga_communication_error_R")
+            | (self.read_attribute("FPGA_firmware_version_R") == "")
+            # we cannot assume all inputs of an FPGA are working until we have a mask for it
+            # | (self.read_attribute("FPGA_jesd204b_csr_dev_syncn_R") == 0).any(axis=1)
+        )
+
+    # --------
+    # overloaded functions
+    # --------
+    def _prepare_hardware(self):
+        """Boot the SDP Firmware user image"""
+        # FPGAs that are actually reachable and we care about
+        wait_for = ~(
+            self.read_attribute("TR_fpga_communication_error_R")
+        ) & self.read_attribute("TR_fpga_mask_R")
+
+        # Order the correct firmare to be loaded
+        self.proxy.FPGA_boot_image_RW = [1] * N_pn
+
+        # Wait for the firmware to be loaded (ignoring masked out elements)
+        self.wait_attribute(
+            "FPGA_boot_image_R", lambda attr: ((attr == 1) | ~wait_for).all(), 60
+        )
+
+    def _disable_hardware(self):
+        """Use the SDP Firmware factory image"""
+        # Save actual mask values
+        TR_fpga_mask = self.proxy.TR_fpga_mask_RW
+        # Set the mask to all Trues
+        self.proxy.TR_fpga_mask_RW = [True] * N_pn
+        # Boot the boot image firmware
+        self.proxy.FPGA_boot_image_RW = [0] * N_pn
+        # Restore the mask
+        self.proxy.TR_fpga_mask_RW = TR_fpga_mask
+
+    # --------
+    # Commands
+    # --------
+    @command()
+    @only_in_states(DEFAULT_COMMAND_STATES)
+    @DebugIt()
+    @log_exceptions()
+    def use_user_image(self):
+        """Boot the SDP Firmware user image"""
+        self._prepare_hardware()
+
+    @command()
+    @only_in_states(DEFAULT_COMMAND_STATES)
+    @DebugIt()
+    @log_exceptions()
+    def use_factory_image(self):
+        """Use the SDP Firmware factory image"""
+        self._disable_hardware()
+
+
+# ----------
+# Run server
+# ----------
+def main(**kwargs):
+    """Main function of the SDP Firmware module."""
+    return entry(SDPFirmware, **kwargs)
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
index db3f3718e..53b675303 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py
@@ -7,7 +7,7 @@
 
 import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
-from tango import AttrWriteType
+from tango import AttrWriteType, DeviceProxy, DevSource
 
 # PyTango imports
 from tango.server import device_property, attribute
@@ -36,14 +36,12 @@ __all__ = ["SDP", "main"]
 
 @device_logging_to_python()
 class SDP(OPCUADevice):
+    """SDP Device Server for LOFAR2.0"""
+
     # -----------------
     # Device Properties
     # -----------------
 
-    TR_fpga_mask_RW_default = device_property(
-        dtype="DevVarBooleanArray", mandatory=False, default_value=[True] * N_pn
-    )
-
     FPGA_processing_enable_RW_default = device_property(
         dtype="DevVarBooleanArray", mandatory=False, default_value=[True] * N_pn
     )
@@ -95,27 +93,10 @@ class SDP(OPCUADevice):
         dtype="DevULong", mandatory=False, default_value=CLK_200_MHZ
     )
 
-    TRANSLATOR_DEFAULT_SETTINGS = ["TR_fpga_mask_RW"]
-
     # ----------
     # Attributes
     # ----------
 
-    FPGA_firmware_version_R = AttributeWrapper(
-        comms_annotation=["FPGA_firmware_version_R"], datatype=str, dims=(N_pn,)
-    )
-    FPGA_boot_image_R = AttributeWrapper(
-        comms_annotation=["FPGA_boot_image_R"],
-        datatype=numpy.int32,
-        dims=(N_pn,),
-        doc="Active FPGA image (0=factory, 1=user)",
-    )
-    FPGA_boot_image_RW = AttributeWrapper(
-        comms_annotation=["FPGA_boot_image_RW"],
-        datatype=numpy.int32,
-        dims=(N_pn,),
-        access=AttrWriteType.READ_WRITE,
-    )
     FPGA_global_node_index_R = AttributeWrapper(
         comms_annotation=["FPGA_global_node_index_R"],
         datatype=numpy.uint32,
@@ -339,18 +320,6 @@ class SDP(OPCUADevice):
         dims=(N_pn, S_pn),
         access=AttrWriteType.READ_WRITE,
     )
-    TR_fpga_mask_R = AttributeWrapper(
-        comms_annotation=["TR_fpga_mask_R"], datatype=bool, dims=(N_pn,)
-    )
-    TR_fpga_mask_RW = AttributeWrapper(
-        comms_annotation=["TR_fpga_mask_RW"],
-        datatype=bool,
-        dims=(N_pn,),
-        access=AttrWriteType.READ_WRITE,
-    )
-    TR_fpga_communication_error_R = AttributeWrapper(
-        comms_annotation=["TR_fpga_communication_error_R"], datatype=bool, dims=(N_pn,)
-    )
     TR_sdp_config_first_fpga_nr_R = AttributeWrapper(
         comms_annotation=["TR_sdp_config_first_fpga_nr_R"], datatype=numpy.uint32
     )
@@ -520,7 +489,7 @@ class SDP(OPCUADevice):
             # (0=LBA, 1=HBA)
             if first_input == "HBA":
                 antenna_band[idx] = 1
-        self.FPGA_sdp_info_antenna_band_index_RW = antenna_band
+        self.proxy.FPGA_sdp_info_antenna_band_index_RW = antenna_band
 
     def read_nyquist_zone_RW(self):
         return self._nyquist_zone
@@ -547,17 +516,19 @@ class SDP(OPCUADevice):
         # Update the packet headers.
         self.proxy.FPGA_sdp_info_nyquist_sampling_zone_index_RW = nyquist_zone_per_fpga
 
-        # Use the correct spectral inversion setting, to make sure the subbands are ascending in frequency
+        # Use the correct spectral inversion setting,
+        # to make sure the subbands are ascending in frequency
         self.proxy.FPGA_spectral_inversion_RW = self._nyquist_zone % 2
 
     def read_clock_RW(self):
-        # We can only return a single value, so we assume the FPGA is configured coherently. Which is something
-        # that is to be checked by an independent monitoring system anyway.
-        mask = self.read_attribute("TR_fpga_mask_RW")
+        # We can only return a single value, so we assume the FPGA is configured coherently.
+        # Which is something that is to be checked by an independent monitoring system anyway.
+        mask = self.sdpfirmware_proxy.TR_fpga_mask_RW
         clocks = self.read_attribute("FPGA_pps_expected_cnt_RW")
         clocks_in_mask = [clock for idx, clock in enumerate(clocks) if mask[idx]]
 
-        # We return first setting within the mask. If there are no FPGAs selected at all, just return a sane default.
+        # We return first setting within the mask.
+        # If there are no FPGAs selected at all, just return a sane default.
         return (
             numpy.uint32(clocks_in_mask[0]) if clocks_in_mask else self.clock_RW_default
         )
@@ -604,9 +575,6 @@ class SDP(OPCUADevice):
     # ----------
     # Summarising Attributes
     # ----------
-    FPGA_error_R = attribute(
-        dtype=(bool,), max_dim_x=N_pn, fisallowed="is_attribute_access_allowed"
-    )
     FPGA_processing_error_R = attribute(
         dtype=(bool,), max_dim_x=N_pn, fisallowed="is_attribute_access_allowed"
     )
@@ -614,22 +582,14 @@ class SDP(OPCUADevice):
         dtype=(bool,), max_dim_x=N_pn, fisallowed="is_attribute_access_allowed"
     )
 
-    def read_FPGA_error_R(self):
-        return self.read_attribute("TR_fpga_mask_R") & (
-            self.read_attribute("TR_fpga_communication_error_R")
-            | (self.read_attribute("FPGA_firmware_version_R") == "")
-            # we cannot assume all inputs of an FPGA are working until we have a mask for it
-            # | (self.read_attribute("FPGA_jesd204b_csr_dev_syncn_R") == 0).any(axis=1)
-        )
-
     def read_FPGA_processing_error_R(self):
-        return self.read_attribute("TR_fpga_mask_R") & (
+        return self.sdpfirmware_proxy.TR_fpga_mask_R & (
             ~self.read_attribute("FPGA_processing_enable_R")
-            | (self.read_attribute("FPGA_boot_image_R") <= 0)
+            | (self.sdpfirmware_proxy.FPGA_boot_image_R <= 0)
         )
 
     def read_FPGA_input_error_R(self):
-        return self.read_attribute("TR_fpga_mask_R") & (
+        return self.sdpfirmware_proxy.TR_fpga_mask_R & (
             self.read_attribute("FPGA_wg_enable_R").any(axis=1)
             | (self.read_attribute("FPGA_signal_input_rms_R") == 0).any(axis=1)
         )
@@ -640,6 +600,9 @@ class SDP(OPCUADevice):
 
     def configure_for_initialise(self):
         super().configure_for_initialise()
+        # Retrieve reference to SDP Firmware
+        self.sdpfirmware_proxy = DeviceProxy("STAT/SDPFirmware/1")
+        self.sdpfirmware_proxy.set_source(DevSource.DEV)
 
         # Store which type of antenna and band filter is connected to each input.
         #
@@ -647,31 +610,6 @@ class SDP(OPCUADevice):
         self._antenna_type = numpy.array([["LBA"] * S_pn] * N_pn, dtype=str)
         self._nyquist_zone = numpy.array([[0] * S_pn] * N_pn, dtype=numpy.uint32)
 
-    def _prepare_hardware(self):
-        # FPGAs that are actually reachable and we care about
-        wait_for = ~(
-            self.read_attribute("TR_fpga_communication_error_R")
-        ) & self.read_attribute("TR_fpga_mask_R")
-
-        # Order the correct firmare to be loaded
-        self.proxy.FPGA_boot_image_RW = [1] * N_pn
-
-        # Wait for the firmware to be loaded (ignoring masked out elements)
-        self.wait_attribute(
-            "FPGA_boot_image_R", lambda attr: ((attr == 1) | ~wait_for).all(), 60
-        )
-
-    def _disable_hardware(self):
-        """Disable the SDP hardware."""
-        # Save actual mask values
-        TR_fpga_mask = self.proxy.TR_fpga_mask_RW
-        # Set the mask to all Trues
-        self.TR_fpga_mask_RW = [True] * N_pn
-        # Boot the boot image firmware
-        self.FPGA_boot_image_RW = [0] * N_pn
-        # Restore the mask
-        self.TR_fpga_mask_RW = TR_fpga_mask
-
     # --------
     # Commands
     # --------
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
index 3d5c53494..d1b3502f4 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py
@@ -25,6 +25,8 @@ __all__ = ["SST", "main"]
 
 
 class SST(Statistics):
+    """SST Device Server for LOFAR2.0"""
+
     STATISTICS_COLLECTOR_CLASS = SSTCollector
 
     # -----------------
@@ -198,7 +200,7 @@ class SST(Statistics):
     FPGA_processing_error_R = attribute(dtype=(bool,), max_dim_x=N_pn)
 
     def read_FPGA_processing_error_R(self):
-        return self.sdp_proxy.TR_fpga_mask_RW & (
+        return self.sdpfirmware_proxy.TR_fpga_mask_RW & (
             ~self.read_attribute("FPGA_sst_offload_enable_R")
         )
 
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
index 4beee6aeb..e546901c5 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py
@@ -8,6 +8,7 @@
 # Additional import
 import asyncio
 import logging
+import numpy
 from attribute_wrapper.attribute_wrapper import AttributeWrapper
 
 from tango import DeviceProxy, DevSource
@@ -21,12 +22,12 @@ from tangostationcontrol.devices.interfaces.opcua_device import OPCUADevice
 
 logger = logging.getLogger()
 
-import numpy
-
 __all__ = ["Statistics"]
 
 
 class Statistics(OPCUADevice):
+    """Base device for Statistics (SST/BST/XST)"""
+
     # In derived classes, set this to a subclass of StatisticsCollector
     @property
     def STATISTICS_COLLECTOR_CLASS(self):
@@ -152,7 +153,7 @@ class Statistics(OPCUADevice):
 
         try:
             self.statistics_client.sync_stop()
-        except Exception as e:
+        except Exception as _e:
             logger.exception(
                 "Exception while stopping statistics_client in configure_for_off. Exception ignored"
             )
@@ -161,7 +162,6 @@ class Statistics(OPCUADevice):
 
     @log_exceptions()
     def configure_for_initialise(self):
-        """user code here. is called when the sate is set to INIT"""
         """Initialises the attributes and properties of the statistics device."""
 
         super().configure_for_initialise()
@@ -194,8 +194,8 @@ class Statistics(OPCUADevice):
         _ = future.result()
 
         # proxy the SDP device in case we need the FPGA mask
-        self.sdp_proxy = DeviceProxy("STAT/SDP/1")
-        self.sdp_proxy.set_source(DevSource.DEV)
+        self.sdpfirmware_proxy = DeviceProxy("STAT/SDPFirmware/1")
+        self.sdpfirmware_proxy.set_source(DevSource.DEV)
 
     async def _connect_statistics(self):
         # map an access helper class
@@ -203,13 +203,14 @@ class Statistics(OPCUADevice):
             try:
                 if i.comms_id == StatisticsClient:
                     await i.async_set_comm_client(self, self.statistics_client)
-            except Exception as e:
+            except Exception as exc:
                 # use the pass function instead of setting read/write fails
                 i.set_pass_func(self)
                 logger.warning(
-                    "error while setting the sst attribute {} read/write function. {}. using pass function instead".format(
-                        i, e
-                    )
+                    "error while setting the sst attribute %s read/write function. %s. \
+                    Using pass function instead",
+                    i,
+                    exc,
                 )
 
         await self.statistics_client.start()
diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
index 88ef3b59c..bcb7fbbf8 100644
--- a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
+++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py
@@ -34,6 +34,8 @@ __all__ = ["XST", "main"]
 
 
 class XST(Statistics):
+    """XST Device Server for LOFAR2.0"""
+
     STATISTICS_COLLECTOR_CLASS = XSTCollector
 
     # -----------------
@@ -704,7 +706,7 @@ class XST(Statistics):
     FPGA_processing_error_R = attribute(dtype=(bool,), max_dim_x=N_pn)
 
     def read_FPGA_processing_error_R(self):
-        return self.sdp_proxy.TR_fpga_mask_RW & (
+        return self.sdpfirmware_proxy.TR_fpga_mask_RW & (
             ~self.read_attribute("FPGA_xst_offload_enable_R")
             | ~self.read_attribute("FPGA_xst_processing_enable_R")
         )
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json b/tangostationcontrol/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json
index 12ccd43e7..07957d944 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json
+++ b/tangostationcontrol/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json
@@ -183,6 +183,25 @@
                 }
             }
         },
+        "SDPFirmware": {
+            "STAT": {
+                "SDPFirmware": {
+                    "STAT/SDPFirmware/1": {
+                        "properties": {
+                            "OPC_Server_Name": [
+                                "sdptr-sim"
+                            ],
+                            "OPC_Server_Port": [
+                                "4840"
+                            ],
+                            "OPC_Time_Out": [
+                                "5.0"
+                            ]
+                        }
+                    }
+                }
+            }
+        },
         "SDP": {
             "STAT": {
                 "SDP": {
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/interfaces/test_power_hierarchy.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/interfaces/test_power_hierarchy.py
index 541549b35..813c81aa6 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/interfaces/test_power_hierarchy.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/interfaces/test_power_hierarchy.py
@@ -11,7 +11,7 @@ from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy
 class TestPowerHierarchy(base.IntegrationTestCase):
     stationmanager_name = "STAT/StationManager/1"
     psoc_name = "STAT/PSOC/1"
-    sdp_name = "STAT/SDP/1"
+    sdpfirmware_name = "STAT/SDPFirmware/1"
 
     def setUp(self):
         return super().setUp()
@@ -34,26 +34,21 @@ class TestPowerHierarchy(base.IntegrationTestCase):
         psoc_proxy.off()
         power_properties = {
             "Power_Parent": ["STAT/StationManager/1"],
-            "Power_Children": ["STAT/CCD/1", "STAT/SDP/1"],
+            "Power_Children": ["STAT/CCD/1", "STAT/SDPFirmware/1"],
         }
         psoc_proxy.put_property(power_properties)
         return psoc_proxy
 
-    def setup_sdp_proxy(self):
-        """Initialise SDP device"""
-        sdp_proxy = TestDeviceProxy(self.sdp_name)
-        sdp_proxy.off()
+    def setup_sdpfirmware_proxy(self):
+        """Initialise SDP Firmware device"""
+        sdpfirmware_proxy = TestDeviceProxy(self.sdpfirmware_name)
+        sdpfirmware_proxy.off()
         power_properties = {
             "Power_Parent": ["STAT/PSOC/1"],
-            "Power_Children": [
-                "STAT/Beamlet/1",
-                "STAT/BST/1",
-                "STAT/SST/1",
-                "STAT/XST/1",
-            ],
+            "Power_Children": ["STAT/SDP/1"],
         }
-        sdp_proxy.put_property(power_properties)
-        return sdp_proxy
+        sdpfirmware_proxy.put_property(power_properties)
+        return sdpfirmware_proxy
 
     def test_power_sequence_definition(self):
         """
@@ -61,18 +56,19 @@ class TestPowerHierarchy(base.IntegrationTestCase):
         """
         self.setup_stationmanager_proxy()
         self.setup_psoc_proxy()
-        self.setup_sdp_proxy()
+        self.setup_sdpfirmware_proxy()
         stationmanager_ph = PowerHierarchy()
         stationmanager_ph.init(self.stationmanager_name)
         children_hierarchy = stationmanager_ph.children(depth=2)
         # Check if PSOC is child of StationManager
-        self.assertTrue(self.psoc_name in children_hierarchy.keys())
+        self.assertTrue(self.psoc_name in children_hierarchy)
         self.assertTrue(
             isinstance(children_hierarchy["STAT/PSOC/1"]["proxy"], DeviceProxy)
         )
-        # Check if SDP is child of PSOC
+        # Check if SDPFirmware is child of PSOC
         self.assertTrue(
-            self.sdp_name in children_hierarchy["STAT/PSOC/1"]["children"].keys()
+            self.sdpfirmware_name
+            in children_hierarchy["STAT/PSOC/1"]["children"].keys()
         )
         # Check if PSOC retrieves correctly its parent state (StationManager -> ON)
         psoc_ph = PowerHierarchy()
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
index 945b32160..ab763ba84 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
@@ -35,6 +35,7 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
             }
         )
         self.recv_proxy = self.setup_recv_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
 
         self.addCleanup(self.shutdown_recv)
@@ -72,6 +73,13 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         recv_proxy.set_defaults()
         return recv_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDPFirmware
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_calibration.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
index 246cddd79..49ff5e3d7 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
@@ -80,6 +80,7 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
             }
         )
         self.recv_proxy = self.setup_recv_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
 
         self.addCleanup(self.shutdown_recv)
@@ -125,6 +126,13 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
         recv_proxy.set_defaults()
         return recv_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDP
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
index cadb3af2c..87a62bfb5 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
@@ -35,6 +35,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
     antennafield_iden = "STAT/AntennaField/HBA"
     beamlet_iden = "STAT/Beamlet/1"
     recv_iden = "STAT/RCU2H/1"
+    sdpfirmware_iden = "STAT/SDPFirmware/1"
     sdp_iden = "STAT/SDP/1"
 
     def setUp(self):
@@ -47,6 +48,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.recv_iden)
 
         self.recv_proxy = self.setup_recv_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         self.beamlet_proxy = self.initialise_beamlet_proxy()
 
@@ -70,6 +72,14 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         beamlet_proxy.set_defaults()
         return beamlet_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDPFirmware
+        sdpfirmware_proxy = TestDeviceProxy(self.sdpfirmware_iden)
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        sdpfirmware_proxy.set_defaults()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP, on which this device depends
         sdp_proxy = TestDeviceProxy(self.sdp_iden)
@@ -109,6 +119,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.sdp_iden)
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.antennafield_iden)
 
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         self.setup_antennafield_proxy(self.antenna_qualities_ok, self.antenna_use_ok)
 
@@ -152,6 +163,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.sdp_iden)
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.antennafield_iden)
 
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         self.setup_antennafield_proxy(self.antenna_qualities_ok, self.antenna_use_ok)
 
@@ -198,6 +210,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.sdp_iden)
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.antennafield_iden)
 
+        self.setup_sdpfirmware_proxy()
         self.setup_sdp_proxy()
         self.antennafield_proxy = self.setup_antennafield_proxy(
             self.antenna_qualities_ok, self.antenna_use_ok
@@ -251,6 +264,7 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.sdp_iden)
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.antennafield_iden)
 
+        self.setup_sdpfirmware_proxy()
         self.setup_sdp_proxy()
         self.setup_antennafield_proxy(self.antenna_qualities_ok, self.antenna_use_ok)
 
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py
index dd375e946..28325f09d 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py
@@ -122,6 +122,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         super().setUp("STAT/Observation/1")
         self.VALID_JSON = TestObservationBase.VALID_JSON
         self.recv_proxy = self.setup_recv_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         self.antennafield_proxy = self.setup_antennafield_proxy()
         self.beamlet_proxy = self.setup_beamlet_proxy()
@@ -136,6 +137,13 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         recv_proxy.set_defaults()
         return recv_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDPFirmware
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
index 4111ed8b4..890e12e14 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
@@ -121,6 +121,7 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         self.recv_proxy = self.setup_recv_proxy()
         self.antennafield_proxy = self.setup_antennafield_proxy()
         self.beamlet_proxy = self.setup_beamlet_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         self.digitalbeam_proxy = self.setup_digitalbeam_proxy()
         self.tilebeam_proxy = self.setup_tilebeam_proxy()
@@ -133,6 +134,13 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         recv_proxy.set_defaults()
         return recv_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDPFirmware
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdp.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
index ca1337802..a49f76cb9 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
@@ -1,8 +1,6 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from tangostationcontrol.common.constants import N_pn
-
 from .base import AbstractTestBases
 
 
@@ -10,12 +8,3 @@ class TestDeviceSDP(AbstractTestBases.TestDeviceBase):
     def setUp(self):
         """Intentionally recreate the device object in each test"""
         super().setUp("STAT/SDP/1")
-
-    def test_device_sdp_read_attribute(self):
-        """Test if we can read an attribute obtained over OPC-UA"""
-
-        self.proxy.warm_boot()
-
-        self.assertListEqual(
-            [True] * N_pn, list(self.proxy.TR_fpga_communication_error_R)
-        )
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py
new file mode 100644
index 000000000..bce1b837a
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+from tangostationcontrol.common.constants import N_pn
+
+from .base import AbstractTestBases
+
+
+class TestDeviceSDPFirmware(AbstractTestBases.TestDeviceBase):
+    def setUp(self):
+        """Intentionally recreate the device object in each test"""
+        super().setUp("STAT/SDPFirmware/1")
+
+    def test_device_sdpfirmware_read_attribute(self):
+        """Test if we can read an attribute obtained over OPC-UA"""
+
+        self.proxy.warm_boot()
+
+        self.assertListEqual(
+            [True] * N_pn, list(self.proxy.TR_fpga_communication_error_R)
+        )
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sst.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sst.py
index b54bdc5fc..6b2565f7f 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sst.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_sst.py
@@ -18,10 +18,19 @@ class TestDeviceSST(AbstractTestBases.TestDeviceBase):
 
     def test_device_read_all_attributes(self):
         # We need to connect to SDP first to read some of our attributes
+        self.sdpfirmware_proxy = self.setup_sdpfirmware()
         self.sdp_proxy = self.setup_sdp()
 
         super().test_device_read_all_attributes()
 
+    def setup_sdpfirmware(self):
+        # setup SDP Firmware
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        sdpfirmware_proxy.set_defaults()
+        return sdpfirmware_proxy
+
     def setup_sdp(self):
         # setup SDP, on which this device depends
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
index 393f72eb9..c33129378 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
@@ -24,6 +24,7 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
     def setUp(self):
         """Intentionally recreate the device object in each test"""
         self.recv_proxy = self.setup_recv_proxy()
+        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
         self.sdp_proxy = self.setup_sdp_proxy()
         super().setUp("STAT/TemperatureManager/1")
 
@@ -40,6 +41,14 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
         self.assertTrue(recv_proxy.is_attribute_polled("HBAT_LED_on_RW"))
         return recv_proxy
 
+    def setup_sdpfirmware_proxy(self):
+        # setup SDPFirmware
+        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/1")
+        sdpfirmware_proxy.off()
+        sdpfirmware_proxy.warm_boot()
+        sdpfirmware_proxy.set_defaults()
+        return sdpfirmware_proxy
+
     def setup_sdp_proxy(self):
         # setup SDP, on which this device depends
         sdp_proxy = TestDeviceProxy("STAT/SDP/1")
@@ -60,6 +69,7 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
         self.proxy.initialise()
         self.proxy.on()
         self.setup_recv_proxy()
+        self.setup_sdpfirmware_proxy()
         self.setup_sdp_proxy()
 
         # make sure none of the devices are in the OFF or FAULT state. Any other state is fine
diff --git a/tangostationcontrol/tangostationcontrol/integration_test/digitalbeam_performance/test_digitalbeam_performance.py b/tangostationcontrol/tangostationcontrol/integration_test/digitalbeam_performance/test_digitalbeam_performance.py
index 6e035960c..9bc5bf258 100644
--- a/tangostationcontrol/tangostationcontrol/integration_test/digitalbeam_performance/test_digitalbeam_performance.py
+++ b/tangostationcontrol/tangostationcontrol/integration_test/digitalbeam_performance/test_digitalbeam_performance.py
@@ -29,19 +29,21 @@ class TestDigitalbeamPerformance(base.IntegrationTestCase):
         antenna_field_proxies,
         beamlet_proxies,
         beam_proxies,
+        sdpfirmware_proxies,
         sdp_proxies,
         recv_proxies,
         tracking=False,
     ):
         # Setup multi SDP / recv and separate hba / lba antennafield / digitalbeams
         for i, item in enumerate(["HBA", "LBA"]):
+            sdpfirmware_proxies.append(TestDeviceProxy(f"STAT/SDPFirmware/{i + 1}"))
             sdp_proxies.append(TestDeviceProxy(f"STAT/SDP/{i + 1}"))
             beamlet_proxies.append(TestDeviceProxy(f"STAT/Beamlet/{i + 1}"))
             antenna_field_proxies.append(TestDeviceProxy(f"STAT/AntennaField/{item}"))
             beam_proxies.append(TestDeviceProxy(f"STAT/DigitalBeam/{item}"))
             recv_proxies.append(TestDeviceProxy(f"STAT/RCU2H/{i + 1}"))
 
-        for sdp in sdp_proxies + recv_proxies + beamlet_proxies:
+        for sdp in sdpfirmware_proxies + sdp_proxies + recv_proxies + beamlet_proxies:
             sdp.off()
             self.assertTrue(sdp.state() is DevState.OFF)
             sdp.warm_boot()
@@ -109,6 +111,7 @@ class TestDigitalbeamPerformance(base.IntegrationTestCase):
         antenna_field_proxies = []
         beamlet_proxies = []
         beam_proxies = []
+        sdpfirmware_proxies = []
         sdp_proxies = []
         recv_proxies = []
 
@@ -116,6 +119,7 @@ class TestDigitalbeamPerformance(base.IntegrationTestCase):
             antenna_field_proxies=antenna_field_proxies,
             beamlet_proxies=beamlet_proxies,
             beam_proxies=beam_proxies,
+            sdpfirmware_proxies=sdpfirmware_proxies,
             sdp_proxies=sdp_proxies,
             recv_proxies=recv_proxies,
             tracking=False,
@@ -153,6 +157,7 @@ class TestDigitalbeamPerformance(base.IntegrationTestCase):
         antenna_field_proxies = []
         beamlet_proxies = []
         beam_proxies = []
+        sdpfirmware_proxies = []
         sdp_proxies = []
         recv_proxies = []
 
@@ -160,6 +165,7 @@ class TestDigitalbeamPerformance(base.IntegrationTestCase):
             antenna_field_proxies=antenna_field_proxies,
             beamlet_proxies=beamlet_proxies,
             beam_proxies=beam_proxies,
+            sdpfirmware_proxies=sdpfirmware_proxies,
             sdp_proxies=sdp_proxies,
             recv_proxies=recv_proxies,
             tracking=True,
-- 
GitLab