diff --git a/.gitignore b/.gitignore
index 4ccabd3716b8e8e40abe61bf82c11916c4dbfbc7..8ee8430be40555ed5cf527dfb54c8e515d0ae80c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,8 @@
 **/vscode-server.tar
 .orig
 
+bin/jumppad
+
 tangostationcontrol/build
 tangostationcontrol/cover
 tangostationcontrol/dist
diff --git a/CDB/integrations/multiobs_ConfigDb.json b/CDB/integrations/multiobs_ConfigDb.json
deleted file mode 100644
index ed52f2202654d17367fb9b379b148a7d4aeb6cec..0000000000000000000000000000000000000000
--- a/CDB/integrations/multiobs_ConfigDb.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "servers": {
-        "ObservationControl": {
-            "STAT": {
-                "Observation": {
-                    "STAT/Observation/1": {},
-                    "STAT/Observation/2": {},
-                    "STAT/Observation/3": {}
-                }
-            }
-        }
-    }
-}
diff --git a/CDB/stations/testenv_cs001.json b/CDB/stations/testenv_cs001.json
index a6286bd311fe9f121cd75347e7156d48b352e428..da4b205c396a99402dde96c116180209efdcf227 100644
--- a/CDB/stations/testenv_cs001.json
+++ b/CDB/stations/testenv_cs001.json
@@ -1115,26 +1115,6 @@
                 }
               }
             }
-        },
-        "ObservationControl": {
-            "STAT": {
-              "Observation": {
-                "STAT/Observation/1": {
-                }
-              },
-              "ObservationControl": {
-                "STAT/ObservationControl/1": {
-                  "properties": {
-                    "Power_Children": [
-                        "STAT/Observation/1"
-                    ],
-                    "Control_Children": [
-                        "STAT/Observation/1"
-                    ]
-                  }
-                }
-              }
-            }
         }
     }
 }
diff --git a/CDB/stations/testenv_cs001.json.orig b/CDB/stations/testenv_cs001.json.orig
deleted file mode 100644
index db183dbea108cf16d064e71e25b6616c4bfed753..0000000000000000000000000000000000000000
--- a/CDB/stations/testenv_cs001.json.orig
+++ /dev/null
@@ -1,1186 +0,0 @@
-{
-  "servers": {
-    "APSCT": {
-      "STAT": {
-        "APSCT": {
-          "STAT/APSCT/L0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apsct-sim"
-              ],
-              "OPC_Server_Port": [
-                "4843"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "APSCT_On_Off_timeout": [
-                "1"
-              ]
-            }
-          },
-          "STAT/APSCT/L1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apsct-sim"
-              ],
-              "OPC_Server_Port": [
-                "4843"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "APSCT_On_Off_timeout": [
-                "1"
-              ]
-            }
-          },
-          "STAT/APSCT/H0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apsct-sim"
-              ],
-              "OPC_Server_Port": [
-                "4843"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "APSCT_On_Off_timeout": [
-                "1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "CCD": {
-      "STAT": {
-        "CCD": {
-          "STAT/CCD/1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "ccd-sim"
-              ],
-              "OPC_Server_Port": [
-                "4843"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "CCD_On_Off_timeout": [
-                "1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "EC": {
-      "STAT": {
-        "EC": {
-          "STAT/EC/1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "ec-sim.service.consul"
-              ],
-              "OPC_Server_Port": [
-                "4850"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "OPC_Node_Path_Prefix": [
-                "3:ServerInterfaces",
-                "4:Environmental_Control"
-              ],
-              "OPC_namespace": [
-                "http://Environmental_Control"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "APSPU": {
-      "STAT": {
-        "APSPU": {
-          "STAT/APSPU/L0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apspu-sim"
-              ],
-              "OPC_Server_Port": [
-                "4842"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          },
-          "STAT/APSPU/L1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apspu-sim"
-              ],
-              "OPC_Server_Port": [
-                "4842"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          },
-          "STAT/APSPU/H0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "apspu-sim"
-              ],
-              "OPC_Server_Port": [
-                "4842"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "Beamlet": {
-      "STAT": {
-        "Beamlet": {
-          "STAT/Beamlet/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/Beamlet/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/Beamlet/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "DigitalBeam": {
-      "STAT": {
-        "DigitalBeam": {
-<<<<<<< HEAD
-          "STAT/DigitalBeam/LBA": {
-            "properties": {
-              "Beam_tracking_interval": [
-                "1.0"
-              ],
-              "Beam_tracking_preparation_period": [
-                "0.5"
-              ]
-=======
-            "STAT": {
-                "DigitalBeam": {
-                    "STAT/DigitalBeam/LBA": {
-                        "properties": {
-                            "Beam_tracking_interval": [
-                                "1.0"
-                            ],
-                            "Beam_tracking_preparation_period": [
-                                "0.5"
-                            ]
-                        }
-                    },
-                    "STAT/DigitalBeam/HBA0": {
-                        "properties": {
-                            "Beam_tracking_interval": [
-                                "1.0"
-                            ],
-                            "Beam_tracking_preparation_period": [
-                                "0.5"
-                            ]
-                        }
-                    },
-                    "STAT/DigitalBeam/HBA1": {
-                        "properties": {
-                            "AntennaField_Device": [
-                                "STAT/AFH/HBA1"
-                            ],
-                            "Beamlet_Device": [
-                                "STAT/Beamlet/HBA1"
-                            ],
-                            "Beam_tracking_interval": [
-                                "1.0"
-                            ],
-                            "Beam_tracking_preparation_period": [
-                                "0.5"
-                            ]
-                        }
-                    }
-                }
->>>>>>> master
-            }
-          },
-          "STAT/DigitalBeam/HBA0": {
-            "properties": {
-              "Beam_tracking_interval": [
-                "1.0"
-              ],
-              "Beam_tracking_preparation_period": [
-                "0.5"
-              ]
-            }
-          },
-          "STAT/DigitalBeam/HBA1": {
-            "properties": {
-              "AntennaField_Device": [
-                "STAT/AntennaField/HBA1"
-              ],
-              "Beamlet_Device": [
-                "STAT/Beamlet/HBA1"
-              ],
-              "Beam_tracking_interval": [
-                "1.0"
-              ],
-              "Beam_tracking_preparation_period": [
-                "0.5"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "TemperatureManager": {
-      "STAT": {
-        "TemperatureManager": {
-          "STAT/TemperatureManager/1": {
-            "properties": {
-              "Alarm_Error_List": [
-                "APSCT, APSCT_TEMP_error_R",
-                "APSPU, APSPU_TEMP_error_R",
-                "UNB2, UNB2_TEMP_error_R",
-                "RECVH, RECV_TEMP_error_R",
-                "RECVL, RECV_TEMP_error_R"
-              ],
-              "Shutdown_Device_List": [
-                "STAT/SDPFirmware/LBA",
-                "STAT/SDPFirmware/HBA0",
-                "STAT/SDPFirmware/HBA1",
-                "STAT/SDP/LBA",
-                "STAT/SDP/HBA0",
-                "STAT/SDP/HBA1",
-                "STAT/UNB2/L0",
-                "STAT/UNB2/L1",
-                "STAT/UNB2/H0",
-                "STAT/RECVH/H0",
-                "STAT/RECVL/L0",
-                "STAT/RECVL/L1",
-                "STAT/APSCT/L0",
-                "STAT/APSCT/L1",
-                "STAT/APSCT/H0",
-                "STAT/CCD/1",
-                "STAT/APSPU/L0",
-                "STAT/APSPU/L1",
-                "STAT/APSPU/H0"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "PCON": {
-      "STAT": {
-        "PCON": {
-          "STAT/PCON/1": {
-            "properties": {
-              "SNMP_use_simulators": [
-                "True"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "PSOC": {
-      "STAT": {
-        "PSOC": {
-          "STAT/PSOC/1": {
-            "properties": {
-              "SNMP_use_simulators": [
-                "True"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "RECVH": {
-      "STAT": {
-        "RECVH": {
-          "STAT/RECVH/H0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "recvh-sim"
-              ],
-              "OPC_Server_Port": [
-                "4844"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "RCU_On_Off_timeout": [
-                "1"
-              ],
-              "RCU_DTH_On_Off_timeout": [
-                "1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "RECVL": {
-      "STAT": {
-        "RECVL": {
-          "STAT/RECVL/L0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "recvl-sim"
-              ],
-              "OPC_Server_Port": [
-                "4845"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "RCU_On_Off_timeout": [
-                "1"
-              ],
-              "RCU_DTH_On_Off_timeout": [
-                "1"
-              ]
-            }
-          },
-          "STAT/RECVL/L1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "recvl-sim"
-              ],
-              "OPC_Server_Port": [
-                "4845"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "RCU_On_Off_timeout": [
-                "1"
-              ],
-              "RCU_DTH_On_Off_timeout": [
-                "1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "SDPFirmware": {
-      "STAT": {
-        "SDPFirmware": {
-          "STAT/SDPFirmware/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "Firmware_Boot_timeout": [
-                "1.0"
-              ]
-            }
-          },
-          "STAT/SDPFirmware/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "Firmware_Boot_timeout": [
-                "1.0"
-              ]
-            }
-          },
-          "STAT/SDPFirmware/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "Firmware_Boot_timeout": [
-                "1.0"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "SDP": {
-      "STAT": {
-        "SDP": {
-          "STAT/SDP/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          },
-          "STAT/SDP/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          },
-          "STAT/SDP/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "BST": {
-      "STAT": {
-        "BST": {
-          "STAT/BST/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/BST/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/BST/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "SST": {
-      "STAT": {
-        "SST": {
-          "STAT/SST/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/SST/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/SST/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "XST": {
-      "STAT": {
-        "XST": {
-          "STAT/XST/LBA": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/XST/HBA0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          },
-          "STAT/XST/HBA1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "sdptr-sim"
-              ],
-              "OPC_Server_Port": [
-                "4840"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB",
-                "01:23:45:67:89:AB"
-              ],
-              "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1",
-                "127.0.0.1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "UNB2": {
-      "STAT": {
-        "UNB2": {
-          "STAT/UNB2/L0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "unb2-sim"
-              ],
-              "OPC_Server_Port": [
-                "4841"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "UNB2_On_Off_timeout": [
-                "1"
-              ]
-            }
-          },
-          "STAT/UNB2/L1": {
-            "properties": {
-              "OPC_Server_Name": [
-                "unb2-sim"
-              ],
-              "OPC_Server_Port": [
-                "4841"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "UNB2_On_Off_timeout": [
-                "1"
-              ]
-            }
-          },
-          "STAT/UNB2/H0": {
-            "properties": {
-              "OPC_Server_Name": [
-                "unb2-sim"
-              ],
-              "OPC_Server_Port": [
-                "4841"
-              ],
-              "OPC_Time_Out": [
-                "5.0"
-              ],
-              "UNB2_On_Off_timeout": [
-                "1"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "TileBeam": {
-      "STAT": {
-        "TileBeam": {
-          "STAT/TileBeam/HBA0": {
-            "properties": {
-              "Tracking_enabled_RW_default": [
-                "True"
-              ]
-            }
-          },
-          "STAT/TileBeam/HBA1": {
-            "properties": {
-              "Tracking_enabled_RW_default": [
-                "True"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "StationManager": {
-      "STAT": {
-        "StationManager": {
-          "STAT/StationManager/1": {
-            "properties": {
-              "Suppress_State_Transition_Failures": [
-                "True"
-              ]
-            }
-          }
-        }
-      }
-    },
-    "ObservationControl": {
-      "STAT": {
-        "Observation": {
-          "STAT/Observation/1": {
-          }
-        },
-        "ObservationControl": {
-          "STAT/ObservationControl/1": {
-            "properties": {
-              "Power_Children": [
-                "STAT/Observation/1"
-              ],
-              "Control_Children": [
-                "STAT/Observation/1"
-              ]
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/README.md b/README.md
index 1dcb85bc833d894f5d33dea39ffb30be39fdacee..59bc8e7de288a778e68a24aa6ce56709bc79f274 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,7 @@ You will also need:
 * bash
 * dig (dnsutils package on ubuntu/debian)
 * wget
+* hexdump (bsdextrautils package on ubuntu/debian)
 
 ## Start dev environment
 
@@ -70,25 +71,6 @@ to start the dev environment including tango.
 
 Nomad is now available at http://localhost:4646/
 
-## Start dev environment
-
-For local development a dev environment is needed. To setup this environment run
-
-```
-./sbin/prepare_dev_env.sh
-```
-
-This will install `jumppad`, if not present yet as well as creating the docker volume needed to simulate the station
-nomad cluster.
-
-Afterwards run
-
-```
-jumppad up infra/dev
-```
-
-to start the dev environment including tango.
-
 ## Bootstrap
 
 The bootstrap procedure is needed only once. First we build all docker
@@ -157,10 +139,12 @@ Next change the version in the following places:
 
 # Release Notes
 
+* 0.24.0 Allow multiple antenna fields to be used in single observation,
+         This renames the `Observation` device to `ObservationField`.
 * 0.23.0 Migrate execution environment to nomad
 * 0.22.0 Split `Antennafield` in `AFL` and `AFH` devices in order to separate Low-Band
          and High-Band functionalities
-         Removed `Antenna_Type_R` attribute from antennafield devices  
+         Removed `Antenna_Type_R` attribute from antennafield devices
 * 0.21.4 Replace `ACC-MIB.mib` with `SP2-MIB.mib` source file in PCON device
 * 0.21.3 Added DigitalBeam.Antenna_Usage_Mask_R to expose antennas used in beamforming
 * 0.21.2 Removed deprecated "Boot" device (use StationManager now)
diff --git a/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
index c181931ebdb89dfbbf34c5f9576587a85818df7b..afd380aba7cbb15ec1cddcf51f8993c1aa7c3020 100644
--- a/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
+++ b/docker-compose/tango-prometheus-exporter/lofar2-fast-policy.json
@@ -30,7 +30,7 @@
         },
         "stat/docker/1": {
         },
-        "stat/observation/*":{
+        "stat/observationfield/*":{
         },
         "stat/observationcontrol/1":{
         },
diff --git a/docker-compose/tango-prometheus-exporter/lofar2-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-policy.json
index 895f6e307dddc2de5e9df3653816695fbe0b9627..ee30f469d34ce5ba7591bde2307da174b3a6050a 100644
--- a/docker-compose/tango-prometheus-exporter/lofar2-policy.json
+++ b/docker-compose/tango-prometheus-exporter/lofar2-policy.json
@@ -67,7 +67,7 @@
         },
         "stat/docker/1": {
         },
-        "stat/observation/*":{
+        "stat/observationfield/*":{
             "exclude": [
                 "saps_pointing_R",
                 "saps_subbands_R"
diff --git a/docker-compose/tango-prometheus-exporter/lofar2-slow-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-slow-policy.json
index c9aee832684042b4d73350d036cec68ecd58cf8d..56bda80f5d98a26fd746319b44a6fbc1cf67dc76 100644
--- a/docker-compose/tango-prometheus-exporter/lofar2-slow-policy.json
+++ b/docker-compose/tango-prometheus-exporter/lofar2-slow-policy.json
@@ -41,7 +41,7 @@
         },
         "stat/docker/1": {
         },
-        "stat/observation/*":{
+        "stat/observationfield/*":{
             "include": [
                 "saps_pointing_R",
                 "saps_subbands_R"
diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION
index ca222b7cf394c7e48e13308f41fb4c49478f6e12..2094a100ca8bd90c2861a5da1807755ff3608831 100644
--- a/tangostationcontrol/VERSION
+++ b/tangostationcontrol/VERSION
@@ -1 +1 @@
-0.23.0
+0.24.0
diff --git a/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json b/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json
index e37e343c4a267ac5d88f52f23c0aacf97cd78094..3a9275db45119dc9cdbbef7106d2a57b78285c3e 100644
--- a/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json
+++ b/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json
@@ -24,9 +24,9 @@
                 }
             }
         },
-        "Observation": {
+        "ObservationField": {
             "STAT": {
-                "Observation": {
+                "ObservationField": {
                 }
             }
         },
@@ -37,10 +37,10 @@
                 }
             }
         },
-        "AntennaField": {
+        "AFH": {
             "STAT": {
-                "AntennaField": {
-                    "STAT/AntennaField/HBA": {
+                "AFH": {
+                    "STAT/AFH/HBA": {
                         "properties": {
                             "Control_Children": ["STAT/RECVH/1"]
                         }
diff --git a/tangostationcontrol/integration_test/configuration/configDB/dummy_positions_ConfigDb.json b/tangostationcontrol/integration_test/configuration/configDB/dummy_positions_ConfigDb.json
index 7758a30d8ad45a0b67effe7b38f5a4812025929c..17a3aa6c27d1ac132f7d2e9a6ea8f30b4dfa1c45 100644
--- a/tangostationcontrol/integration_test/configuration/configDB/dummy_positions_ConfigDb.json
+++ b/tangostationcontrol/integration_test/configuration/configDB/dummy_positions_ConfigDb.json
@@ -1,9 +1,9 @@
 {
     "servers": {
-        "AntennaField": {
+        "AFH": {
             "STAT": {
-                "AntennaField": {
-                    "STAT/AntennaField/HBA": {
+                "AFH": {
+                    "STAT/AFH/HBA": {
                         "properties": {
                             "Control_to_RECV_mapping": [
                                 "1",  "0",
diff --git a/tangostationcontrol/integration_test/configuration/configDB/test_environment_ConfigDb.json b/tangostationcontrol/integration_test/configuration/configDB/test_environment_ConfigDb.json
index 17ce2a7f58083db9dc31b5b39b34c81bda50cd56..3e574fddfb0f2aeda2b9ee23ccc7816f4d56b275 100644
--- a/tangostationcontrol/integration_test/configuration/configDB/test_environment_ConfigDb.json
+++ b/tangostationcontrol/integration_test/configuration/configDB/test_environment_ConfigDb.json
@@ -10,13 +10,6 @@
                     }
                 }
             }
-        },
-        "ObservationControl": {
-            "STAT": {
-                "Observation": {
-                    "STAT/Observation/1": {}
-                }
-            }
         }
     }
 }
diff --git a/tangostationcontrol/integration_test/configuration/test_device_configuration.py b/tangostationcontrol/integration_test/configuration/test_device_configuration.py
index 78c3044b9f3f14f7034089a6e500eafca302f87f..0f998b8f74d23ec97b8b803291f7c249914e21e8 100644
--- a/tangostationcontrol/integration_test/configuration/test_device_configuration.py
+++ b/tangostationcontrol/integration_test/configuration/test_device_configuration.py
@@ -22,10 +22,10 @@ INITIAL_CONFIGURATION = None
 class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
     TEST_CONFIGURATION = """{
                             "servers": {
-                                "AntennaField": {
+                                "AFH": {
                                     "STAT": {
-                                        "AntennaField": {
-                                            "STAT/AntennaField/HBA": {
+                                        "AFH": {
+                                            "STAT/AFH/HBA": {
                                                 "properties": {
                                                     "Control_Children": [ "STAT/MOCKRCU/1" ]
                                                 }
@@ -35,8 +35,8 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
                                 },
                                 "ObservationControl": {
                                     "STAT": {
-                                        "Observation": {
-                                            "STAT/Observation/11": {}
+                                        "ObservationControl": {
+                                            "STAT/ObservationControl/11": {}
                                         }
                                     }
                                 }
@@ -148,20 +148,19 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
                 msg=f"{dbdata}",
             )  # configuration device
             self.assertFalse(
-                "stat/observation/11"
-                in dbdata["servers"]["observationcontrol"]["stat"]["observation"],
+                "stat/observationcontrol/11"
+                in dbdata["servers"]["observationcontrol"]["stat"][
+                    "observationcontrol"
+                ],
                 msg=f"{dbdata}",
             )  # observation device
             self.assertTrue(
-                "stat/antennafield/hba"
-                in dbdata["servers"]["antennafield"]["stat"]["antennafield"],
+                "stat/afh/hba" in dbdata["servers"]["afh"]["stat"]["afh"],
                 msg=f"{dbdata}",
             )  # antennafield device
 
             antennafield_properties = CaseInsensitiveDict(
-                dbdata["servers"]["antennafield"]["stat"]["antennafield"][
-                    "stat/antennafield/hba"
-                ]["properties"]
+                dbdata["servers"]["afh"]["stat"]["afh"]["stat/afh/hba"]["properties"]
             )
 
             self.assertIn(
@@ -183,9 +182,9 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
             )
             # Test whether new device has been added
             self.assertTrue(
-                "stat/observation/11"
+                "stat/observationcontrol/11"
                 in updated_dbdata["servers"]["observationcontrol"]["stat"][
-                    "observation"
+                    "observationcontrol"
                 ],
                 msg=f"{updated_dbdata}",
             )  # observation device
@@ -195,15 +194,14 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
             )  # recvh device
             # Test whether old attribute has been updated
             self.assertTrue(
-                "stat/antennafield/hba"
-                in updated_dbdata["servers"]["antennafield"]["stat"]["antennafield"],
+                "stat/afh/hba" in updated_dbdata["servers"]["afh"]["stat"]["afh"],
                 msg=f"{updated_dbdata}",
             )
 
             antennafield_properties = CaseInsensitiveDict(
-                updated_dbdata["servers"]["antennafield"]["stat"]["antennafield"][
-                    "stat/antennafield/hba"
-                ]["properties"]
+                updated_dbdata["servers"]["afh"]["stat"]["afh"]["stat/afh/hba"][
+                    "properties"
+                ]
             )
 
             self.assertIn(
@@ -241,8 +239,10 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
         )
         # Test whether new device has been added
         self.assertTrue(
-            "stat/observation/11"
-            in updated_dbdata["servers"]["observationcontrol"]["stat"]["observation"],
+            "stat/observationcontrol/11"
+            in updated_dbdata["servers"]["observationcontrol"]["stat"][
+                "observationcontrol"
+            ],
             msg=f"{updated_dbdata}",
         )  # observation device
         # Test whether old device has NOT been deleted
@@ -252,14 +252,13 @@ class TestDeviceConfiguration(AbstractTestBases.TestDeviceBase):
         )  # recvh device
         # Test whether old attribute has been updated
         self.assertTrue(
-            "stat/antennafield/hba"
-            in updated_dbdata["servers"]["antennafield"]["stat"]["antennafield"],
+            "stat/afh/hba" in updated_dbdata["servers"]["afh"]["stat"]["afh"],
             msg=f"{updated_dbdata}",
         )
         antennafield_properties = CaseInsensitiveDict(
-            updated_dbdata["servers"]["antennafield"]["stat"]["antennafield"][
-                "stat/antennafield/hba"
-            ]["properties"]
+            updated_dbdata["servers"]["afh"]["stat"]["afh"]["stat/afh/hba"][
+                "properties"
+            ]
         )
 
         self.assertIn(
diff --git a/tangostationcontrol/integration_test/default/common/test_configuration.py b/tangostationcontrol/integration_test/default/common/test_configuration.py
index c2b5b3ed70af955f54cdbf66ebdda37d16a6467a..6b6a8e2f1159c923c0c7eb9eb8b03c6e0a6e6a1c 100644
--- a/tangostationcontrol/integration_test/default/common/test_configuration.py
+++ b/tangostationcontrol/integration_test/default/common/test_configuration.py
@@ -16,10 +16,10 @@ class TestStationConfiguration(BaseIntegrationTestCase):
 
     TEST_CONFIGURATION = """{
                             "servers": {
-                                "AntennaField": {
+                                "AFH": {
                                     "STAT": {
-                                        "AntennaField": {
-                                            "STAT/AntennaField/1": {
+                                        "AFH": {
+                                            "STAT/AFH/1": {
                                                 "properties": {
                                                     "Control_Children": [ "STAT/MOCKRCU/1" ]
                                                 }
@@ -154,9 +154,9 @@ class TestStationConfiguration(BaseIntegrationTestCase):
         self.assertTrue(self.check_configuration_validity(self.TEST_CONFIGURATION))
         # Insert invalid field
         json_test_configuration = json.loads(self.TEST_CONFIGURATION)
-        invalid_configuration = json_test_configuration["servers"]["AntennaField"][
-            "STAT"
-        ]["AntennaField"]["STAT/AntennaField/1"]["new_field"] = "test"
+        invalid_configuration = json_test_configuration["servers"]["AFH"]["STAT"][
+            "AFH"
+        ]["STAT/AFH/1"]["new_field"] = "test"
         self.assertFalse(
             self.check_configuration_validity(json.dumps(invalid_configuration))
         )
diff --git a/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py b/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py
index b65e62870dcf2eba5977395b95f0dd80ccae59e0..b49b1902ad71cc3456cbd64def017ffad45fc801 100644
--- a/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py
+++ b/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py
@@ -4,7 +4,6 @@
 import time
 import numpy
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 from tango import DevState
 
@@ -28,7 +27,9 @@ from tangostationcontrol.devices.base_device_classes.antennafield_device import
 
 class TestHBADevice(AbstractTestBases.TestDeviceBase):
     def setUp(self):
-        self.stationmanager_proxy = self.setup_stationmanager_proxy()
+        self.stationmanager_proxy = self.setup_proxy("STAT/StationManager/1")
+
+        # Setup will dump current properties and restore them for us
         super().setUp("STAT/AFH/HBA0")
 
         # Typical tests emulate 'CS001_TILES' number of antennas in
@@ -48,66 +49,15 @@ class TestHBADevice(AbstractTestBases.TestDeviceBase):
                 ).flatten(),
             }
         )
-        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)
-        self.addCleanup(self.shutdown_sdp)
+        self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True)
+        self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0")
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0")
 
         # configure the frequencies, which allows access
         # to the calibration attributes and commands
         self.sdpfirmware_proxy.clock_RW = CLK_160_MHZ
         self.recv_proxy.RCU_band_select_RW = [[1] * N_rcu_inp] * N_rcu
 
-    def restore_antennafield(self):
-        self.proxy.put_property(
-            {
-                "Power_to_RECV_mapping": [-1, -1] * CS001_TILES,
-                "Control_to_RECV_mapping": [-1, -1] * CS001_TILES,
-            }
-        )
-
-    @staticmethod
-    def shutdown_recv():
-        recv_proxy = TestDeviceProxy("STAT/RECVH/H0")
-        recv_proxy.off()
-
-    @staticmethod
-    def shutdown_sdp():
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-
-    def setup_recv_proxy(self):
-        # setup RECV
-        recv_proxy = TestDeviceProxy("STAT/RECVH/H0")
-        recv_proxy.off()
-        recv_proxy.boot()
-        recv_proxy.set_defaults()
-        return recv_proxy
-
-    def setup_sdpfirmware_proxy(self):
-        # setup SDPFirmware
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        return sdpfirmware_proxy
-
-    def setup_sdp_proxy(self):
-        # setup SDP
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        return sdp_proxy
-
-    def setup_stationmanager_proxy(self):
-        """Setup StationManager"""
-        stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1")
-        stationmanager_proxy.off()
-        stationmanager_proxy.boot()
-        self.assertEqual(stationmanager_proxy.state(), DevState.ON)
-        return stationmanager_proxy
-
     def test_ANT_mask_RW_configured_after_Antenna_Usage_Mask(self):
         """Verify if ANT_mask_RW values are correctly configured from Antenna_Usage_Mask values"""
 
diff --git a/tangostationcontrol/integration_test/default/devices/base.py b/tangostationcontrol/integration_test/default/devices/base.py
index 8b3d44d12d35ca34286a907c5b6b2c8e1c8b13b4..d8afd1004b5f1daf8073fd1598b836d47ad0ff5c 100644
--- a/tangostationcontrol/integration_test/default/devices/base.py
+++ b/tangostationcontrol/integration_test/default/devices/base.py
@@ -1,5 +1,6 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
+from typing import Callable
 
 from tango._tango import DevState, AttrWriteType
 
@@ -29,19 +30,56 @@ class AbstractTestBases:
 
             # make a backup of the properties, in case they're changed
             # NB: "or {}" is needed to deal with devices that have no properties.
-            self.original_properties = (
-                self.proxy.get_property(self.proxy.get_property_list("*") or {}) or {}
-            )
+            self.original_properties = self.current_properties()
 
             self.addCleanup(TestDeviceProxy.test_device_turn_off, self.name)
             self.addCleanup(self.restore_properties)
 
             super().setUp()
 
-        def restore_properties(self):
+        def current_properties(self):
+            """Return the current properties of self.proxy"""
+            return (
+                self.proxy.get_property(self.proxy.get_property_list("*") or {}) or {}
+            )
+
+        def setup_proxy(
+            self,
+            name: str,
+            defaults: bool = False,
+            restore_properties: bool = False,
+            cb: Callable[[TestDeviceProxy], None] = None,
+        ):
+            """Setup A TestDeviceProxy and handle cleanup"""
+            proxy = TestDeviceProxy(name)
+
+            if restore_properties:
+                self.addCleanup(proxy.test_device_turn_off, name)
+                self.addCleanup(self.restore_properties, self.current_properties())
+
+            if cb:
+                cb(proxy)
+
+            proxy.off()
+            proxy.boot()
+
+            if defaults:
+                proxy.set_defaults()
+
+            self.assertEqual(proxy.state(), DevState.ON)
+
+            if not restore_properties:
+                self.addCleanup(proxy.test_device_turn_off, name)
+
+            return proxy
+
+        def restore_properties(self, properties=None):
             """Restore the properties as they were before the test."""
 
-            self.proxy.put_property(self.original_properties)
+            if not properties:
+                properties = self.original_properties
+
+            self.proxy.put_property(properties)
 
         def test_device_fetch_state(self):
             """Test if we can successfully fetch state"""
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py b/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py
index ecfd45b55ed04e4033c6d3ea3202220f67299bd7..b07ea727cb8c60aabae2a3772cca3a109d0742de 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py
@@ -7,7 +7,6 @@ from ctypes import c_short
 import numpy
 import numpy.testing
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 from tango import DevState
@@ -26,42 +25,15 @@ class TestDeviceBeamlet(AbstractTestBases.TestDeviceBase):
         """Intentionally recreate the device object in each test"""
         super().setUp("STAT/Beamlet/HBA0")
 
-    def test_device_read_all_attributes(self):
-        # We need to connect to SDP first to read some of our attributes
-        self.sdp_proxy = self.setup_sdp()
-        self.sdpfirmware_proxy = self.setup_sdpfirmware()
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0", defaults=True)
+        self.sdp_proxy.nyquist_zone_RW = [[2] * S_pn] * N_pn
 
-        super().test_device_read_all_attributes()
-
-    def setup_sdp(self, clock=CLK_200_MHZ):
-        # setup SDP, on which this device depends
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        sdp_proxy.set_defaults()
-
-        # setup the frequencies as expected in the test
-        sdp_proxy.nyquist_zone_RW = [[2] * S_pn] * N_pn
-
-        return sdp_proxy
-
-    def setup_sdpfirmware(self, clock=CLK_200_MHZ):
-        # setup SDP, on which this device depends
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        sdpfirmware_proxy.set_defaults()
-
-        # setup the frequencies as expected in the test
-        sdpfirmware_proxy.clock_RW = clock
-
-        return sdpfirmware_proxy
+        self.sdpfirmware_proxy = self.setup_proxy(
+            "STAT/SDPFirmware/HBA0", defaults=True
+        )
+        self.sdpfirmware_proxy.clock_RW = CLK_200_MHZ
 
     def test_pointing_to_zenith(self):
-        # Setup configuration
-        sdp_proxy = self.setup_sdp()
-        sdpfirmware_proxy = self.setup_sdpfirmware()
-
         self.proxy.initialise()
         self.proxy.on()
 
@@ -82,10 +54,6 @@ class TestDeviceBeamlet(AbstractTestBases.TestDeviceBase):
         numpy.testing.assert_almost_equal(expected_bf_weights, calculated_bf_weights)
 
     def test_subband_select_change(self):
-        # Setup configuration
-        sdp_proxy = self.setup_sdp()
-        sdpfirmware_proxy = self.setup_sdpfirmware()
-
         # Change subband
         self.proxy.off()
         self.proxy.initialise()
@@ -110,10 +78,7 @@ class TestDeviceBeamlet(AbstractTestBases.TestDeviceBase):
         )
 
     def test_sdp_clock_change(self):
-        # Setup configuration
-        sdp_proxy = self.setup_sdp()
-        sdpfirmware_proxy = self.setup_sdpfirmware()
-
+        sdpfirmware_proxy = self.sdpfirmware_proxy
         self.proxy.initialise()
         self.proxy.subband_select_RW = numpy.array(
             list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)),
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_bst.py b/tangostationcontrol/integration_test/default/devices/test_device_bst.py
index ccab06aa504b6f77fb2e3e22e304a4416747f7e3..5b19450db75fe59312c4701dd573d6c5631b9f28 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_bst.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_bst.py
@@ -1,7 +1,6 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 
@@ -12,14 +11,7 @@ class TestDeviceBST(AbstractTestBases.TestDeviceBase):
 
     def test_device_read_all_attributes(self):
         # We need to connect to SDP first to read some of our attributes
-        self.sdp_proxy = self.setup_sdp()
+        self.setup_proxy("STAT/sdpfirmware/HBA0", defaults=True)
+        self.setup_proxy("STAT/SDP/HBA0", defaults=True)
 
         super().test_device_read_all_attributes()
-
-    def setup_sdp(self):
-        # setup SDP, on which this device depends
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        sdp_proxy.set_defaults()
-        return sdp_proxy
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_calibration.py b/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
index 9629ece0342cf802723f5d7f9c8146aae738003d..0b3c7e93cd0e03dcb8a97e454da15ecf19206745 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_calibration.py
@@ -2,7 +2,6 @@
 #  SPDX-License-Identifier: Apache-2.0
 
 import numpy
-from tango import DevState
 
 from tangostationcontrol.common.constants import (
     N_rcu,
@@ -11,7 +10,6 @@ from tangostationcontrol.common.constants import (
     CLK_200_MHZ,
 )
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 
@@ -68,32 +66,30 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
     ]
 
     def setUp(self):
-        self.stationmanager_proxy = self.setup_stationmanager_proxy()
-        super().setUp("STAT/Calibration/1")
-
-        self.recv_proxy = self.setup_recv_proxy()
-        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
-        self.sdp_proxy = self.setup_sdp_proxy()
+        self.stationmanager_proxy = self.setup_proxy("STAT/StationManager/1")
 
-        self.antennafield_proxy = self.setup_proxy("STAT/AFH/HBA0")
+        super().setUp("STAT/Calibration/1")
 
-        # make sure we restore any properties we modify
-        self.original_antennafield_properties = self.antennafield_proxy.get_property(
-            self.antennafield_proxy.get_property_list("*")
-        )
-        self.addCleanup(self.restore_antennafield)
-
-        self.antennafield_proxy.put_property(
-            {
-                "Power_to_RECV_mapping": [1, 1, 1, 0]
-                + [-1] * ((DEFAULT_N_HBA_TILES * 2) - 4),
-                "Antenna_Sets": ["ALL"],
-                "Antenna_Set_Masks": ["1" * DEFAULT_N_HBA_TILES],
-                "Frequency_Band_RW_default": ["HBA_110_190"]
-                * (DEFAULT_N_HBA_TILES * 2),
-            }
+        self.recv_proxy = self.setup_proxy("STAT/RECVH/H0")
+        self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0")
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0")
+
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0",
+            restore_properties=True,
+            cb=lambda x: {
+                x.put_property(
+                    {
+                        "Power_to_RECV_mapping": [1, 1, 1, 0]
+                        + [-1] * ((DEFAULT_N_HBA_TILES * 2) - 4),
+                        "Antenna_Sets": ["ALL"],
+                        "Antenna_Set_Masks": ["1" * DEFAULT_N_HBA_TILES],
+                        "Frequency_Band_RW_default": ["HBA_110_190"]
+                        * (DEFAULT_N_HBA_TILES * 2),
+                    }
+                )
+            },
         )
-        self.antennafield_proxy.boot()
 
         # configure the frequencies, which allows access
         # to the calibration attributes and commands
@@ -103,45 +99,6 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
     def restore_antennafield(self):
         self.proxy.put_property(self.original_antennafield_properties)
 
-    @staticmethod
-    def shutdown(device: str):
-        def off():
-            proxy = TestDeviceProxy(device)
-            proxy.off()
-
-        return off
-
-    def setup_recv_proxy(self):
-        # setup RECV
-        recv_proxy = self.setup_proxy("STAT/RECVH/H0")
-        recv_proxy.boot()
-        return recv_proxy
-
-    def setup_sdpfirmware_proxy(self):
-        # setup SDP
-        sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.boot()
-        return sdpfirmware_proxy
-
-    def setup_sdp_proxy(self):
-        # setup SDP
-        sdp_proxy = self.setup_proxy("STAT/SDP/HBA0")
-        sdp_proxy.boot()
-        return sdp_proxy
-
-    def setup_proxy(self, dev: str):
-        proxy = TestDeviceProxy(dev)
-        proxy.off()
-        self.addCleanup(self.shutdown(dev))
-        return proxy
-
-    def setup_stationmanager_proxy(self):
-        """Setup StationManager"""
-        stationmanager_proxy = self.setup_proxy("STAT/StationManager/1")
-        stationmanager_proxy.boot()
-        self.assertEqual(stationmanager_proxy.state(), DevState.ON)
-        return stationmanager_proxy
-
     def test_calibrate_recv(self):
         calibration_properties = {
             "Antenna_Cables": ["50m", "80m"] * (DEFAULT_N_HBA_TILES // 2),
@@ -155,9 +112,10 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
             "Frequency_Band_RW_default": ["HBA_110_190"] * (DEFAULT_N_HBA_TILES * 2),
         }
 
-        self.antennafield_proxy = self.setup_proxy("STAT/AFH/HBA0")
-        self.antennafield_proxy.put_property(calibration_properties)
-        self.antennafield_proxy.boot()
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0",
+            cb=lambda x: {x.put_property(calibration_properties)},
+        )
 
         self.proxy.boot()
 
@@ -215,9 +173,10 @@ class TestCalibrationDevice(AbstractTestBases.TestDeviceBase):
             "Frequency_Band_RW_default": ["HBA_110_190"] * (DEFAULT_N_HBA_TILES * 2),
         }
 
-        self.antennafield_proxy = self.setup_proxy("STAT/AFH/HBA0")
-        self.antennafield_proxy.put_property(calibration_properties)
-        self.antennafield_proxy.boot()
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0",
+            cb=lambda x: {x.put_property(calibration_properties)},
+        )
 
         self.proxy.boot()
 
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py b/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
index 428b151885c789d7a9def82b4a1671c2021a7f9c..ac26002f382fba3114cbf9ae850a122a1d0b9f92 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py
@@ -47,84 +47,46 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
 
         self.proxy.put_property({"Beamlet_Select": [True] * N_beamlets_ctrl})
 
-        self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden)
-        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.recv_proxy = self.setup_proxy(self.recv_iden, defaults=True)
+        self.sdpfirmware_proxy = self.setup_proxy(self.sdpfirmware_iden, defaults=True)
+        self.sdp_proxy = self.setup_proxy(self.sdp_iden)
         self.beamlet_proxy = self.initialise_beamlet_proxy()
 
-    def setup_recv_proxy(self):
-        recv_proxy = TestDeviceProxy(self.recv_iden)
-        recv_proxy.off()
-        recv_proxy.boot()
-        recv_proxy.set_defaults()
-        return recv_proxy
-
-    def initialise_beamlet_proxy(self):
-        beamlet_proxy = TestDeviceProxy(self.beamlet_iden)
-        beamlet_proxy.off()
-        beamlet_proxy.initialise()
-        return beamlet_proxy
-
-    def setup_beamlet_proxy(self):
-        beamlet_proxy = TestDeviceProxy(self.beamlet_iden)
-        beamlet_proxy.off()
-        beamlet_proxy.boot()
-        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.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)
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        sdp_proxy.set_defaults()
-        return sdp_proxy
-
-    def setup_antennafield_proxy(self, antenna_qualities, antenna_use):
-        # setup AntennaField
         NR_TILES = CS001_TILES
-        antennafield_proxy = TestDeviceProxy(self.antennafield_iden)
         control_mapping = [[1, i] for i in range(NR_TILES)]
         sdp_mapping = [[i // 6, i % 6] for i in range(NR_TILES)]
-        antennafield_proxy.put_property(
-            {
-                "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(),
-                "Antenna_to_SDP_Mapping": numpy.array(sdp_mapping).flatten(),
-                "Antenna_Quality": antenna_qualities,
-                "Antenna_Use": antenna_use,
-                "Antenna_Cables": ["50m", "80m"] * (CS001_TILES // 2),
-                "Antenna_Sets": ["FIRST", "ALL"],
-                "Antenna_Set_Masks": [
-                    "1" + ("0" * (NR_TILES - 1)),
-                    "1" * NR_TILES,
-                ],
-            }
+        self.antennafield_proxy = self.setup_proxy(
+            self.antennafield_iden,
+            cb=lambda x: {
+                x.put_property(
+                    {
+                        "Control_to_RECV_mapping": numpy.array(
+                            control_mapping
+                        ).flatten(),
+                        "Antenna_to_SDP_Mapping": numpy.array(sdp_mapping).flatten(),
+                        "Antenna_Quality": self.antenna_qualities_ok,
+                        "Antenna_Use": self.antenna_use_ok,
+                        "Antenna_Cables": ["50m", "80m"] * (CS001_TILES // 2),
+                        "Antenna_Sets": ["FIRST", "ALL"],
+                        "Antenna_Set_Masks": [
+                            "1" + ("0" * (NR_TILES - 1)),
+                            "1" * NR_TILES,
+                        ],
+                        "Antenna_Type": "HBA",
+                    }
+                )
+            },
         )
-        antennafield_proxy.off()
-        antennafield_proxy.boot()
-        return antennafield_proxy
 
-    def test_pointing_to_zenith_clock_change(self):
         self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden)
-        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)
+    def initialise_beamlet_proxy(self):
+        beamlet_proxy = TestDeviceProxy(self.beamlet_iden)
+        beamlet_proxy.off()
+        beamlet_proxy.initialise()
+        return beamlet_proxy
 
-        self.beamlet_proxy = self.initialise_beamlet_proxy()
+    def test_pointing_to_zenith_clock_change(self):
         self.beamlet_proxy.on()
 
         # Set first (default) clock configuration
@@ -160,15 +122,6 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
         )
 
     def test_pointing_to_zenith_subband_change(self):
-        self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden)
-        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)
-
-        self.beamlet_proxy = self.initialise_beamlet_proxy()
         self.beamlet_proxy.subband_select_RW = numpy.array(
             list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)),
             dtype=numpy.uint32,
@@ -207,16 +160,6 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
     def test_set_pointing_masked_enable(self):
         """Verify that only selected inputs are written"""
 
-        self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden)
-        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
-        )
-
         self.proxy.initialise()
         self.proxy.Tracking_enabled_RW = False
         self.proxy.on()
@@ -261,14 +204,6 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase):
     def test_beam_tracking_90_percent_interval(self):
         """Verify that the beam tracking operates within 95% of interval"""
 
-        self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden)
-        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)
-
         self.proxy.initialise()
         self.proxy.Tracking_enabled_RW = True
         self.proxy.on()
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py b/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
index 2e65a37b4f54ff82c23cf678ed34e342cdd4d59f..772ab495c57464cefed3b46b63ceb972ce5bde9e 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py
@@ -2,17 +2,22 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import json
+import logging
 from datetime import datetime
 from datetime import timedelta
 
 import numpy
-
-from integration_test.device_proxy import TestDeviceProxy
-from integration_test.default.devices.base import AbstractTestBases
 from tango import DevFailed
 from tango import DevState
+
 from tangostationcontrol.common.constants import CS001_TILES
-from tangostationcontrol.test.devices.test_observation_base import TestObservationBase
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_hba_immediate,
+)
+
+from integration_test.default.devices.base import AbstractTestBases
+
+logger = logging.getLogger()
 
 
 class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
@@ -67,81 +72,44 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         "5",
     ]
 
+    VALID_JSON = get_observation_settings_hba_immediate().to_json()
+    EXPECTED_OBS_ID = json.loads(VALID_JSON)["antenna_fields"][0]["observation_id"]
+
     def setUp(self):
         super().setUp("STAT/ObservationControl/1")
-        self.VALID_JSON = TestObservationBase.VALID_JSON
-        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()
-
-    def setup_recv_proxy(self):
-        # setup RECV
-        recv_proxy = TestDeviceProxy("STAT/RECVH/H0")
-        recv_proxy.off()
-        recv_proxy.boot()
-        recv_proxy.set_defaults()
-        return recv_proxy
-
-    def setup_sdpfirmware_proxy(self):
-        # setup SDPFirmware
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        return sdpfirmware_proxy
-
-    def setup_sdp_proxy(self):
-        # setup SDP
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        return sdp_proxy
-
-    def setup_antennafield_proxy(self):
-        # setup AntennaField
-        antennafield_proxy = TestDeviceProxy("STAT/AFH/HBA0")
+        self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True)
+        self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0")
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0")
+
         control_mapping = [[1, i] for i in range(CS001_TILES)]
-        antennafield_proxy.put_property(
-            {
-                "Antenna_Set": "ALL",
-                "Power_to_RECV_mapping": numpy.array(control_mapping).flatten(),
-                "Antenna_to_SDP_Mapping": self.ANTENNA_TO_SDP_MAPPING,
-            }
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0",
+            defaults=True,
+            restore_properties=True,
+            cb=lambda x: {
+                x.put_property(
+                    {
+                        "Antenna_Set": "ALL",
+                        "Power_to_RECV_mapping": numpy.array(control_mapping).flatten(),
+                        "Antenna_to_SDP_Mapping": self.ANTENNA_TO_SDP_MAPPING,
+                    }
+                )
+            },
         )
-        antennafield_proxy.off()
-        antennafield_proxy.boot()
-        antennafield_proxy.set_defaults()
-        return antennafield_proxy
-
-    def setup_beamlet_proxy(self):
-        # setup Digitalbeam
-        beamlet_proxy = TestDeviceProxy("STAT/Beamlet/HBA0")
-        beamlet_proxy.off()
-        beamlet_proxy.boot()
-        beamlet_proxy.set_defaults()
-        return beamlet_proxy
-
-    def setup_digitalbeam_proxy(self):
-        # setup Digitalbeam
-        digitalbeam_proxy = TestDeviceProxy("STAT/DigitalBeam/HBA0")
-        digitalbeam_proxy.off()
-        digitalbeam_proxy.boot()
-        digitalbeam_proxy.set_defaults()
-        return digitalbeam_proxy
-
-    def setup_tilebeam_proxy(self):
-        # Setup Tilebeam
-        tilebeam_proxy = TestDeviceProxy("STAT/TileBeam/HBA0")
-        tilebeam_proxy.off()
-        tilebeam_proxy.boot()
-        tilebeam_proxy.set_defaults()
-        return tilebeam_proxy
+
+        self.beamlet_proxy = self.setup_proxy("STAT/Beamlet/HBA0", defaults=True)
+        self.digitalbeam_proxy = self.setup_proxy(
+            "STAT/DigitalBeam/HBA0", defaults=True
+        )
+        self.tilebeam_proxy = self.setup_proxy("STAT/TileBeam/HBA0", defaults=True)
 
     def on_device_assert(self, proxy):
-        """Transition the device to ON and assert intermediate states"""
+        """Transition the device to ON and assert intermediate states
+
+        This will repeatedly call ``stop_all_observations_now`` in turn calling
+        ``_destroy_all_observation_field_devices`` cleaning the Database from exported
+        devices
+        """
 
         proxy.Off()
         self.assertEqual(DevState.OFF, proxy.state())
@@ -159,33 +127,9 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
 
         self.on_device_assert(self.proxy)
         self.assertFalse(self.proxy.is_any_observation_running())
-        self.assertFalse(self.proxy.is_observation_running(12345))
+        self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
         self.assertFalse(self.proxy.is_observation_running(54321))
 
-    def test_check_and_convert_parameters_invalid_id(self):
-        """Test invalid parameter detection"""
-
-        parameters = json.loads(self.VALID_JSON)
-        parameters["observation_id"] = -1
-
-        self.on_device_assert(self.proxy)
-        self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters))
-
-    def test_check_and_convert_parameters_invalid_time(self):
-        """Test invalid parameter detection"""
-
-        parameters = json.loads(self.VALID_JSON)
-        parameters["stop_time"] = (datetime.now() - timedelta(seconds=1)).isoformat()
-
-        self.on_device_assert(self.proxy)
-        self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters))
-
-    def test_check_and_convert_parameters_invalid_empty(self):
-        """Test empty parameter detection"""
-
-        self.on_device_assert(self.proxy)
-        self.assertRaises(DevFailed, self.proxy.add_observation, "{}")
-
     def test_add_observation_now(self):
         """Test starting an observation now"""
 
@@ -194,9 +138,9 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         self.proxy.add_observation(self.VALID_JSON)
 
         self.assertTrue(self.proxy.is_any_observation_running())
-        self.assertTrue(self.proxy.is_observation_running(12345))
+        self.assertTrue(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
 
-        self.proxy.stop_observation_now(12345)
+        self.proxy.stop_observation_now(self.EXPECTED_OBS_ID)
 
     def test_add_observation_future(self):
         """Test starting an observation in the future"""
@@ -204,22 +148,28 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         self.on_device_assert(self.proxy)
 
         parameters = json.loads(self.VALID_JSON)
-        parameters["start_time"] = (datetime.now() + timedelta(days=1)).isoformat()
-        parameters["stop_time"] = (datetime.now() + timedelta(days=2)).isoformat()
+        for antenna_field in parameters["antenna_fields"]:
+            antenna_field["start_time"] = (
+                datetime.now() + timedelta(days=1)
+            ).isoformat()
+            antenna_field["stop_time"] = (
+                datetime.now() + timedelta(days=2)
+            ).isoformat()
+
         self.proxy.add_observation(json.dumps(parameters))
 
-        self.assertIn(12345, self.proxy.observations_R)
-        self.assertNotIn(12345, self.proxy.running_observations_R)
+        self.assertIn(self.EXPECTED_OBS_ID, self.proxy.observations_R)
+        self.assertNotIn(self.EXPECTED_OBS_ID, self.proxy.running_observations_R)
         self.assertFalse(self.proxy.is_any_observation_running())
-        self.assertFalse(self.proxy.is_observation_running(12345))
+        self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
 
-        self.proxy.stop_observation_now(12345)
+        self.proxy.stop_observation_now(self.EXPECTED_OBS_ID)
 
     def test_add_observation_multiple(self):
         """Test starting multiple observations"""
 
         second_observation_json = json.loads(self.VALID_JSON)
-        second_observation_json["observation_id"] = 54321
+        second_observation_json["antenna_fields"][0]["observation_id"] = 54321
 
         self.on_device_assert(self.proxy)
 
@@ -227,10 +177,10 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
         self.proxy.add_observation(json.dumps(second_observation_json))
 
         self.assertTrue(self.proxy.is_any_observation_running())
-        self.assertTrue(self.proxy.is_observation_running(12345))
+        self.assertTrue(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
         self.assertTrue(self.proxy.is_observation_running(54321))
 
-        self.proxy.stop_observation_now(12345)
+        self.proxy.stop_observation_now(self.EXPECTED_OBS_ID)
         self.proxy.stop_observation_now(54321)
 
     def test_stop_observation_invalid_id(self):
@@ -265,31 +215,60 @@ class TestObservationControlDevice(AbstractTestBases.TestDeviceBase):
 
         # uses ID 12345
         self.proxy.add_observation(self.VALID_JSON)
-        self.proxy.stop_observation_now(12345)
+        self.proxy.stop_observation_now(self.EXPECTED_OBS_ID)
 
         # Test false
-        self.assertFalse(self.proxy.is_observation_running(12345))
+        self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
 
     def test_start_multi_stop_all_observation(self):
         """Test starting and stopping multiple observations"""
 
         second_observation_json = json.loads(self.VALID_JSON)
-        second_observation_json["observation_id"] = 54321
+        second_observation_json["antenna_fields"][0]["observation_id"] = 54321
 
         self.on_device_assert(self.proxy)
 
-        # uses ID 12345
+        # uses ID 5
         self.proxy.add_observation(self.VALID_JSON)
         self.proxy.add_observation(json.dumps(second_observation_json))
         self.proxy.stop_all_observations_now()
 
         # Test false
-        self.assertFalse(self.proxy.is_observation_running(12345))
+        self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID))
         self.assertFalse(self.proxy.is_observation_running(54321))
 
+    def test_check_and_convert_parameters_invalid_id(self):
+        """Test invalid parameter detection"""
+
+        parameters = json.loads(self.VALID_JSON)
+        for station in parameters["antenna_fields"]:
+            station["observation_id"] = -1
+
+        self.on_device_assert(self.proxy)
+        self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters))
+
+    def test_check_and_convert_parameters_invalid_empty(self):
+        """Test empty parameter detection"""
+
+        self.on_device_assert(self.proxy)
+        self.assertRaises(DevFailed, self.proxy.add_observation, "{}")
+
     def test_check_and_convert_parameters_invalid_antenna_set(self):
         """Test invalid antenna set name"""
         parameters = json.loads(self.VALID_JSON)
-        parameters["antenna_set"] = "ZZZ"
+        for station in parameters["antenna_fields"]:
+            station["antenna_set"] = "ZZZ"
+        self.on_device_assert(self.proxy)
+        self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters))
+
+    def test_check_and_convert_parameters_invalid_time(self):
+        """Test invalid parameter detection"""
+
+        parameters = json.loads(self.VALID_JSON)
+        for antenna_field in parameters["antenna_fields"]:
+            antenna_field["stop_time"] = (
+                datetime.now() - timedelta(seconds=1)
+            ).isoformat()
+
         self.on_device_assert(self.proxy)
         self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters))
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation.py b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py
similarity index 73%
rename from tangostationcontrol/integration_test/default/devices/test_device_observation.py
rename to tangostationcontrol/integration_test/default/devices/test_device_observation_field.py
index 8dd06ebca2a9fa72feac5160b359e4ff53216146..61f6e7f367189d6a3bbb6931bdc6536963488f49 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_observation.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py
@@ -1,6 +1,7 @@
 #  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 #  SPDX-License-Identifier: Apache-2.0
 
+import json
 from datetime import datetime
 from json import loads
 
@@ -9,7 +10,8 @@ import numpy
 from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
-from tango import DevState, DevFailed
+from tango import DevState
+from tango import DevFailed
 from tangostationcontrol.common.constants import (
     N_beamlets_ctrl,
     N_elements,
@@ -21,10 +23,13 @@ from tangostationcontrol.devices.base_device_classes.antennafield_device import
     AntennaQuality,
     AntennaUse,
 )
-from tangostationcontrol.test.devices.test_observation_base import TestObservationBase
+
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_hba_immediate,
+)
 
 
-class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
+class TestDeviceObservationField(AbstractTestBases.TestDeviceBase):
     ANTENNA_TO_SDP_MAPPING = [
         "0",
         "0",
@@ -81,51 +86,46 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
     def test_device_write_all_attributes(self):
         pass
 
+    @classmethod
+    def setUpClass(cls):
+        obs_control = TestDeviceProxy("STAT/ObservationControl/1")
+        obs_control.create_test_device()
+
+    @classmethod
+    def tearDownClass(cls):
+        obs_control = TestDeviceProxy("STAT/ObservationControl/1")
+        obs_control.destroy_test_device()
+
     def setUp(self):
-        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()
-        self.digitalbeam_proxy = self.setup_digitalbeam_proxy()
-        self.tilebeam_proxy = self.setup_tilebeam_proxy()
-
-    def setup_recv_proxy(self):
-        # setup RECV
-        recv_proxy = TestDeviceProxy("STAT/RECVH/H0")
-        recv_proxy.off()
-        recv_proxy.boot()
-        recv_proxy.set_defaults()
-        return recv_proxy
-
-    def setup_sdpfirmware_proxy(self):
-        # setup SDPFirmware
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        return sdpfirmware_proxy
-
-    def setup_sdp_proxy(self):
-        # setup SDP
-        sdp_proxy = TestDeviceProxy("STAT/SDP/HBA0")
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        return sdp_proxy
-
-    def setup_antennafield_proxy(self):
-        # setup AntennaField
-        antennafield_proxy = TestDeviceProxy("STAT/AFH/HBA0")
+        super().setUp("STAT/ObservationField/1")
+        self.VALID_JSON = json.dumps(
+            json.loads(get_observation_settings_hba_immediate().to_json())[
+                "antenna_fields"
+            ][0]
+        )
+        self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True)
+        self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0")
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0")
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0", cb=self.antennafield_configure
+        )
+        self.beamlet_proxy = self.setup_proxy("STAT/Beamlet/HBA0", defaults=True)
+        self.digitalbeam_proxy = self.setup_proxy(
+            "STAT/DigitalBeam/HBA0", defaults=True
+        )
+        self.tilebeam_proxy = self.setup_proxy("STAT/TileBeam/HBA0", defaults=True)
+
+    @staticmethod
+    def antennafield_configure(proxy: TestDeviceProxy):
         power_mapping = [[1, i * 2 + 0] for i in range(CS001_TILES)]
         control_mapping = [[1, i * 2 + 1] for i in range(CS001_TILES)]
         antenna_qualities = numpy.array([AntennaQuality.OK] * MAX_ANTENNA)
         antenna_use = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA)
-        antennafield_proxy.put_property(
+        proxy.put_property(
             {
                 "Power_to_RECV_mapping": numpy.array(power_mapping).flatten(),
                 "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(),
-                "Antenna_to_SDP_Mapping": self.ANTENNA_TO_SDP_MAPPING,
+                "Antenna_to_SDP_Mapping": TestDeviceObservationField.ANTENNA_TO_SDP_MAPPING,
                 "Antenna_Quality": antenna_qualities,
                 "Antenna_Use": antenna_use,
                 "Antenna_Sets": ["INNER", "OUTER", "SPARSE_EVEN", "SPARSE_ODD", "ALL"],
@@ -141,47 +141,12 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
                 ],
             }
         )
-        antennafield_proxy.off()
-        antennafield_proxy.boot()
-        return antennafield_proxy
-
-    def setup_beamlet_proxy(self):
-        # setup Digitalbeam
-        beamlet_proxy = TestDeviceProxy("STAT/Beamlet/HBA0")
-        beamlet_proxy.off()
-        beamlet_proxy.boot()
-        beamlet_proxy.set_defaults()
-        return beamlet_proxy
-
-    def setup_digitalbeam_proxy(self):
-        # setup Digitalbeam
-        digitalbeam_proxy = TestDeviceProxy("STAT/DigitalBeam/HBA0")
-        digitalbeam_proxy.off()
-        digitalbeam_proxy.boot()
-        digitalbeam_proxy.set_defaults()
-        return digitalbeam_proxy
-
-    def setup_tilebeam_proxy(self):
-        # Setup Tilebeam
-        tilebeam_proxy = TestDeviceProxy("STAT/TileBeam/HBA0")
-        tilebeam_proxy.off()
-        tilebeam_proxy.boot()
-        tilebeam_proxy.set_defaults()
-        return tilebeam_proxy
-
-    def setup_stationmanager_proxy(self):
-        """Setup StationManager"""
-        stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1")
-        stationmanager_proxy.off()
-        stationmanager_proxy.boot()
-        self.assertEqual(stationmanager_proxy.state(), DevState.ON)
-        return stationmanager_proxy
 
     def test_init_valid(self):
         """Initialize an observation with valid JSON"""
 
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.assertEqual(DevState.STANDBY, self.proxy.state())
 
@@ -204,7 +169,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
 
         # Cannot write invalid settings
         with self.assertRaises(DevFailed):
-            self.proxy.observation_settings_RW = "{}"
+            self.proxy.observation_field_settings_RW = "{}"
 
         self.assertEqual(DevState.OFF, self.proxy.state())
 
@@ -212,11 +177,11 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         """Test that changing observation settings is disallowed once init"""
 
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
 
         with self.assertRaises(DevFailed):
-            self.proxy.write_attribute("observation_settings_RW", self.VALID_JSON)
+            self.proxy.write_attribute("observation_field_settings_RW", self.VALID_JSON)
 
     def test_attribute_match(self):
         """Test that JSON data is exposed to attributes"""
@@ -250,7 +215,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         dab_filter = data["HBA"]["DAB_filter"]
 
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
 
@@ -271,16 +236,16 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
 
     def test_apply_antennafield_settings(self):
         """Test that attribute filter is correctly applied"""
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        antennafield_proxy = self.setup_antennafield_proxy()
+        self.setup_proxy("STAT/StationManager/1")
+
+        antennafield_proxy = self.antennafield_proxy
         antennafield_proxy.RCU_band_select_RW = [[0, 0]] * CS001_TILES
         self.assertListEqual(
             antennafield_proxy.RCU_band_select_RW.tolist(),
             [[0, 0]] * CS001_TILES,
         )
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
         expected_bands = [
@@ -292,12 +257,12 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
 
     def test_apply_subbands(self):
         """Test that attribute sap subbands is correctly applied"""
-        beamlet_proxy = self.setup_beamlet_proxy()
+        beamlet_proxy = self.beamlet_proxy
         subband_select = [0] * N_beamlets_ctrl
         beamlet_proxy.subband_select_RW = subband_select
         self.assertListEqual(beamlet_proxy.subband_select_RW.tolist(), subband_select)
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
         expected_subbands = [10, 20, 30] + [0] * (N_beamlets_ctrl - 3)
@@ -307,14 +272,14 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
 
     def test_apply_pointing(self):
         """Test that attribute sap pointing is correctly applied"""
-        digitalbeam_proxy = self.setup_digitalbeam_proxy()
+        digitalbeam_proxy = self.digitalbeam_proxy
         default_pointing = [("AZELGEO", "0rad", "1.570796rad")] * N_beamlets_ctrl
         digitalbeam_proxy.Pointing_direction_RW = default_pointing
         self.assertListEqual(
             list(digitalbeam_proxy.Pointing_direction_RW), default_pointing
         )
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
 
@@ -338,14 +303,14 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
     def test_apply_tilebeam(self):
         # failing
         """Test that attribute tilebeam is correctly applied"""
-        tilebeam_proxy = self.setup_tilebeam_proxy()
+        tilebeam_proxy = self.tilebeam_proxy
         pointing_direction = [("J2000", "0rad", "0rad")] * CS001_TILES
         tilebeam_proxy.Pointing_direction_RW = pointing_direction
         self.assertListEqual(
             list(tilebeam_proxy.Pointing_direction_RW[0]), ["J2000", "0rad", "0rad"]
         )
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
 
@@ -360,7 +325,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
     def test_apply_element_selection(self):
         # failing
         """Test that attribute element_selection is correctly applied"""
-        antennafield_proxy = self.setup_antennafield_proxy()
+        antennafield_proxy = self.antennafield_proxy
         antennafield_proxy.HBAT_PWR_on_RW = numpy.ones(
             (CS001_TILES, N_elements * N_pol), dtype=bool
         )
@@ -369,7 +334,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         )
 
         self.proxy.off()
-        self.proxy.observation_settings_RW = self.VALID_JSON
+        self.proxy.observation_field_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
 
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_sdp.py b/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
index ce74408401c5ba8ff048e007648a103bce4bd72d..6df13f529dcbb3b32633f047746027c63e415233 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_sdp.py
@@ -8,3 +8,5 @@ class TestDeviceSDP(AbstractTestBases.TestDeviceBase):
     def setUp(self):
         """Intentionally recreate the device object in each test"""
         super().setUp("STAT/SDP/HBA0")
+
+        self.setup_proxy("STAT/SDPFirmware/HBA0")
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_sst.py b/tangostationcontrol/integration_test/default/devices/test_device_sst.py
index 521562abed9480634d25c27ae329b5a49eedcd01..4975fd253f4a1ca4bbdbb4662e18418860c9afcf 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_sst.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_sst.py
@@ -5,7 +5,6 @@ import socket
 import sys
 import time
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 from tango import DevState
@@ -16,15 +15,9 @@ class TestDeviceSST(AbstractTestBases.TestDeviceBase):
         """Intentionally recreate the device object in each test"""
         super().setUp("STAT/SST/HBA0")
 
-        self.sdpfirmware_proxy = self.setup_sdpfirmware()
-
-    def setup_sdpfirmware(self):
-        # setup SDP Firmware
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        sdpfirmware_proxy.set_defaults()
-        return sdpfirmware_proxy
+        self.sdpfirmware_proxy = self.setup_proxy(
+            "STAT/SDPFirmware/HBA0", defaults=True
+        )
 
     def test_device_sst_send_udp(self):
         port_property = {"Statistics_Client_TCP_Port": "4998"}
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py b/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
index fa561eb2ddd0d27c10d4d274d678244a1a8d9aa9..f9dbfefece2a1db754f0870376988a4e62b16db1 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py
@@ -17,7 +17,6 @@ from tangostationcontrol.common.constants import (
 
 from tangostationcontrol.devices.recv.recvh import RECVH
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 logger = logging.getLogger()
@@ -37,8 +36,8 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
         self.recvh_names = devices
 
         self.recv_proxies = self.setup_recvh_proxies()
-        self.sdpfirmware_proxy = self.setup_sdpfirmware_proxy()
-        self.sdp_proxy = self.setup_sdp_proxy()
+        self.sdpfirmware_proxy = self.setup_proxy(self.sdp_firmware_name, defaults=True)
+        self.sdp_proxy = self.setup_proxy(self.sdp_name, defaults=True)
         super().setUp(self.temperature_manager_name)
 
         self.addCleanup(self.restore_polling)
@@ -51,33 +50,13 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase):
         proxies = []
 
         for recvh_name in self.recvh_names:
-            recv_proxy = TestDeviceProxy(recvh_name)
+            recv_proxy = self.setup_proxy(recvh_name)
             proxies.append(recv_proxy)
-            recv_proxy.off()
-            recv_proxy.initialise()
-            recv_proxy.on()
-
             recv_proxy.poll_attribute(self.hbat_led_attribute, DEFAULT_POLLING_PERIOD)
             self.assertTrue(recv_proxy.is_attribute_polled(self.hbat_led_attribute))
 
         return proxies
 
-    def setup_sdpfirmware_proxy(self):
-        # setup SDPFirmware
-        sdpfirmware_proxy = TestDeviceProxy(self.sdp_firmware_name)
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.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_name)
-        sdp_proxy.off()
-        sdp_proxy.boot()
-        sdp_proxy.set_defaults()
-        return sdp_proxy
-
     def test_alarm(self):
         # Exclude other devices which raise a TimeoutError,
         # since they wait for the attribute *_translator_busy_R to become False
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
index 728d2aabc3361b7dacf72b1320639b1e38e30bbd..e8f2bcbedcb5d8a1e211521627600dee0fe807b9 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py
@@ -9,7 +9,6 @@ import numpy
 from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
-from tango import DevState
 from tangostationcontrol.common.constants import (
     CS001_TILES,
     MAX_ANTENNA,
@@ -38,48 +37,34 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         """Setup Tilebeam"""
         super().setUp("STAT/TileBeam/HBA0")
 
-    def setup_recv_proxy(self):
-        """Setup RECV"""
-        recv_proxy = TestDeviceProxy("STAT/RECVH/H0")
-        recv_proxy.off()
-        recv_proxy.boot()
-        recv_proxy.set_defaults()
-        return recv_proxy
-
-    def setup_stationmanager_proxy(self):
-        """Setup StationManager"""
-        stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1")
-        stationmanager_proxy.off()
-        stationmanager_proxy.boot()
-        self.assertEqual(stationmanager_proxy.state(), DevState.ON)
-        return stationmanager_proxy
-
-    def setup_antennafield_proxy(self):
+        self.station_manager_proxy = self.setup_proxy("STAT/StationManager/1")
+        self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True)
+        self.sdpfirmware_proxy = self.setup_proxy(
+            "STAT/SDPFirmware/HBA0", defaults=True
+        )
+        self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0", defaults=True)
+        self.antennafield_proxy = self.setup_proxy(
+            "STAT/AFH/HBA0", cb=self.setup_antennafield_property
+        )
+
+    def setup_antennafield_property(self, proxy: TestDeviceProxy):
         """Setup AntennaField"""
-        antennafield_proxy = TestDeviceProxy("STAT/AFH/HBA0")
         control_mapping = [[1, i] for i in range(CS001_TILES)]
         antenna_qualities = numpy.array([AntennaQuality.OK] * MAX_ANTENNA)
         antenna_use = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA)
-        antennafield_proxy.put_property(
+        proxy.put_property(
             {
                 "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(),
                 "Antenna_Quality": antenna_qualities,
                 "Antenna_Use": antenna_use,
             }
         )
-        antennafield_proxy.off()
-        antennafield_proxy.boot()
 
         # check if AntennaField really exposes the expected number of tiles
-        self.assertEqual(CS001_TILES, antennafield_proxy.nr_antennas_R)
-        return antennafield_proxy
+        self.assertEqual(CS001_TILES, proxy.nr_antennas_R)
 
     def test_delays_dims(self):
         """Verify delays are retrieved with correct dimensions"""
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        self.setup_antennafield_proxy()
-
         # setup BEAM
         self.proxy.boot()
 
@@ -89,16 +74,13 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
 
     def test_set_pointing(self):
         """Verify if set pointing procedure is correctly executed"""
-        self.setup_stationmanager_proxy()
-        antennafield_proxy = self.setup_antennafield_proxy()
-
         # setup BEAM
         self.proxy.boot()
         self.proxy.Tracking_enabled_RW = False
 
         # Verify attribute is present (all zeros if never used before)
         delays_r1 = numpy.array(
-            antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
         )
 
         self.assertIsNotNone(delays_r1)
@@ -108,7 +90,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         # Verify writing operation does not lead to errors
         self.proxy.set_pointing(self.POINTING_DIRECTION)  # write values to RECV
         delays_r2 = numpy.array(
-            antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
         )
 
         self.assertIsNotNone(delays_r2)
@@ -117,11 +99,6 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         # self.assertFalse((delays_r1==delays_r2).all())
 
     def test_pointing_to_zenith(self):
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        # setup AntennaField as well
-        antennafield_proxy = self.setup_antennafield_proxy()
-
         self.proxy.boot()
         self.proxy.Tracking_enabled_RW = False
 
@@ -131,7 +108,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         )
 
         calculated_HBAT_delay_steps = numpy.array(
-            antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
         )
 
         expected_HBAT_delay_steps = numpy.array(
@@ -143,10 +120,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         )
 
     def test_pointing_across_horizon(self):
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        # setup AntennaField as well
-        antennafield_proxy = self.setup_antennafield_proxy()
+        antennafield_proxy = self.antennafield_proxy
 
         self.proxy.boot()
         self.proxy.Tracking_enabled_RW = False
@@ -185,11 +159,6 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
             )
 
     def test_delays_same_as_LOFAR_ref_pointing(self):
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        # setup AntennaField as well
-        antennafield_proxy = self.setup_antennafield_proxy()
-
         self.proxy.boot()
         self.proxy.Tracking_enabled_RW = False
 
@@ -206,7 +175,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         self.proxy.set_pointing_for_specific_time(json_string)
 
         calculated_HBAT_delay_steps = numpy.array(
-            antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
+            self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_RW").value
         )  # dims (CS001_TILES, 32)
 
         # Check all delay steps are zero with small margin
@@ -230,9 +199,6 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase):
         )
 
     def test_tilebeam_tracking(self):
-        self.setup_stationmanager_proxy()
-        self.setup_recv_proxy()
-        self.setup_antennafield_proxy()
         self.proxy.boot()
 
         # check if we're really tracking
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_xst.py b/tangostationcontrol/integration_test/default/devices/test_device_xst.py
index 7f0218b0d3ac379d4cde1de957efa9754374a476..2acca91246d88ce7465e732d559ca33eec4ad25e 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_xst.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_xst.py
@@ -1,7 +1,6 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from integration_test.device_proxy import TestDeviceProxy
 from integration_test.default.devices.base import AbstractTestBases
 
 
@@ -10,12 +9,6 @@ class TestDeviceXST(AbstractTestBases.TestDeviceBase):
         """Intentionally recreate the device object in each test"""
         super().setUp("STAT/XST/HBA0")
 
-        self.sdpfirmware_proxy = self.setup_sdpfirmware()
-
-    def setup_sdpfirmware(self):
-        # setup SDP Firmware
-        sdpfirmware_proxy = TestDeviceProxy("STAT/SDPFirmware/HBA0")
-        sdpfirmware_proxy.off()
-        sdpfirmware_proxy.boot()
-        sdpfirmware_proxy.set_defaults()
-        return sdpfirmware_proxy
+        self.sdpfirmware_proxy = self.setup_proxy(
+            "STAT/SDPFirmware/HBA0", defaults=True
+        )
diff --git a/tangostationcontrol/integration_test/default/devices/test_observation.py b/tangostationcontrol/integration_test/default/devices/test_observation_client.py
similarity index 74%
rename from tangostationcontrol/integration_test/default/devices/test_observation.py
rename to tangostationcontrol/integration_test/default/devices/test_observation_client.py
index 6233bbd5bc8907106990ca3ea06e038823841eb5..3b46b8319110f5acc9404af44e3f5e1765e21ad1 100644
--- a/tangostationcontrol/integration_test/default/devices/test_observation.py
+++ b/tangostationcontrol/integration_test/default/devices/test_observation_client.py
@@ -9,11 +9,12 @@ from lofar_station_client.observation.station_observation import (
 )
 from integration_test import base
 from integration_test.device_proxy import TestDeviceProxy
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_hba_immediate,
+)
 
 from tango import DevState
 
-from tangostationcontrol.test.devices.test_observation_base import TestObservationBase
-
 
 class TestObservation(base.IntegrationTestCase):
     def setUp(self):
@@ -21,10 +22,15 @@ class TestObservation(base.IntegrationTestCase):
         self.observation_control_proxy = TestDeviceProxy("STAT/ObservationControl/1")
         self.observation_control_proxy.off()
         self.observation_control_proxy.boot()
+        self.addCleanup(
+            self.observation_control_proxy.test_device_turn_off,
+            self.observation_control_proxy,
+        )
 
         # make sure any devices we depend on are also started
         for device in [
             "STAT/RECVH/H0",
+            "STAT/SDPFirmware/HBA0",
             "STAT/SDP/HBA0",
             "STAT/Beamlet/HBA0",
             "STAT/DigitalBeam/HBA0",
@@ -34,22 +40,25 @@ class TestObservation(base.IntegrationTestCase):
             proxy = TestDeviceProxy(device)
             proxy.off()
             proxy.boot()
+            self.addCleanup(proxy.test_device_turn_off, proxy)
 
     def setup_stationmanager_proxy(self):
         """Setup StationManager"""
         stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1")
         stationmanager_proxy.off()
         stationmanager_proxy.boot()
+        self.addCleanup(stationmanager_proxy.test_device_turn_off, stationmanager_proxy)
         self.assertEqual(stationmanager_proxy.state(), DevState.ON)
         return stationmanager_proxy
 
     def test_observation(self):
         """Test of the observation_wrapper class basic functionality"""
 
-        # convert the JSON specificiation to a dict for this class
-        specification_dict = loads(TestObservationBase.VALID_JSON)
+        # convert the JSON specification to a dict for this class
+        specification_dict = loads(get_observation_settings_hba_immediate().to_json())
 
-        # create an observation class using the dict and as host just get it using a util function
+        # create an observation class using the dict and as host just get it using a
+        # util function
         observation = StationObservation(
             specification=specification_dict, host=environ["TANGO_HOST"]
         )
@@ -60,7 +69,7 @@ class TestObservation(base.IntegrationTestCase):
 
         # Assert the proxy is on
         station = observation.observation
-        proxy = station.observation_proxy
+        proxy = station.observation_field_proxies[0]
         self.assertTrue(proxy.state() == DevState.ON)
 
         # Assert the observation has stopped after aborting
diff --git a/tangostationcontrol/integration_test/device_proxy.py b/tangostationcontrol/integration_test/device_proxy.py
index d61e4f00a6a3afee2f8c2925dc61baa6deabedd0..87557e2194547434e1fb3307cd5ef1798b0ce607 100644
--- a/tangostationcontrol/integration_test/device_proxy.py
+++ b/tangostationcontrol/integration_test/device_proxy.py
@@ -25,7 +25,7 @@ class TestDeviceProxy(DeviceProxy):
         self.set_source(DevSource.DEV)
 
     @staticmethod
-    def test_device_turn_off(endpoint):
+    def test_device_turn_off(endpoint: str, sleep: float = 1):
         d = TestDeviceProxy(endpoint)
         try:
             d.Off()
@@ -33,5 +33,5 @@ class TestDeviceProxy(DeviceProxy):
             """Failing to turn Off devices should not raise errors here"""
             logger.error(f"Failed to turn device off in teardown {e}")
 
-            """Wait for 1 second to prevent propagating reconnection errors"""
-            time.sleep(1)
+            """Wait to prevent propagating reconnection errors"""
+            time.sleep(sleep)
diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt
index a875eb4b0bfff61e9d732e0c0f7048f84730b9e5..58c9d0693759fb5e84d69e2e4028051488627317 100644
--- a/tangostationcontrol/requirements.txt
+++ b/tangostationcontrol/requirements.txt
@@ -2,7 +2,7 @@
 # order of appearance. Changing the order has an impact on the overall
 # integration process, which may cause wedges in the gate later.
 
-lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client # Apache 2
+lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client  # Apache 2
 PyTango>=9.4.2 # LGPL v3
 numpy>=1.21.6 # BSD3
 asyncua >= 0.9.90 # LGPLv3
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
index 90a78e2f90be128adf4a0e6fc8e15878e9217e06..f9594fe96de49f5420aad7bf8ba5850ff30f1edb 100644
--- a/tangostationcontrol/setup.cfg
+++ b/tangostationcontrol/setup.cfg
@@ -33,14 +33,14 @@ where = .
 console_scripts =
     l2ss-ds = tangostationcontrol.device_server:main
     l2ss-analyse-dsconfig-hierarchies = tangostationcontrol.toolkit.analyse_dsconfig_hierarchies:main
+    l2ss-version = tangostationcontrol:print_version
+    l2ss-health = tangostationcontrol.common.health:main
 
 # The following entry points should eventually be removed / replaced
     l2ss-hardware-device-template = tangostationcontrol.examples.HW_device_template:main
     l2ss-ini-device = tangostationcontrol.examples.load_from_disk.ini_device:main
     l2ss-parse-statistics-packet = tangostationcontrol.statistics.packet:main
     l2ss-random-data = tangostationcontrol.test.devices.random_data:main
-    l2ss-version = tangostationcontrol:print_version
-    l2ss-health = tangostationcontrol.common.health:main
 
 [options.package_data]
 * = *.json, *.mib
diff --git a/tangostationcontrol/tangostationcontrol/common/__init__.py b/tangostationcontrol/tangostationcontrol/common/__init__.py
index bbdd80eaa2ac676116d0ae5a10856f970e3378b0..c92b615444d854a6e87370b16cf733a5859a07e7 100644
--- a/tangostationcontrol/tangostationcontrol/common/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/common/__init__.py
@@ -1,6 +1,2 @@
 #  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 #  SPDX-License-Identifier: Apache-2.0
-
-from .observation_controller import ObservationController
-
-__all__ = ["ObservationController"]
diff --git a/tangostationcontrol/tangostationcontrol/common/antennas.py b/tangostationcontrol/tangostationcontrol/common/antennas.py
index a06e58984a085fa775154e75fd8cc530789c5046..309934820b94a9b575822ec062e094c771515fdd 100644
--- a/tangostationcontrol/tangostationcontrol/common/antennas.py
+++ b/tangostationcontrol/tangostationcontrol/common/antennas.py
@@ -1,5 +1,6 @@
 #  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 #  SPDX-License-Identifier: Apache-2.0
+
 import numpy
 
 from tango import Util
@@ -52,6 +53,10 @@ def device_member_to_full_device_name(device_member: str) -> str:
         return f"{util.get_ds_inst_name()}/AFL/{device_member}"
     if CaseInsensitiveString("HBA") in CaseInsensitiveString(device_member):
         return f"{util.get_ds_inst_name()}/AFH/{device_member}"
+    if CaseInsensitiveString("HBA0") in CaseInsensitiveString(device_member):
+        return f"{util.get_ds_inst_name()}/AFH/{device_member}"
+    if CaseInsensitiveString("HBA1") in CaseInsensitiveString(device_member):
+        return f"{util.get_ds_inst_name()}/AFH/{device_member}"
     raise ValueError(f"Invalid value for antennafield parameter: {device_member}")
 
 
@@ -68,4 +73,8 @@ def device_member_to_antenna_type(device_member: str) -> str:
         return "LBA"
     if CaseInsensitiveString("HBA") in CaseInsensitiveString(device_member):
         return "HBA"
+    if CaseInsensitiveString("HBA0") in CaseInsensitiveString(device_member):
+        return "HBA"
+    if CaseInsensitiveString("HBA1") in CaseInsensitiveString(device_member):
+        return "HBA"
     raise ValueError(f"Invalid value for antennafield: {device_member}")
diff --git a/tangostationcontrol/tangostationcontrol/common/case_insensitive_string.py b/tangostationcontrol/tangostationcontrol/common/case_insensitive_string.py
index 507bb69700501a0808a6a5eb95237ec168e85599..35e510e917f8638d1a98f51a1ccedc6bb2f67f15 100644
--- a/tangostationcontrol/tangostationcontrol/common/case_insensitive_string.py
+++ b/tangostationcontrol/tangostationcontrol/common/case_insensitive_string.py
@@ -14,6 +14,11 @@ class CaseInsensitiveString(str):
     def __hash__(self):
         return hash(self.__str__())
 
+    def __contains__(self, key):
+        if isinstance(key, str):
+            return key.casefold() in str(self)
+        return key in str(self)
+
     def __str__(self) -> str:
         return self.casefold().__str__()
 
diff --git a/tangostationcontrol/tangostationcontrol/common/configuration.py b/tangostationcontrol/tangostationcontrol/common/configuration.py
index 09be2024ee77f3a7ba60b7df25f2537aecaf6d24..b2fd8549bff314821d41109b31d336ec2eb3d043 100644
--- a/tangostationcontrol/tangostationcontrol/common/configuration.py
+++ b/tangostationcontrol/tangostationcontrol/common/configuration.py
@@ -49,7 +49,6 @@ class StationConfiguration:
     @classmethod
     def get_validator(cls):
         """Retrieve the JSON validator from Schemas container"""
-        name = cls.__name__
         return Draft7Validator(
             REGISTRY["station-configuration"].contents,
             format_checker=FormatChecker(),
diff --git a/tangostationcontrol/tangostationcontrol/common/observation_controller.py b/tangostationcontrol/tangostationcontrol/common/observation_controller.py
deleted file mode 100644
index 360e353515b25fa4b1f88cbeb97da696f7ce52c5..0000000000000000000000000000000000000000
--- a/tangostationcontrol/tangostationcontrol/common/observation_controller.py
+++ /dev/null
@@ -1,262 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-
-import logging
-import time
-from datetime import datetime
-
-from tango import DevFailed, DevState, Except, Util, EventType, DeviceProxy
-from tangostationcontrol.common.lofar_logging import log_exceptions
-from tangostationcontrol.common.proxy import create_device_proxy
-from tangostationcontrol.configuration import ObservationSettings
-
-logger = logging.getLogger()
-
-
-class Observation(object):
-    @property
-    def proxy(self) -> DeviceProxy:
-        return self._device_proxy
-
-    @property
-    def observation_id(self) -> int:
-        return self._parameters.observation_id
-
-    @property
-    def class_name(self) -> str:
-        from tangostationcontrol.devices.observation import Observation
-
-        return Observation.__name__
-
-    @property
-    def device_name(self) -> str:
-        return f"{self._tango_domain}/{self.class_name}/{self.observation_id}"
-
-    # Name for the Observation.observation_running subscription
-    @property
-    def attribute_name(self) -> str:
-        return f"{self.device_name}/alive_R"
-
-    def __init__(self, tango_domain, parameters: ObservationSettings):
-        self._device_proxy: DeviceProxy | None = None
-        self._event_id: int | None = None
-        self._parameters: ObservationSettings = parameters
-        self._tango_domain: str = tango_domain
-
-        # The pyTango.Util class is a singleton and every DS can only
-        # have one instance of it.
-        self._tango_util: Util = Util.instance()
-
-    def create_observation_device(self):
-        """Instatiate an Observation Device"""
-        logger.info("Create device: %s", self.device_name)
-        try:
-            # Create the Observation device and instantiate it.
-            self._tango_util.create_device(self.class_name, f"{self.device_name}")
-        except DevFailed as ex:
-            logger.exception(ex)
-            if (
-                ex.args[0].desc
-                == f"The device {self.device_name.lower()} is already defined in the database"
-            ):
-                # and self.is_observation_running(self.observation_id) is False:
-                self._tango_util.delete_device(self.class_name, self.device_name)
-                error_string = f"Cannot create the Observation device {self.device_name} \
-                    because it is already present in the Database but it is not running. \
-                    Try to re-run the start_observation command"
-                logger.exception(error_string)
-                Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
-            else:
-                error_string = f"Cannot create the Observation device instance \
-                    {self.device_name} for ID={self.observation_id}. \
-                    This means that the observation did not start."
-                logger.exception(error_string)
-                Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
-
-    def destroy_observation_device(self):
-        try:
-            self._tango_util.delete_device(self.class_name, self.device_name)
-        except DevFailed:
-            logger.exception(
-                f"Could not delete device {self.device_name} of class {self.class_name} from Tango DB."
-            )
-
-    def initialise_observation(self):
-        # Instantiate a dynamic Tango Device "Observation".
-        self._device_proxy = create_device_proxy(self.device_name)
-
-        # Initialise generic properties
-        self.proxy.put_property({"Control_Children": [], "Power_Children": []})
-
-        # Configure the dynamic device its attribute for the observation
-        # parameters.
-        self.proxy.observation_settings_RW = self._parameters.to_json()
-
-        # Take the Observation device through the motions.  Pass the
-        # entire JSON set of parameters so that it can pull from it what it
-        # needs.
-        self.proxy.Initialise()
-
-    def start(self):
-        self.proxy.On()
-
-    def is_running(self):
-        return self.proxy and self.proxy.state() == DevState.ON
-
-    def subscribe(self, cb):
-        # Turn on the polling for the attribute.
-        # Note that this is not automatically done despite the attribute
-        # having the right polling values set in the ctor.
-        self.proxy.poll_attribute(self.attribute_name.split("/")[-1], 1000)
-
-        # Right. Now subscribe to periodic events.
-        self._event_id = self._device_proxy.subscribe_event(
-            self.attribute_name.split("/")[-1], EventType.PERIODIC_EVENT, cb
-        )
-        logger.info(
-            "Successfully started an observation with ID=%s.", self.observation_id
-        )
-
-    def stop(self):
-        # Check if the device has not terminated itself in the meanwhile.
-        try:
-            self.proxy.ping()
-        except DevFailed:
-            logger.error(
-                f"Observation device for ID={self.observation_id} unexpectedly disappeared."
-            )
-        else:
-            # Unsubscribe from the subscribed event.
-            self.proxy.unsubscribe_event(self._event_id)
-
-            # Tell the Observation device to stop the running
-            # observation.  This is a synchronous call and the clean-up
-            # does not take long.
-            self.proxy.Off()
-
-        # Finally remove the device object from the Tango DB.
-        self.destroy_observation_device()
-
-
-class ObservationController(dict[str, Observation]):
-    """A dictionary of observations. Actively manages the observation state transtions
-    (start, stop)."""
-
-    def __init__(self, tango_domain: str):
-        self._tango_util = Util.instance()
-        self._tango_domain = tango_domain
-
-    @property
-    def running_observations(self) -> list[str]:
-        return [obs_id for obs_id, obs in self.items() if obs.is_running()]
-
-    @log_exceptions()
-    def observation_callback(self, event):
-        """
-        This callback checks and manages the state transitions
-        for each observation.
-
-        It starts observations at their specified start_time,
-        and stops & removes them at their specified stop_time.
-        """
-        if event.err:
-            # Something is fishy with this event.
-            logger.warning(
-                "The Observation device %s sent an event but the event \
-                signals an error.  It is advised to check the logs for any indication \
-                that something went wrong in that device.  Event data=%s",
-                event.device,
-                event,
-            )
-            return
-
-        # update the state of this observation, if needed
-        self._update_observation_state(event.device)
-
-    def _update_observation_state(self, device: DeviceProxy):
-        """Start or stop the observation managed by the given Observation
-        device."""
-
-        # Get the Observation ID from the sending device.
-        obs_id = device.observation_id_R
-
-        # Get the start/stop times from the sending device
-        obs_start_time = device.start_time_R
-        obs_stop_time = device.stop_time_R
-
-        # Get how much earlier we have to start
-        obs_lead_time = device.lead_time_R
-
-        # Obtain the current time ONCE to avoid race conditions
-        now = time.time()
-
-        # Manage state transitions
-        if now > obs_stop_time:
-            # Stop observation
-            self.stop_observation_now(obs_id)
-        elif (
-            now >= obs_start_time - obs_lead_time and device.state() == DevState.STANDBY
-        ):
-            # Start observation
-            self.start_observation(obs_id)
-
-    def add_observation(self, settings: ObservationSettings):
-        """Create a new Observation Device and start an observation"""
-        # Check further properties that cannot be validated through a JSON schema
-        if settings.stop_time <= datetime.now():
-            raise ValueError(
-                f"Cannot start observation {settings.observation_id} because it is already past its stop time {settings.stop_time}"
-            )
-
-        obs = Observation(self._tango_domain, settings)
-        obs.create_observation_device()
-
-        try:
-            obs.initialise_observation()
-        except DevFailed as ex:
-            # Remove the device again.
-            obs.destroy_observation_device()
-
-            raise Exception(
-                "Failed to initialise observation {settings.observation_id}"
-            ) from ex
-
-        # Register this observation now that is has been succesfully created
-        self[obs.observation_id] = obs
-
-        # Keep pinging it to manage its state transitions
-        try:
-            obs.subscribe(self.observation_callback)
-        except DevFailed as ex:
-            # Remove the device again.
-            obs.destroy_observation_device()
-
-            raise Exception(
-                "Cannot subscribe to attribute. Cancelled observation {settings.observation_id}."
-            ) from ex
-
-        # Make sure the current state is accurate
-        self._update_observation_state(obs.proxy)
-
-    def start_observation(self, obs_id):
-        """Start the observation with the given ID"""
-        try:
-            observation = self[obs_id]
-        except KeyError as _exc:
-            raise Exception(f"Unknown observation: {obs_id}")
-
-        observation.start()
-
-    def stop_observation_now(self, obs_id):
-        """Stop the observation with the given ID"""
-        try:
-            observation = self.pop(obs_id)
-        except KeyError as _exc:
-            raise Exception(f"Unknown observation: {obs_id}")
-
-        observation.stop()
-
-    def stop_all_observations_now(self):
-        """Stop all observations (running or to be run)"""
-        for obs_id in list(self):  # draw a copy as we modify the list
-            self.stop_observation_now(obs_id)
diff --git a/tangostationcontrol/tangostationcontrol/common/states.py b/tangostationcontrol/tangostationcontrol/common/states.py
index 6ab0c5a581978e00e1ae09bcb4c3d3d08c219879..e5664f35234cbe659f2c86d5eb36ba8618e0fb47 100644
--- a/tangostationcontrol/tangostationcontrol/common/states.py
+++ b/tangostationcontrol/tangostationcontrol/common/states.py
@@ -71,7 +71,7 @@ DEVICES_ON_IN_STATION_STATE: Dict[str, Optional[StationState]] = {
     "RECVH": StationState.STANDBY,
     "RECVL": StationState.STANDBY,
     # TODO(JDM) Lingering observations (and debug observation 0) should not be booted as we do not persist their settings
-    "Observation": None,
+    "ObservationField": None,
     # Unmentioned devices will go ON in this state
     "_default": StationState.ON,
 }
diff --git a/tangostationcontrol/tangostationcontrol/configuration/__init__.py b/tangostationcontrol/tangostationcontrol/configuration/__init__.py
index d5b9dc9bd9cc8bd7d1e442f79c5e00832c7b2b5b..b567fc35750358442fa60da4e845980744541f72 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/__init__.py
@@ -5,7 +5,16 @@ from ._schemas import REGISTRY
 from .dithering import Dithering
 from .hba import HBA
 from .observation_settings import ObservationSettings
+from .observation_field_settings import ObservationFieldSettings
 from .pointing import Pointing
 from .sap import Sap
 
-__all__ = ["ObservationSettings", "Pointing", "Sap", "HBA", "Dithering", "REGISTRY"]
+__all__ = [
+    "ObservationSettings",
+    "ObservationFieldSettings",
+    "Pointing",
+    "Sap",
+    "HBA",
+    "Dithering",
+    "REGISTRY",
+]
diff --git a/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py b/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py
index 026be5be9c924a59591fe72717d8e0ec7e37e917..92daf15a46e8bc444263c2380936c7f072ab56b3 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py
@@ -11,13 +11,23 @@ def _from_json_hook_t(primary: Type):
         Pointing,
         Sap,
         ObservationSettings,
+        ObservationFieldSettings,
         HBA,
         Dithering,
     )
 
     def actual_hook(json_dct):
+        """Validate json_dct for each schema layer up to the primary type"""
         primary_ex = None
-        for t in [Pointing, Sap, HBA, Dithering, ObservationSettings]:
+        # Order is critical, must match inheritance, deepest layers first
+        for t in [
+            Pointing,
+            Sap,
+            HBA,
+            Dithering,
+            ObservationFieldSettings,
+            ObservationSettings,
+        ]:
             try:
                 t.get_validator().validate(json_dct)
             except ValidationError as ex:
diff --git a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
index ea8769d550f9659e1b5ce516c14339574686a84d..a8e94dc8a140765fe32b7463d6f6e8a5c7e64ea8 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py
@@ -29,17 +29,21 @@ jsonschema.validators.Draft7Validator.TYPE_CHECKER = (
 
 class _ConfigurationBase(ABC):
     @staticmethod
-    def _class_to_url(cls_name):
+    def _class_to_url(cls_name: str) -> str:
+        """Class name to json schema file conversion name"""
         cls_name = cls_name.replace("HBA", "hba")
         cls_name = cls_name.replace("LBA", "lba")
         return re.sub(r"(?<!^)(?=[A-Z])", "-", cls_name).lower()
 
     @classmethod
-    def get_validator(cls):
-        """Retrieve the JSON validator from Schemas container"""
-        name = cls.__name__
+    def get_validator(cls) -> Draft7Validator:
+        """Retrieve schema validation file and return Draft7Validator instance
+
+        Schema file name is derived from class name see :py:func:`~._class_to_url` for
+        conversion
+        """
         return Draft7Validator(
-            REGISTRY[_ConfigurationBase._class_to_url(name)].contents,
+            REGISTRY[_ConfigurationBase._class_to_url(cls.__name__)].contents,
             format_checker=FormatChecker(),
             registry=REGISTRY,
         )
@@ -62,7 +66,7 @@ class _ConfigurationBase(ABC):
     def __contains__(self, item):
         return hasattr(self, item) and getattr(self, item) is not None
 
-    def to_json(self):
+    def to_json(self) -> str:
         return self.__str__()
 
     @staticmethod
@@ -75,6 +79,7 @@ class _ConfigurationBase(ABC):
         s = json.loads(data, object_hook=_from_json_hook_t(cls))
         if not isinstance(s, cls):
             raise ValidationError(
-                f"Unexpected type: expected <{cls.__class__.__name__}>, got <{type(s).__name__}>"
+                f"Unexpected type: expected <{cls.__class__.__name__}>, got "
+                f"<{type(s).__name__}>"
             )
         return s
diff --git a/tangostationcontrol/tangostationcontrol/configuration/dithering.py b/tangostationcontrol/tangostationcontrol/configuration/dithering.py
index 750a6e463358e34b56151baf001e46ce24638530..fa02bde00ec5541b87bc9a2911a74f1be0d81ba3 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/dithering.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/dithering.py
@@ -11,7 +11,7 @@ class Dithering(_ConfigurationBase):
         self,
         enabled: bool,
         power: float | None,
-        frequency: float | None,
+        frequency: int | None,
     ):
         self.enabled = enabled
         self.power = power
diff --git a/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py b/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..d862468ac193d8184bbdf12b1b29b7e6a10e86c7
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py
@@ -0,0 +1,86 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+from datetime import datetime
+from typing import Sequence
+
+from tangostationcontrol.configuration.configuration_base import _ConfigurationBase
+from tangostationcontrol.configuration.dithering import Dithering
+from tangostationcontrol.configuration.hba import HBA
+from tangostationcontrol.configuration.sap import Sap
+
+
+class ObservationFieldSettings(_ConfigurationBase):
+    def __init__(
+        self,
+        observation_id: int,
+        start_time: str | datetime | None,
+        stop_time: str | datetime,
+        antenna_field: str,
+        antenna_set: str,
+        filter: str,
+        SAPs: Sequence[Sap],
+        HBA: HBA | None = None,
+        first_beamlet: int = 0,
+        lead_time: float | None = None,
+        dithering: Dithering | None = None,
+    ):
+        self.observation_id = observation_id
+        self.start_time = self._parse_and_convert_datetime(start_time)
+        self.stop_time = self._parse_and_convert_datetime(stop_time)
+        self.antenna_field = antenna_field
+        self.antenna_set = antenna_set
+        self.filter = filter
+        self.SAPs = SAPs
+        self.HBA = HBA
+        self.first_beamlet = first_beamlet
+        self.lead_time = lead_time
+        self.dithering = dithering
+
+    @staticmethod
+    def _parse_and_convert_datetime(time: str | datetime | None):
+        """Transparently convert datetime to string in isoformat"""
+        if time and not isinstance(time, datetime):
+            try:
+                datetime.fromisoformat(time)
+            except ValueError as ex:
+                raise ex
+        if time and isinstance(time, datetime):
+            return time.isoformat()
+
+        return time
+
+    def __iter__(self):
+        yield "observation_id", self.observation_id
+        if self.start_time:
+            yield "start_time", self.start_time
+        yield from {
+            "stop_time": self.stop_time,
+            "antenna_field": self.antenna_field,
+            "antenna_set": self.antenna_set,
+            "filter": self.filter,
+            "SAPs": [dict(s) for s in self.SAPs],
+        }.items()
+        if self.HBA:
+            yield "HBA", dict(self.HBA)
+        yield "first_beamlet", self.first_beamlet
+        if self.lead_time is not None:
+            yield "lead_time", self.lead_time
+        if self.dithering is not None:
+            yield "dithering", dict(self.dithering)
+
+    @staticmethod
+    def to_object(json_dct) -> "ObservationFieldSettings":
+        return ObservationFieldSettings(
+            json_dct["observation_id"],
+            json_dct["start_time"] if "start_time" in json_dct else None,
+            json_dct["stop_time"],
+            json_dct["antenna_field"],
+            json_dct["antenna_set"],
+            json_dct["filter"],
+            json_dct["SAPs"],
+            json_dct.get("HBA"),
+            json_dct.get("first_beamlet", 0),
+            json_dct.get("lead_time"),
+            json_dct.get("dithering"),
+        )
diff --git a/tangostationcontrol/tangostationcontrol/configuration/observation_settings.py b/tangostationcontrol/tangostationcontrol/configuration/observation_settings.py
index 40d64922187fb1bcec7cb7c30578e0922d1ce1ae..dff442d7cd63b057f59f71604c2f28328d949ce2 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/observation_settings.py
+++ b/tangostationcontrol/tangostationcontrol/configuration/observation_settings.py
@@ -1,75 +1,25 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from datetime import datetime
 from typing import Sequence
 
 from tangostationcontrol.configuration.configuration_base import _ConfigurationBase
-from tangostationcontrol.configuration.dithering import Dithering
-from tangostationcontrol.configuration.hba import HBA
-from tangostationcontrol.configuration.sap import Sap
+from tangostationcontrol.configuration.observation_field_settings import (
+    ObservationFieldSettings,
+)
 
 
 class ObservationSettings(_ConfigurationBase):
     def __init__(
-        self,
-        observation_id: int,
-        start_time: datetime | None,
-        stop_time: datetime,
-        antenna_field: str,
-        antenna_set: str,
-        filter: str,
-        SAPs: Sequence[Sap],
-        HBA: HBA | None = None,
-        first_beamlet: int = 0,
-        lead_time: float | None = None,
-        dithering: Dithering | None = None,
+        self, station: str, antenna_fields: Sequence[ObservationFieldSettings]
     ):
-        self.observation_id = observation_id
-        self.start_time = start_time
-        self.stop_time = stop_time
-        self.antenna_field = antenna_field
-        self.antenna_set = antenna_set
-        self.filter = filter
-        self.SAPs = SAPs
-        self.HBA = HBA
-        self.first_beamlet = first_beamlet
-        self.lead_time = lead_time
-        self.dithering = dithering
+        self.station = station
+        self.antenna_fields = antenna_fields
 
     def __iter__(self):
-        yield "observation_id", self.observation_id
-        if self.start_time:
-            yield "start_time", self.start_time.isoformat()
-        yield from {
-            "stop_time": self.stop_time.isoformat(),
-            "antenna_field": self.antenna_field,
-            "antenna_set": self.antenna_set,
-            "filter": self.filter,
-            "SAPs": [dict(s) for s in self.SAPs],
-        }.items()
-        if self.HBA:
-            yield "HBA", dict(self.HBA)
-        yield "first_beamlet", self.first_beamlet
-        if self.lead_time is not None:
-            yield "lead_time", self.lead_time
-        if self.dithering is not None:
-            yield "dithering", dict(self.dithering)
+        yield "station", self.station
+        yield "antenna_fields", [dict(s) for s in self.antenna_fields]
 
     @staticmethod
     def to_object(json_dct) -> "ObservationSettings":
-        return ObservationSettings(
-            json_dct["observation_id"],
-            datetime.fromisoformat(json_dct["start_time"])
-            if "start_time" in json_dct
-            else None,
-            datetime.fromisoformat(json_dct["stop_time"]),
-            json_dct["antenna_field"],
-            json_dct["antenna_set"],
-            json_dct["filter"],
-            json_dct["SAPs"],
-            json_dct.get("HBA"),
-            json_dct.get("first_beamlet", 0),
-            json_dct.get("lead_time"),
-            json_dct.get("dithering"),
-        )
+        return ObservationSettings(json_dct["station"], json_dct["antenna_fields"])
diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
index 05c327b6eb2bcf92af96b457fef97f6024605987..7197cea261a21e69d097b168d3dade4463c085d8 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/dithering.json
@@ -19,7 +19,7 @@
       "default": -4.0
     },
     "frequency": {
-      "type": "number",
+      "type": "integer",
       "description": "Frequency of dithering signal, in Hz",
       "default": 102000000
     }
diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..98d90868c0f863a5b4f355211d3eb7f12e531ea4
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json
@@ -0,0 +1,87 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema",
+  "$id": "observation-field-settings",
+  "type": "object",
+  "required": [
+    "observation_id",
+    "stop_time",
+    "antenna_field",
+    "antenna_set",
+    "filter",
+    "SAPs"
+  ],
+  "properties": {
+    "observation_id": {
+      "type": "number",
+      "minimum": 1
+    },
+    "start_time": {
+      "type": "string",
+      "format": "date-time",
+      "default": "1970-01-01T00:00:00"
+    },
+    "stop_time": {
+      "type": "string",
+      "format": "date-time"
+    },
+    "lead_time": {
+      "type": "number",
+      "description": "Number of seconds to start before the provided start time, to account for initialising the on-line signal chain, and for possibly negative geometrical delay compensation.",
+      "default": 2.0,
+      "minimum": 0
+    },
+    "antenna_field": {
+      "default": "HBA",
+      "description": "Antenna field to use",
+      "type": "string",
+      "enum": [
+        "LBA",
+        "HBA",
+        "HBA0",
+        "HBA1"
+      ]
+    },
+    "antenna_set": {
+      "default": "ALL",
+      "description": "Fields & antennas to use",
+      "type": "string",
+      "enum": [
+        "ALL",
+        "INNER",
+        "OUTER",
+        "SPARSE_EVEN",
+        "SPARSE_ODD"
+      ]
+    },
+    "dithering": {
+      "$ref": "dithering"
+    },
+    "filter": {
+      "type": "string",
+      "enum": [
+        "LBA_10_90",
+        "LBA_10_70",
+        "LBA_30_90",
+        "LBA_30_70",
+        "HBA_170_230",
+        "HBA_110_190",
+        "HBA_210_250"
+      ]
+    },
+    "SAPs": {
+      "type": "array",
+      "minItems": 1,
+      "items": {
+        "$ref": "sap"
+      }
+    },
+    "first_beamlet": {
+      "type": "number",
+      "default": 0,
+      "minimum": 0
+    },
+    "HBA": {
+      "$ref": "hba"
+    }
+  }
+}
diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
index c23561dc71f93d298a9c03cccfe7bbf388e655f3..a7d6ce1057a7dfbb9767a9ee8c2e55659df8ca84 100644
--- a/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
+++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-settings.json
@@ -3,85 +3,20 @@
   "$id": "observation-settings",
   "type": "object",
   "required": [
-    "observation_id",
-    "stop_time",
-    "antenna_field",
-    "antenna_set",
-    "filter",
-    "SAPs"
+    "station",
+    "antenna_fields"
   ],
   "properties": {
-    "observation_id": {
-      "type": "number",
-      "minimum": 1
-    },
-    "start_time": {
-      "type": "string",
-      "format": "date-time",
-      "default": "1970-01-01T00:00:00"
-    },
-    "stop_time": {
+    "station": {
       "type": "string",
-      "format": "date-time"
-    },
-    "lead_time": {
-      "type": "number",
-      "description": "Number of seconds to start before the provided start time, to account for initialising the on-line signal chain, and for possibly negative geometrical delay compensation.",
-      "default": 2.0,
-      "minimum": 0
+      "default": "CS001"
     },
-    "antenna_field": {
-      "default": "HBA",
-      "description": "Antenna field to use",
-      "type": "string",
-      "enum": [
-        "LBA",
-        "HBA",
-        "HBA0",
-        "HBA1"
-      ]
-    },
-    "antenna_set": {
-      "default": "ALL",
-      "description": "Fields & antennas to use",
-      "type": "string",
-      "enum": [
-        "ALL",
-        "INNER",
-        "OUTER",
-        "SPARSE_EVEN",
-        "SPARSE_ODD"
-      ]
-    },
-    "dithering": {
-      "$ref": "dithering"
-    },
-    "filter": {
-      "type": "string",
-      "enum": [
-        "LBA_10_90",
-        "LBA_10_70",
-        "LBA_30_90",
-        "LBA_30_70",
-        "HBA_170_230",
-        "HBA_110_190",
-        "HBA_210_250"
-      ]
-    },
-    "SAPs": {
+    "antenna_fields": {
       "type": "array",
       "minItems": 1,
       "items": {
-        "$ref": "sap"
+        "$ref": "observation-field-settings"
       }
-    },
-    "first_beamlet": {
-      "type": "number",
-      "default": 0,
-      "minimum": 0
-    },
-    "HBA": {
-      "$ref": "hba"
     }
   }
 }
diff --git a/tangostationcontrol/tangostationcontrol/devices/__init__.py b/tangostationcontrol/tangostationcontrol/devices/__init__.py
index b6038faf0137d4b710dbe92be7f0a953755d1a0f..acbde7b69d7a2d2bdbb00b6fc504b69dfa749d59 100644
--- a/tangostationcontrol/tangostationcontrol/devices/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/devices/__init__.py
@@ -11,7 +11,7 @@ from .ccd import CCD
 from .configuration import Configuration
 from .docker import Docker
 from .ec import EC
-from .observation import Observation
+from .observation_field import ObservationField
 from .observation_control import ObservationControl
 from .pcon import PCON
 from .psoc import PSOC
@@ -41,7 +41,7 @@ __all__ = [
     "Configuration",
     "Docker",
     "EC",
-    "Observation",
+    "ObservationField",
     "ObservationControl",
     "PCON",
     "PSOC",
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
index ef6eb5fdd377454dbf8795c3eed605e4203104f0..714b4507e7fdd04221372cb487d269d0c4ce5561 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
@@ -12,15 +12,17 @@ from tango import (
     DevString,
 )
 from tango.server import Device, command, attribute
-from tangostationcontrol.common import ObservationController
+from tangostationcontrol.observation.observation_controller import ObservationController
 from tangostationcontrol.common.lofar_logging import (
     device_logging_to_python,
     log_exceptions,
 )
 from tangostationcontrol.configuration import ObservationSettings
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
+from tangostationcontrol.devices.observation_field import ObservationField
 from tangostationcontrol.common.device_decorators import only_when_on
 
+
 logger = logging.getLogger()
 
 __all__ = ["ObservationControl"]
@@ -29,31 +31,33 @@ __all__ = ["ObservationControl"]
 @device_logging_to_python()
 class ObservationControl(LOFARDevice):
     """Observation Control Device Server for LOFAR2.0
-    The ObservationControl Tango device controls the instantiation of a Tango Dynamic Device
-    from the Observation class.
-    ObservationControl then keeps a record of the Observation devices and if they are still alive.
+    The ObservationControl Tango device controls the instantiation of a Tango Dynamic
+    Device from the ObservationField class.
+    ObservationControl then keeps a record of the ObservationField devices and if they
+    are still alive.
 
     At the end of an observation ObservationControl checks if the respective
-    Observation device has stopped its execution and releases it. If the Observation device
-    has not stopped its execution yet, it is attempted to forcefully stop the execution
-    of the Observation device.
+    ObservationField devices have stopped its execution and releases it. If the
+    ObservationField devices have not stopped its execution yet, it is attempted to
+    forcefully stop the execution of them.
 
-    The Observation devices are responsible for the "real" execution of an observation.
-    They get references to the hardware devices that are needed to set values in the
-    relevant Control Points. The Observation device performs only a check if enough parameters
-    are available to perform the set-up.
+    The ObservationField devices are responsible for the "real" execution of an
+    observation. They get references to the hardware devices that are needed to set
+    values in the relevant Control Points. The ObservationField device performs only a
+    check if enough parameters are available to perform the set-up.
 
     Essentially this is what happens:
     Somebody calls ObservationControl.add_observation(parameters).
     Then ObservationControl will perform:
-    - Creates a new instance of an Observation device in the Tango DB
+    - Creates a new instances of an ObservationField devices in the Tango DB
+      (one per antenna field)
     - Call Initialise(parameters)
     - Wait for initialise to return
     - Check status()
     - If status() is NOT STANDBY, abort with an exception
     - Wait for start_time - lead_time
     - Call On()
-    - Subscribe to the Observation.running MP's periodic event
+    - Subscribe to the ObservationField.alive_R MP's periodic event
     - Register the observation in the dict self.running_observations[ID]
     - The Observation updates the MP every second with the current time
     - The callback gets called periodically.
@@ -65,19 +69,6 @@ class ObservationControl(LOFARDevice):
         - Call off()
         - Remove the device from the Tango DB which will make the device disappear
 
-    This should in broad strokes pretty much cover any type of observation.
-
-    ObservationControl can expose this interface:
-
-    Functions
-    - Normal lifecycle funcs: initialise, on, off
-    - add_observation(parameters)
-    - start_observation_now(ID)
-    - stop_observation_now(ID)
-    - stop_all_observations_now()
-    - running_observations() -> dict
-    - is_observation_running(obs_id) -> bool
-
     MPs
     - string version
     """
@@ -101,6 +92,15 @@ class ObservationControl(LOFARDevice):
     def running_observations_R(self):
         return self._observation_controller.running_observations
 
+    @attribute(
+        doc="List of active antenna fields.",
+        dtype=(str,),
+        max_dim_x=1000,
+        fisallowed="is_attribute_access_allowed",
+    )
+    def active_antenna_fields_R(self):
+        return self._observation_controller.active_antenna_fields
+
     def __init__(self, cl, name):
         # Super must be called after variable assignment due to executing init_device!
         super().__init__(cl, name)
@@ -113,10 +113,13 @@ class ObservationControl(LOFARDevice):
             self.myTangoDomain
         )
 
+        self._stop_all_observations_now()
+
     # Core functions
     @log_exceptions()
     @DebugIt()
     def init_device(self):
+        logger.debug("[ObservationControl] init device")
         Device.init_device(self)
         self.set_state(DevState.OFF)
 
@@ -130,7 +133,7 @@ class ObservationControl(LOFARDevice):
     def configure_for_off(self):
         super().configure_for_off()
 
-        self.stop_all_observations_now()
+        self._stop_all_observations_now()
 
     # API
     @command(dtype_in=DevString)
@@ -138,7 +141,10 @@ class ObservationControl(LOFARDevice):
     @log_exceptions()
     def start_observation(self, parameters: DevString = None):
         """Deprecated. For backward compatibility with old lofar-station-clients."""
-
+        logger.warning(
+            "Deprecated start_observation command used, please use add_observation "
+            "or start_observation_now instead"
+        )
         self.add_observation(parameters)
 
     @command(dtype_in=numpy.int64)
@@ -147,6 +153,10 @@ class ObservationControl(LOFARDevice):
     def stop_observation(self, obs_id: numpy.int64):
         """Deprecated. For backward compatibility with old lofar-station-clients."""
 
+        logger.warning(
+            "Deprecated stop_observation command used, please use stop_observation_now "
+            "instead"
+        )
         self.stop_observation_now(obs_id)
 
     @command(dtype_in=DevString)
@@ -176,12 +186,16 @@ class ObservationControl(LOFARDevice):
 
         self._observation_controller.stop_observation_now(obs_id)
 
+    def _stop_all_observations_now(self):
+        logger.info("Force stopping all observations!")
+        self._observation_controller.stop_all_observations_now()
+
     @command()
     @only_when_on()
     @log_exceptions()
     def stop_all_observations_now(self):
         """Force all observations to stop now."""
-        self._observation_controller.stop_all_observations_now()
+        self._stop_all_observations_now()
 
     @command(dtype_in=numpy.int64, dtype_out=DevBoolean)
     @only_when_on()
@@ -194,3 +208,23 @@ class ObservationControl(LOFARDevice):
     @log_exceptions()
     def is_any_observation_running(self) -> DevBoolean:
         return len(self._observation_controller.running_observations) > 0
+
+    @command(dtype_out=DevBoolean)
+    @only_when_on()
+    @log_exceptions()
+    def is_antenna_field_active(self, antenna_field: str) -> DevBoolean:
+        return antenna_field in self._observation_controller.active_antenna_fields
+
+    TEST_OBS_FIELD_NAME = "STAT/ObservationField/1"
+
+    @command()
+    def create_test_device(self):
+        Util.instance().create_device(
+            ObservationField.__name__, self.TEST_OBS_FIELD_NAME
+        )
+
+    @command()
+    def destroy_test_device(self):
+        Util.instance().delete_device(
+            ObservationField.__name__, self.TEST_OBS_FIELD_NAME
+        )
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation_field.py
similarity index 78%
rename from tangostationcontrol/tangostationcontrol/devices/observation.py
rename to tangostationcontrol/tangostationcontrol/devices/observation_field.py
index 9ccd2a821b2d0c3a8b01554ece37c9394e3aecdc..da4bc6c028d255a8f379c8b758609b44ecd8c93d 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_field.py
@@ -1,5 +1,7 @@
-#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-#  SPDX-License-Identifier: Apache-2.0
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+from datetime import datetime
 
 import logging
 from itertools import chain
@@ -24,26 +26,29 @@ from tangostationcontrol.common.lofar_logging import device_logging_to_python
 from tangostationcontrol.common.lofar_logging import log_exceptions
 from tangostationcontrol.common.proxy import create_device_proxy
 from tangostationcontrol.common.antennas import device_member_to_full_device_name
-from tangostationcontrol.configuration import ObservationSettings
+from tangostationcontrol.configuration import ObservationFieldSettings
 from tangostationcontrol.devices.base_device_classes.lofar_device import LOFARDevice
 from tangostationcontrol.common.device_decorators import fault_on_error
 from tangostationcontrol.common.device_decorators import only_in_states
 
+
 logger = logging.getLogger()
 
-__all__ = ["Observation"]
+__all__ = ["ObservationField"]
 
 
 @device_logging_to_python()
-class Observation(LOFARDevice):
-    """Observation Device for LOFAR2.0
+class ObservationField(LOFARDevice):
+    """ObservationField Device for LOFAR2.0
+
     This Tango device is responsible for the set-up of hardware for a
-    specific observation.  It will, if necessary keep tabs on HW MPs to signal
-    issues that are not caught by MPs being outside their nominal range.
+    specific observation and antenna field pair.  It will, if necessary keep tabs on
+    HW MPs to signal issues that are not caught by MPs being outside their nominal
+    range.
 
     The lifecycle of instances of this device is controlled by ObservationControl.
 
-    Settings are written to observation_settings_RW, which can only be done
+    Settings are written to observation_field_settings_RW, which can only be done
     in the OFF state.
     """
 
@@ -63,7 +68,15 @@ class Observation(LOFARDevice):
         dtype=numpy.int64,
     )
     def observation_id_R(self):
-        return self._observation_settings.observation_id
+        return self._observation_field_settings.observation_id
+
+    @attribute(
+        doc="Which antenna field this ObservationField configures",
+        dtype=str,
+        fisallowed="is_attribute_access_allowed",
+    )
+    def antenna_field_R(self):
+        return self._observation_field_settings.antenna_field
 
     @attribute(
         doc="Observation start time (seconds since 1970), or 0 if immediate.",
@@ -72,8 +85,10 @@ class Observation(LOFARDevice):
     )
     def start_time_R(self):
         return (
-            self._observation_settings.start_time.timestamp()
-            if self._observation_settings.start_time
+            datetime.fromisoformat(
+                self._observation_field_settings.start_time
+            ).timestamp()
+            if self._observation_field_settings.start_time
             else 0.0
         )
 
@@ -83,31 +98,26 @@ class Observation(LOFARDevice):
         dtype=numpy.float64,
     )
     def stop_time_R(self):
-        return self._observation_settings.stop_time.timestamp()
+        return datetime.fromisoformat(
+            self._observation_field_settings.stop_time
+        ).timestamp()
 
     @attribute(
-        doc="Seconds to be on sky before the observation start time, to fill buffers and allow for negative geometric delay compensation downstream.",
+        doc="Seconds to be on sky before the observation start time, to fill buffers "
+        "and allow for negative geometric delay compensation downstream.",
         unit="s",
         dtype=numpy.float64,
     )
     def lead_time_R(self):
-        return self._observation_settings.lead_time or 0.0
+        return self._observation_field_settings.lead_time or 0.0
 
     @attribute(
-        doc="Which antenna field this Observation configures",
-        dtype=str,
-        fisallowed="is_attribute_access_allowed",
-    )
-    def antenna_field_R(self):
-        return self._observation_settings.antenna_field
-
-    @attribute(
-        doc="Which antenna set this Observation configures",
+        doc="Which antenna set this ObservationField configures",
         dtype=str,
         fisallowed="is_attribute_access_allowed",
     )
     def antenna_set_R(self):
-        return self._observation_settings.antenna_set
+        return self._observation_field_settings.antenna_set
 
     @attribute(
         doc="Whether to add dithering to the signal to increase its linearity",
@@ -116,7 +126,7 @@ class Observation(LOFARDevice):
     )
     def dithering_enabled_R(self):
         try:
-            return self._observation_settings.dithering.enabled or False
+            return self._observation_field_settings.dithering.enabled or False
         except AttributeError:
             return False
 
@@ -128,7 +138,7 @@ class Observation(LOFARDevice):
     )
     def dithering_power_R(self):
         try:
-            return self._observation_settings.dithering.power or -4.0
+            return self._observation_field_settings.dithering.power or -4.0
         except AttributeError:
             return -4.0
 
@@ -140,7 +150,7 @@ class Observation(LOFARDevice):
     )
     def dithering_frequency_R(self):
         try:
-            return self._observation_settings.dithering.frequency or 102_000_000
+            return self._observation_field_settings.dithering.frequency or 102_000_000
         except AttributeError:
             return 102_000_000
 
@@ -150,7 +160,7 @@ class Observation(LOFARDevice):
         fisallowed="is_attribute_access_allowed",
     )
     def filter_R(self):
-        return self._observation_settings.filter
+        return self._observation_field_settings.filter
 
     @attribute(
         doc="Which subbands to beamform for each beamlet.",
@@ -160,7 +170,9 @@ class Observation(LOFARDevice):
     )
     def saps_subband_R(self):
         return numpy.array(
-            list(chain(*[sap.subbands for sap in self._observation_settings.SAPs])),
+            list(
+                chain(*[sap.subbands for sap in self._observation_field_settings.SAPs])
+            ),
             dtype=numpy.uint32,
         )
 
@@ -172,7 +184,7 @@ class Observation(LOFARDevice):
     )
     def saps_pointing_R(self):
         saps_pointing = []
-        for sap in self._observation_settings.SAPs:
+        for sap in self._observation_field_settings.SAPs:
             for _ in sap.subbands:
                 saps_pointing.append(
                     (
@@ -184,12 +196,13 @@ class Observation(LOFARDevice):
         return saps_pointing
 
     @attribute(
-        doc="Beamlet index of the FPGA output, at which to start mapping the beamlets of this observation.",
+        doc="Beamlet index of the FPGA output, at which to start mapping the beamlets "
+        "of this observation.",
         dtype=numpy.uint64,
         fisallowed="is_attribute_access_allowed",
     )
     def first_beamlet_R(self):
-        return self._observation_settings.first_beamlet
+        return self._observation_field_settings.first_beamlet
 
     @attribute(
         doc="Which pointing to beamform all HBA tiles to (if any).",
@@ -198,12 +211,12 @@ class Observation(LOFARDevice):
     )
     def HBA_tile_beam_R(self):
         try:
-            if self._observation_settings.HBA.tile_beam is None:
+            if self._observation_field_settings.HBA.tile_beam is None:
                 return None
         except AttributeError:
             return None
 
-        pointing_direction = self._observation_settings.HBA.tile_beam
+        pointing_direction = self._observation_field_settings.HBA.tile_beam
         return [
             str(pointing_direction.direction_type),
             f"{pointing_direction.angle1}rad",
@@ -217,7 +230,7 @@ class Observation(LOFARDevice):
     )
     def HBA_DAB_filter_R(self):
         try:
-            return self._observation_settings.HBA.DAB_filter or False
+            return self._observation_field_settings.HBA.DAB_filter or False
         except AttributeError:
             return False
 
@@ -228,45 +241,48 @@ class Observation(LOFARDevice):
     )
     def HBA_element_selection_R(self):
         try:
-            return self._observation_settings.HBA.element_selection or "ALL"
+            return self._observation_field_settings.HBA.element_selection or "ALL"
         except AttributeError:
             return "ALL"
 
-    observation_settings_RW = attribute(dtype=str, access=AttrWriteType.READ_WRITE)
+    observation_field_settings_RW = attribute(
+        dtype=str, access=AttrWriteType.READ_WRITE
+    )
 
     def __init__(self, cl, name):
         self.antennafield_proxy: Optional[DeviceProxy] = None
         self.beamlet_proxy: Optional[DeviceProxy] = None
         self.digitalbeam_proxy: Optional[DeviceProxy] = None
         self.tilebeam_proxy: Optional[DeviceProxy] = None
-        self._observation_settings: Optional[ObservationSettings] = None
+        self._observation_field_settings: Optional[ObservationFieldSettings] = None
 
         # Super must be called after variable assignment due to executing init_device!
         super().__init__(cl, name)
 
     def init_device(self):
         """Setup some class member variables for observation state"""
+        logger.debug("[ObservationField] init device")
         super().init_device()
 
     def configure_for_initialise(self):
         """Load the JSON from the attribute and configure member variables"""
 
-        if self._observation_settings is None:
+        if self._observation_field_settings is None:
             raise RuntimeError("Device can not be initialized without configuration")
 
         super().configure_for_initialise()
 
         logger.info(
-            f"Initialising observation with ID={self._observation_settings.observation_id} "
-            f"with settings: {self._observation_settings.to_json()}"
+            f"Initialising observation with ID={self._observation_field_settings.observation_id} "
+            f"with settings: {self._observation_field_settings.to_json()}"
         )
 
         self._prepare_observation()
 
         logger.info(
-            f"The observation with ID={self._observation_settings.observation_id} "
-            f"is initialised to run between {self._observation_settings.start_time} "
-            f"and {self._observation_settings.stop_time}."
+            f"The observation with ID={self._observation_field_settings.observation_id} "
+            f"is initialised to run between {self._observation_field_settings.start_time} "
+            f"and {self._observation_field_settings.stop_time}."
         )
 
     def configure_for_on(self):
@@ -277,7 +293,7 @@ class Observation(LOFARDevice):
         self._start_observation()
 
         logger.info(
-            f"Started the observation with ID={self._observation_settings.observation_id}."
+            f"Started the observation with ID={self._observation_field_settings.observation_id}."
         )
 
     def configure_for_off(self):
@@ -290,8 +306,8 @@ class Observation(LOFARDevice):
         logger.info(
             "Stopped the observation with ID=%s.",
             {
-                self._observation_settings.observation_id
-                if self._observation_settings
+                self._observation_field_settings.observation_id
+                if self._observation_field_settings
                 else None
             },
         )
@@ -309,7 +325,7 @@ class Observation(LOFARDevice):
         # certain aspects that only an Observation device can know.
 
         util = Util.instance()
-        antennafield = self._observation_settings.antenna_field
+        antennafield = self._observation_field_settings.antenna_field
         self.antennafield_proxy = create_device_proxy(
             device_member_to_full_device_name(antennafield)
         )
@@ -332,7 +348,7 @@ class Observation(LOFARDevice):
 
     @log_exceptions()
     def _start_observation(self):
-        """Configure the station for this observation."""
+        """Configure the station for this observation and antenna field."""
 
         # Apply ObservationID
         self.antennafield_proxy.FPGA_sdp_info_observation_id_RW = (
@@ -403,28 +419,30 @@ class Observation(LOFARDevice):
 
     @fault_on_error()
     @log_exceptions()
-    def read_observation_settings_RW(self):
+    def read_observation_field_settings_RW(self):
         """Return current observation_parameters string"""
         return (
             None
-            if self._observation_settings is None
-            else self._observation_settings.to_json()
+            if self._observation_field_settings is None
+            else self._observation_field_settings.to_json()
         )
 
     @only_in_states([DevState.OFF])
     @fault_on_error()
     @log_exceptions()
-    def write_observation_settings_RW(self, parameters: str):
+    def write_observation_field_settings_RW(self, parameters: str):
         """No validation on configuring parameters as task of control device"""
         try:
-            self._observation_settings = ObservationSettings.from_json(parameters)
-        except ValidationError:
-            self._observation_settings = None
-            raise
+            self._observation_field_settings = ObservationFieldSettings.from_json(
+                parameters
+            )
+        except ValidationError as ex:
+            self._observation_field_settings = None
+            raise ex
 
     def _is_HBA(self):
         """Return whether this observation should control a TileBeam device."""
-        return self._observation_settings.antenna_field.startswith("HBA")
+        return self._observation_field_settings.antenna_field.startswith("HBA")
 
     def _apply_antennafield_settings(self, filter_name: str):
         """Retrieve the RCU band from filter name, returning the correct format for
diff --git a/tangostationcontrol/tangostationcontrol/observation/__init__.py b/tangostationcontrol/tangostationcontrol/observation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..28c62b201ab99e836031972c5c0a1cf14e22ebde
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/observation/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+from tangostationcontrol.observation.observation_controller import ObservationController
+
+__all__ = ["ObservationController"]
diff --git a/tangostationcontrol/tangostationcontrol/observation/observation.py b/tangostationcontrol/tangostationcontrol/observation/observation.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e64f6bb80e43625f3efce127b02101452b6a4c7
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/observation/observation.py
@@ -0,0 +1,215 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import logging
+from typing import List
+import time
+
+from tango import DeviceProxy, Except, EventData
+from tango import DevFailed
+from tango import DevState
+from tangostationcontrol.configuration import ObservationSettings
+from tangostationcontrol.configuration import ObservationFieldSettings
+from tangostationcontrol.common.lofar_logging import log_exceptions
+from tangostationcontrol.observation.observation_field import ObservationField
+
+logger = logging.getLogger()
+
+
+class Observation(object):
+    """Manage one or more ObservationField devices depending on observation"""
+
+    def is_partially_running(self) -> bool:
+        """
+
+        :raises DevFailed: when calling :py:func:`is_running` failed
+        """
+        return any([field.is_running for field in self._observation_fields])
+
+    def is_running(self) -> bool:
+        """
+
+        :raises DevFailed: when calling :py:fund:`is_running` failed
+        """
+        return all([field.is_running() for field in self._observation_fields])
+
+    @property
+    def observation_id(self) -> int:
+        return self._parameters.antenna_fields[0].observation_id
+
+    @property
+    def antenna_fields(self) -> list[str]:
+        return [field.antenna_field for field in self._parameters.antenna_fields]
+
+    def __init__(self, tango_domain, parameters: ObservationSettings):
+        self._tango_domain: str = tango_domain
+        self._parameters: ObservationSettings = parameters
+
+        for antenna_field in self._parameters.antenna_fields:
+            if not antenna_field.observation_id == self.observation_id:
+                raise RuntimeError(
+                    "Observation configured for different observation IDs across "
+                    "antenna fields"
+                )
+
+        self._observation_fields: List[ObservationField] = []
+
+    @log_exceptions()
+    def observation_callback(self, event: EventData):
+        """
+        This callback checks and manages the state transitions
+        for each observation.
+
+        It starts observations at their specified start_time,
+        and stops & removes them at their specified stop_time.
+
+        :raises DevFailed: if calling :py:func:`~._update_observation_state` fails
+        """
+        if event.err:
+            # Something is fishy with this event.
+            logger.warning(
+                "The ObservationField device %s sent an event but the event \
+                signals an error.  It is advised to check the logs for any indication \
+                that something went wrong in that device.  Event data=%s",
+                event.device,
+                event,
+            )
+            return
+
+        # update the state of this observation, if needed
+        self._update_observation_state(event.device)
+
+    def _update_observation_state(self, device: DeviceProxy):
+        """Start / stop the observation managed by the given ObservationField device.
+
+        :raises DevFailed: if either calling :py:func:`~._stop_antenna_field`,
+                           :py:func:`~._start_antenna_field` or reading attribute fails
+        """
+
+        # Get the antenna field name
+        antenna_field = device.antenna_field_R
+
+        # Get the start/stop times from the sending device
+        obs_start_time = device.start_time_R
+        obs_stop_time = device.stop_time_R
+
+        # Get how much earlier we have to start
+        obs_lead_time = device.lead_time_R
+
+        # Obtain the current time ONCE to avoid race conditions
+        now = time.time()
+
+        # Manage state transitions
+        if now > obs_stop_time:
+            # Stop observation
+            logger.info("Time: %f now surpassed %f stopping", now, obs_stop_time)
+            self._stop_antenna_field(antenna_field)
+        elif (
+            now >= obs_start_time - obs_lead_time and device.state() == DevState.STANDBY
+        ):
+            logger.info(
+                "Time: %f now surpassed %f starting",
+                now,
+                obs_start_time - obs_lead_time,
+            )
+            # Start observation
+            self._start_antenna_field(antenna_field)
+
+    def create_devices(self):
+        """Call create_observation_field_device per ObservationField
+
+        :raises DevFailed: Raised when calling :py:func:`~._create_device` fails
+        """
+        for observation_field in self._parameters.antenna_fields:
+            self._create_device(observation_field)
+
+    def destroy_devices(self):
+        """Destroy each of the registered ObservationField device"""
+
+        for observation_field in self._observation_fields:
+            observation_field.destroy_observation_field_device()
+
+    def _create_device(self, parameters: ObservationFieldSettings):
+        """Create singular ObservationField device with ObservationFieldSettings
+
+        :raises DevFailed: Raised when :py:func:`create_observation_field_device` fails
+        """
+        f = ObservationField(self._tango_domain, parameters)
+        f.create_observation_field_device()
+        self._observation_fields.append(f)
+
+    def initialise_observation(self):
+        """Call initialise_observation per ObservationField
+
+        :raises DevFailed: Raised when :py:func:`initialise_observation_field` fails
+        """
+        for observation_field in self._observation_fields:
+            try:
+                observation_field.initialise_observation_field()
+            except DevFailed as ex:
+                error_string = (
+                    f"Failed to initialise observation: "
+                    f"{observation_field.observation_id} due to error for antenna "
+                    f"field: {observation_field.antenna_field}"
+                )
+                Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
+
+    def update(self):
+        """Call callback per ObservationField device proxy
+
+        :raises DevFailed: If calling :py:func:`~._update_observation_state` fails
+        """
+
+        for observation_field in self._observation_fields:
+            self._update_observation_state(observation_field.proxy)
+
+    def create_subscriptions(self):
+        """Register event subscription for callback per ObservationField device proxy
+
+        :raises DevFailed: Raised when :py:func:`subscribe` fails
+        """
+
+        for observation_field in self._observation_fields:
+            try:
+                observation_field.subscribe(self.observation_callback)
+            except DevFailed as ex:
+                error_string = (
+                    "Cannot subscribe to attribute. Cancelled observation: "
+                    f"{observation_field.observation_id} for antenna field: "
+                    f"{observation_field.antenna_field}"
+                )
+                Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
+
+    def _start_antenna_field(self, antenna_field: str):
+        """
+
+        raises DevFailed: when executing start for the specified observation field fails
+        """
+        for observation_field in self._observation_fields:
+            if observation_field.antenna_field == antenna_field:
+                observation_field.start()
+
+    def _stop_antenna_field(self, antenna_field: str):
+        """
+
+        raises DevFailed: when executing stop for the specified observation field fails
+        """
+        for observation_field in self._observation_fields:
+            if observation_field.antenna_field == antenna_field:
+                observation_field.stop()
+
+    def start(self):
+        """Call start on each ObservationField
+
+        raises DevFailed: when executing start for a specific observation field fails
+        """
+        for observation_field in self._observation_fields:
+            observation_field.start()
+
+    def stop(self):
+        """Call stop on each ObservationField
+
+        raises DevFailed: when executing stop for a specific observation field fails
+        """
+        for observation_field in self._observation_fields:
+            observation_field.stop()
diff --git a/tangostationcontrol/tangostationcontrol/observation/observation_controller.py b/tangostationcontrol/tangostationcontrol/observation/observation_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..25bbb7fd7e8d714eb63bd52c856b48e34e3817a8
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/observation/observation_controller.py
@@ -0,0 +1,133 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+from datetime import datetime
+import logging
+
+from tango import DevFailed, Util, Database
+from tangostationcontrol.configuration import ObservationSettings
+from tangostationcontrol.observation.observation import Observation
+from tangostationcontrol.devices.observation_field import ObservationField
+
+logger = logging.getLogger()
+
+
+class ObservationController(dict[int, Observation]):
+    """A dictionary of observations. Actively manages the observation state transitions
+    (start, stop)."""
+
+    def __init__(self, tango_domain: str):
+        super().__init__()
+        self._tango_util = Util.instance()
+        self._tango_domain = tango_domain
+
+    @property
+    def running_observations(self) -> list[int]:
+        return [
+            obs_id for obs_id, observation in self.items() if observation.is_running()
+        ]
+
+    @property
+    def active_antenna_fields(self) -> list[str]:
+        active_fields = []
+        running_field_sets = [
+            observation.antenna_fields
+            for obs_id, observation in self.items()
+            if observation.is_running()
+        ]
+        for running_field_set in running_field_sets:
+            active_fields.extend(running_field_set)
+        return active_fields
+
+    def add_observation(self, settings: ObservationSettings):
+        """Create an Observation which will start 1 or more ObservationField devices
+
+        :raises RuntimeError: if either settings contains multiple observation ids or if
+                              the creation and configuration of devices failed
+        """
+
+        # Check further properties that cannot be validated through a JSON schema
+        # TODO(Corne): Discuss do we want this?
+        for observation_field_settings in settings.antenna_fields:
+            if (
+                datetime.fromisoformat(observation_field_settings.stop_time)
+                <= datetime.now()
+            ):
+                raise ValueError(
+                    "Cannot start observation "
+                    f"{observation_field_settings.observation_id} because antenna "
+                    f"field {observation_field_settings.antenna_field} is already "
+                    f"past its stop time {observation_field_settings.stop_time}"
+                )
+
+        obs = Observation(self._tango_domain, settings)
+
+        try:
+            obs.create_devices()
+            obs.initialise_observation()
+            # Register this observation now that it has been successfully created
+            self[obs.observation_id] = obs
+            # Keep pinging to manage its state transitions
+            obs.create_subscriptions()
+            # Make sure the current state is accurate
+            obs.update()
+        except DevFailed as ex:
+            # Remove the devices again if creation, proxies or subscriptions fail.
+            obs.destroy_devices()
+
+            raise RuntimeError(
+                f"Observation: {settings.antenna_fields[0].observation_id} failed, "
+                "destroying devices..."
+            ) from ex
+
+    def start_observation(self, obs_id: int):
+        """Start the observation with the given ID
+
+        :raises DevFailed: if executing :py:func:`start` fails
+        :raises KeyError: if observation id is unknown
+        """
+        try:
+            observation = self[obs_id]
+        except KeyError as _exc:
+            raise KeyError(f"Unknown observation: {obs_id}")
+
+        observation.start()
+
+    def stop_observation_now(self, obs_id: int):
+        """Stop the observation with the given ID
+
+        :raises DevFailed: if executing :py:func:`stop` fails
+        :raises KeyError: if observation id is unknown
+        """
+        try:
+            observation = self.pop(obs_id)
+        except KeyError as _exc:
+            raise KeyError(f"Unknown observation: {obs_id}")
+
+        observation.stop()
+
+    def stop_all_observations_now(self):
+        """Stop all observations (running or to be run)"""
+        for obs_id in list(self):  # draw a copy as we modify the list
+            try:
+                self.stop_observation_now(obs_id)
+            except DevFailed as ex:
+                logger.exception(ex)
+
+        self._destroy_all_observation_field_devices()
+
+    @staticmethod
+    def _destroy_all_observation_field_devices():
+        """Prevent any lingering observation field devices, remove all from database"""
+        db = Database()
+        devices = db.get_device_exported_for_class(ObservationField.__name__)
+        for device in devices:
+            # if CaseInsensitiveString(device) == CaseInsensitiveString(
+            #     "Stat/ObservationField/1"
+            # ):
+            #     continue
+            try:
+                db.delete_device(device)
+                logger.warning("Destroyed lingering device: %s", device)
+            except Exception as ex:
+                logger.exception(ex)
diff --git a/tangostationcontrol/tangostationcontrol/observation/observation_field.py b/tangostationcontrol/tangostationcontrol/observation/observation_field.py
new file mode 100644
index 0000000000000000000000000000000000000000..a294b347c188121a9d6fc8b96c2045ce24c95731
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/observation/observation_field.py
@@ -0,0 +1,159 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import logging
+
+from tango import DevFailed, DevState, Except, Util, EventType, DeviceProxy
+from tangostationcontrol.common.proxy import create_device_proxy
+from tangostationcontrol.configuration.observation_field_settings import (
+    ObservationFieldSettings,
+)
+
+logger = logging.getLogger()
+
+
+class ObservationField(object):
+    """Manage specific antenna field for a particular observation id"""
+
+    @property
+    def proxy(self) -> DeviceProxy:
+        return self._device_proxy
+
+    @property
+    def observation_id(self) -> int:
+        return self._parameters.observation_id
+
+    @property
+    def antenna_field(self) -> str:
+        return self._parameters.antenna_field
+
+    @property
+    def class_name(self) -> str:
+        from tangostationcontrol.devices.observation_field import ObservationField
+
+        return ObservationField.__name__
+
+    @property
+    def device_name(self) -> str:
+        """Device names property creating unique device name"""
+        return (
+            f"{self._tango_domain}/{self.class_name}/{self.observation_id}-"
+            f"{self.antenna_field}"
+        )
+
+    @property
+    def attribute_name(self) -> str:
+        """Name for the ObservationField.observation_running subscription"""
+        return f"{self.device_name}/alive_R"
+
+    def __init__(self, tango_domain, parameters: ObservationFieldSettings):
+        self._device_proxy: DeviceProxy | None = None
+        self._event_id: int | None = None
+        self._parameters: ObservationFieldSettings = parameters
+        self._tango_domain: str = tango_domain
+
+        # The pyTango.Util class is a singleton and every DS can only
+        # have one instance of it.
+        self._tango_util: Util = Util.instance()
+
+    def create_observation_field_device(self):
+        """Instantiate an Observation Device
+
+        :raises DevFailed: when calling :py:func:`create_device` fails
+        """
+        logger.info("Create device: %s", self.device_name)
+        try:
+            # Create the Observation device and instantiate it.
+            self._tango_util.create_device(self.class_name, f"{self.device_name}")
+        except DevFailed as ex:
+            logger.exception(ex)
+            error_string = (
+                f"Cannot create the ObservationField device instance "
+                f"{self.device_name} for ID={self.observation_id} and "
+                f"field={self.antenna_field} "
+            )
+            Except.re_throw_exception(ex, "DevFailed", error_string, __name__)
+
+    def destroy_observation_field_device(self):
+        try:
+            self._tango_util.delete_device(self.class_name, self.device_name)
+        except DevFailed:
+            logger.exception(
+                f"Could not delete device {self.device_name} of class "
+                f"{self.class_name} from Tango DB."
+            )
+
+    def initialise_observation_field(self):
+        """
+
+        raises DevFailed: when an operation on the device proxy fails
+        """
+        # Instantiate a dynamic Tango Device "Observation".
+        self._device_proxy = create_device_proxy(self.device_name)
+
+        # Initialise generic properties
+        self.proxy.put_property({"Control_Children": [], "Power_Children": []})
+
+        # Configure the dynamic device its attribute for the observation
+        # parameters.
+        self.proxy.observation_field_settings_RW = self._parameters.to_json()
+
+        # Take the Observation device through the motions.  Pass the
+        # entire JSON set of parameters so that it can pull from it what it
+        # needs.
+        self.proxy.Initialise()
+
+    def start(self):
+        """
+
+        raises DevFailed: when executing the :py:func:`On` command fails
+        """
+        self.proxy.On()
+
+    def is_running(self) -> bool:
+        """
+
+        :raises DevFailed: when proxy state could not be retrieved
+        """
+        return self.proxy and self.proxy.state() == DevState.ON
+
+    def subscribe(self, cb):
+        """
+
+        raises DevFailed: when an operation on the device proxy fails
+        """
+        # Turn on the polling for the attribute.
+        # Note that this is not automatically done despite the attribute
+        # having the right polling values set in the ctor.
+        self.proxy.poll_attribute(self.attribute_name.split("/")[-1], 1000)
+
+        # Right. Now subscribe to periodic events.
+        self._event_id = self._device_proxy.subscribe_event(
+            self.attribute_name.split("/")[-1], EventType.PERIODIC_EVENT, cb
+        )
+        logger.info(
+            "Successfully started an observation field with ID=%s for antenna field=%s",
+            self.observation_id,
+            self.antenna_field,
+        )
+
+    def stop(self):
+        # Check if the device has not terminated itself in the meanwhile.
+        try:
+            self.proxy.ping()
+        except DevFailed:
+            logger.error(
+                f"Observation field device for ID={self.observation_id} and antenna"
+                f"field={self.antenna_field} unexpectedly disappeared."
+            )
+        else:
+            # Unsubscribe from the subscribed event.
+            self.proxy.unsubscribe_event(self._event_id)
+
+            # Tell the ObservationField device to stop the running
+            # observation for the given antenna field. This is a synchronous call and
+            # the clean-up does not take long.
+            self.proxy.Off()
+
+        # Finally remove the device object from the Tango DB.
+        self.destroy_observation_field_device()
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py
deleted file mode 100644
index 37e863b4e4f1e11e027082872c0d9b97d56a6d8c..0000000000000000000000000000000000000000
--- a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_base.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-
-
-class TestObservationBase:
-    VALID_JSON = """
-            {
-              "observation_id": 12345,
-              "stop_time": "2106-02-07T00:00:00",
-              "antenna_field": "HBA0",
-              "antenna_set": "ALL",
-              "filter": "HBA_110_190",
-              "SAPs": [{
-                    "subbands": [10, 20, 30],
-                    "pointing": {
-                        "angle1": 0.0261799, "angle2": 0, "direction_type": "J2000"
-                    }
-              }],
-              "dithering": {
-                "enabled": true,
-                "power": -10.0,
-                "frequency": 123000000
-              },
-              "HBA": {
-                "tile_beam":
-                  { "angle1": 0.0261799, "angle2": 0, "direction_type": "J2000" },
-                "DAB_filter": true,
-                "element_selection": "GENERIC_201512"
-              },
-              "first_beamlet": 0
-            }
-        """
diff --git a/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab8a8c626cda8c8f734d39727be2c95a4b9b94c3
--- /dev/null
+++ b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+import copy
+
+from tangostationcontrol.configuration import (
+    Dithering,
+    ObservationFieldSettings,
+    ObservationSettings,
+    Pointing,
+    Sap,
+    HBA,
+)
+
+SETTINGS_TWO_FIELDS = ObservationSettings(
+    "cs001",
+    [
+        ObservationFieldSettings(
+            5,
+            "2022-10-26T11:35:54.704150",
+            "2022-10-27T11:35:54.704150",
+            "HBA0",
+            "ALL",
+            "filter settings",
+            [
+                Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
+                Sap([1], Pointing(3.3, 4.4, "MOON")),
+            ],
+        ),
+        ObservationFieldSettings(
+            5,
+            "2022-10-26T11:35:54.704150",
+            "2022-10-27T11:35:54.704150",
+            "LBA",
+            "ALL",
+            "filter settings",
+            [
+                Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
+                Sap([1], Pointing(3.3, 4.4, "MOON")),
+            ],
+        ),
+    ],
+)
+
+SETTINGS_HBA_IMMEDIATE = ObservationSettings(
+    "cs001",
+    [
+        ObservationFieldSettings(
+            observation_id=12345,
+            start_time=None,
+            stop_time="2106-02-07T00:00:00",
+            antenna_field="HBA0",
+            antenna_set="ALL",
+            filter="HBA_110_190",
+            SAPs=[
+                Sap([10, 20, 30], Pointing(0.0261799, 0, "J2000")),
+            ],
+            HBA=HBA(
+                tile_beam=Pointing(0.0261799, 0, "J2000"),
+                DAB_filter=True,
+                element_selection="GENERIC_201512",
+            ),
+            dithering=Dithering(enabled=True, power=-10, frequency=123000000),
+        ),
+    ],
+)
+
+
+def get_observation_settings_two_fields() -> ObservationSettings:
+    """Get an observation with two antenna fields"""
+    return copy.deepcopy(SETTINGS_TWO_FIELDS)
+
+
+def get_observation_settings_hba_immediate() -> ObservationSettings:
+    """Get an observation with one antenna field and no start time"""
+    return copy.deepcopy(SETTINGS_HBA_IMMEDIATE)
diff --git a/tangostationcontrol/test/common/test_case_insensitive_string.py b/tangostationcontrol/test/common/test_case_insensitive_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee9ce71941ec7b8e9d0ebf7ccb5b0f2616e65828
--- /dev/null
+++ b/tangostationcontrol/test/common/test_case_insensitive_string.py
@@ -0,0 +1,22 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+from tangostationcontrol.common.case_insensitive_dict import CaseInsensitiveString
+
+from test import base
+
+
+class TestCaseInsensitiveString(base.TestCase):
+    def test_a_in_b(self):
+        """Get and set an item with different casing"""
+
+        self.assertTrue(CaseInsensitiveString("hba0") in CaseInsensitiveString("HBA0"))
+
+    def test_b_in_a(self):
+        """Get and set an item with different casing"""
+
+        self.assertTrue(CaseInsensitiveString("HBA0") in CaseInsensitiveString("hba0"))
+
+    def test_a_not_in_b(self):
+        """Get and set an item with different casing"""
+
+        self.assertFalse(CaseInsensitiveString("hba0") in CaseInsensitiveString("LBA0"))
diff --git a/tangostationcontrol/test/common/test_observation_controller.py b/tangostationcontrol/test/common/test_observation_controller.py
deleted file mode 100644
index a9d31c01c77e80dd712f843bdd436918e12fa090..0000000000000000000000000000000000000000
--- a/tangostationcontrol/test/common/test_observation_controller.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
-# SPDX-License-Identifier: Apache-2.0
-
-import importlib
-import sys
-from datetime import datetime
-from unittest import mock
-from unittest.mock import Mock
-
-from tango import DevState
-
-from tangostationcontrol.common import ObservationController
-from tangostationcontrol.common.observation_controller import Observation
-from tangostationcontrol.common.proxy import create_device_proxy
-from tangostationcontrol.configuration import ObservationSettings, Pointing, Sap
-
-from test import base
-
-
-class MockObservation(object):
-    def __init__(self, running):
-        self._running = running
-
-    def is_running(self):
-        return self._running
-
-
-@mock.patch("tango.Util.instance")
-class TestObservationController(base.TestCase):
-    """Test Observation Controller main operations"""
-
-    def test_observations_running(self, _):
-        sut = ObservationController("DMR")
-        self.assertListEqual([], sut.running_observations)
-        sut[1] = MockObservation(True)
-        self.assertListEqual([1], sut.running_observations)
-
-    def test_observations_not_running(self, _):
-        sut = ObservationController("DMR")
-        self.assertListEqual([], sut.running_observations)
-        sut[2] = MockObservation(False)
-        self.assertListEqual([], sut.running_observations)
-
-    def test_stop_all_observations_now_no_running(self, _):
-        sut = ObservationController("DMR")
-        sut.stop_all_observations_now()
-
-
-@mock.patch("tango.Util.instance")
-class TestObservation(base.TestCase):
-    SETTINGS = ObservationSettings(
-        5,
-        datetime.fromisoformat("2022-10-26T11:35:54.704150"),
-        datetime.fromisoformat("2022-10-27T11:35:54.704150"),
-        "HBA",
-        "ALL",
-        "filter settings",
-        [Sap([3, 2], Pointing(1.2, 2.1, "LMN")), Sap([1], Pointing(3.3, 4.4, "MOON"))],
-    )
-
-    def test_properties(self, _):
-        sut = Observation("DMR", TestObservation.SETTINGS)
-        self.assertEqual(5, sut.observation_id)
-        self.assertEqual("Observation", sut.class_name)
-        self.assertEqual("DMR/Observation/5", sut.device_name)
-        self.assertEqual("DMR/Observation/5/alive_R", sut.attribute_name)
-
-    def test_create_observation_device(self, tu_mock):
-        sut = Observation("DMR", TestObservation.SETTINGS)
-        sut.create_observation_device()
-
-    @mock.patch("tango.DeviceProxy")
-    def test_initialise_observation(self, dp_mock, tu_mock):
-        importlib.reload(sys.modules[create_device_proxy.__module__])
-        sut = Observation("DMR", TestObservation.SETTINGS)
-        sut.initialise_observation()
-
-        self.assertEqual(
-            dp_mock.return_value.observation_settings_RW,
-            TestObservation.SETTINGS.to_json(),
-        )
-        dp_mock.return_value.Initialise.assert_called()
-
-    @mock.patch("tango.DeviceProxy")
-    def test_start(self, dp_mock, tu_mock):
-        importlib.reload(sys.modules[create_device_proxy.__module__])
-        sut = Observation("DMR", TestObservation.SETTINGS)
-        sut.initialise_observation()
-        sut.start()
-
-        dp_mock.return_value.On.assert_called()
-
-    def test_subscribe(self, _):
-        def dummy():
-            pass
-
-        sut = Observation("DMR", TestObservation.SETTINGS)
-        dp_mock = Mock()
-        sut._device_proxy = dp_mock
-        sut.subscribe(dummy)
-
-        dp_mock.poll_attribute.assert_called()
-        dp_mock.subscribe_event.assert_called()
-
-    def test_stop(self, tu_mock):
-        importlib.reload(sys.modules[Observation.__module__])
-        sut = Observation("DMR", TestObservation.SETTINGS)
-
-        dp_mock = Mock()
-        dp_mock.state.return_value = DevState.OFF
-
-        sut._device_proxy = dp_mock
-
-        sut.stop()
-
-        dp_mock.ping.assert_called()
-        dp_mock.unsubscribe_event.assert_called()
-        dp_mock.Off.assert_called()
-
-        tu_mock.return_value.delete_device.assert_called()
diff --git a/tangostationcontrol/test/configuration/test_observation_field_settings.py b/tangostationcontrol/test/configuration/test_observation_field_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..90b8aae33e9acbd09e51916fc13545b4076597d5
--- /dev/null
+++ b/tangostationcontrol/test/configuration/test_observation_field_settings.py
@@ -0,0 +1,144 @@
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+import json
+
+from jsonschema.exceptions import ValidationError
+
+from tangostationcontrol.configuration import Pointing, ObservationFieldSettings, Sap
+
+from test import base
+
+
+class TestObservationFieldSettings(base.TestCase):
+    def test_from_json(self):
+        sut = ObservationFieldSettings.from_json(
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
+            '"antenna_field": "HBA", '
+            '"antenna_set": "ALL", "filter": "HBA_110_190",'
+            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}]}'
+        )
+
+        self.assertEqual(sut.observation_id, 3)
+        self.assertEqual(sut.stop_time, "2012-04-23T18:25:43")
+        self.assertEqual(sut.antenna_set, "ALL")
+        self.assertEqual(sut.filter, "HBA_110_190")
+        self.assertEqual(len(sut.SAPs), 1)
+
+        sut = ObservationFieldSettings.from_json(
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
+            '"antenna_field": "HBA", '
+            '"antenna_set": "ALL", "filter": "HBA_110_190",'
+            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],'
+            '"HBA": { "tile_beam": {"angle1":2.2, "angle2": 3.1, "direction_type":"MOON"} } }'
+        )
+
+        self.assertEqual(sut.HBA.tile_beam.angle1, 2.2)
+        self.assertEqual(sut.HBA.tile_beam.angle2, 3.1)
+        self.assertEqual(sut.HBA.tile_beam.direction_type, "MOON")
+
+        sut = ObservationFieldSettings.from_json(
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
+            '"antenna_field": "HBA", '
+            '"antenna_set": "ALL", "filter": "HBA_110_190",'
+            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],'
+            '"HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}'
+        )
+
+        self.assertEqual(sut.first_beamlet, 2)
+
+    def test_from_json_type_missmatch(self):
+        for json_str in [
+            # observation_id
+            '{"observation_id": "3", "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
+            # stop_time
+            '{"observation_id": 3, "stop_time": "test", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
+            # antenna_set
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": 4, "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
+            # filter
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": 1,"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
+            # SAPs
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": {"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}',
+            # first_beamlet
+            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": "2"}',
+        ]:
+            with self.assertRaises((ValidationError, ValueError), msg=f"{json_str}"):
+                ObservationFieldSettings.from_json(json_str)
+
+    def test_from_json_missing_fields(self):
+        complete_json = '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}'
+
+        for field in (
+            "observation_id",
+            "stop_time",
+            "antenna_field",
+            "antenna_set",
+            "filter",
+            "SAPs",
+        ):
+            # construct a JSON string with the provided field missing
+            json_dict = json.loads(complete_json)
+            del json_dict[field]
+            json_str = json.dumps(json_dict)
+
+            # trigger validation error
+            with self.assertRaises(
+                (ValidationError, ValueError), msg=f"Omitted field {field}: {json_str}"
+            ):
+                ObservationFieldSettings.from_json(json_str)
+
+    def test_to_json(self):
+        sut = ObservationFieldSettings(
+            5,
+            "2022-10-26T11:35:54.704150",
+            "2022-10-27T11:35:54.704150",
+            "HBA",
+            "ALL",
+            "filter settings",
+            [
+                Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
+                Sap([1], Pointing(3.3, 4.4, "MOON")),
+            ],
+        )
+        self.assertEqual(
+            sut.to_json(),
+            '{"observation_id": 5, "start_time": "2022-10-26T11:35:54.704150", '
+            '"stop_time": "2022-10-27T11:35:54.704150", '
+            '"antenna_field": "HBA", '
+            '"antenna_set": "ALL", "filter": "filter settings", "SAPs": '
+            '[{"subbands": [3, 2], "pointing": {"angle1": 1.2, "angle2": 2.1, '
+            '"direction_type": "LMN"}}, {"subbands": [1], "pointing": {"angle1": 3.3, '
+            '"angle2": 4.4, "direction_type": "MOON"}}], "first_beamlet": 0}',
+        )
+
+    def test_to_json_datetime_named_args(self):
+        sut = ObservationFieldSettings(
+            observation_id=5,
+            start_time=None,
+            stop_time="2106-02-07T00:00:00",
+            antenna_field="HBA",
+            antenna_set="ALL",
+            filter="filter settings",
+            SAPs=[
+                Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
+                Sap([1], Pointing(3.3, 4.4, "MOON")),
+            ],
+        )
+
+        self.assertEqual(
+            sut.to_json(),
+            '{"observation_id": 5, "stop_time": "2106-02-07T00:00:00", '
+            '"antenna_field": "HBA", "antenna_set": "ALL", "filter": '
+            '"filter settings", "SAPs": [{"subbands": [3, 2], "pointing": '
+            '{"angle1": 1.2, "angle2": 2.1, "direction_type": "LMN"}}, '
+            '{"subbands": [1], "pointing": {"angle1": 3.3, "angle2": 4.4, '
+            '"direction_type": "MOON"}}], "first_beamlet": 0}',
+        )
+
+    def test_throw_wrong_instance(self):
+        for json_str in [
+            '{"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}',
+            '{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}',
+        ]:
+            with self.assertRaises(ValidationError):
+                ObservationFieldSettings.from_json(json_str)
diff --git a/tangostationcontrol/test/configuration/test_observation_settings.py b/tangostationcontrol/test/configuration/test_observation_settings.py
index 6220062ee59e905d6ba0db31a93e7fb526234de0..e727d149fbffe3232e7a124a28ee55a149e34221 100644
--- a/tangostationcontrol/test/configuration/test_observation_settings.py
+++ b/tangostationcontrol/test/configuration/test_observation_settings.py
@@ -1,114 +1,72 @@
-#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-#  SPDX-License-Identifier: Apache-2.0
-
-import json
-from datetime import datetime
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
 
 from jsonschema.exceptions import ValidationError
 
-from tangostationcontrol.configuration import Pointing, ObservationSettings, Sap
+from tangostationcontrol.configuration import ObservationSettings
+from tangostationcontrol.configuration import Sap
+from tangostationcontrol.configuration import Pointing
+from tangostationcontrol.configuration import ObservationFieldSettings
+
 from test import base
 
 
 class TestObservationSettings(base.TestCase):
     def test_from_json(self):
         sut = ObservationSettings.from_json(
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
-            '"antenna_field": "HBA", '
-            '"antenna_set": "ALL", "filter": "HBA_110_190",'
-            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}]}'
-        )
-
-        self.assertEqual(sut.observation_id, 3)
-        self.assertEqual(sut.stop_time, datetime.fromisoformat("2012-04-23T18:25:43"))
-        self.assertEqual(sut.antenna_set, "ALL")
-        self.assertEqual(sut.filter, "HBA_110_190")
-        self.assertEqual(len(sut.SAPs), 1)
-
-        sut = ObservationSettings.from_json(
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
-            '"antenna_field": "HBA", '
-            '"antenna_set": "ALL", "filter": "HBA_110_190",'
-            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],'
-            '"HBA": { "tile_beam": {"angle1":2.2, "angle2": 3.1, "direction_type":"MOON"} } }'
-        )
-
-        self.assertEqual(sut.HBA.tile_beam.angle1, 2.2)
-        self.assertEqual(sut.HBA.tile_beam.angle2, 3.1)
-        self.assertEqual(sut.HBA.tile_beam.direction_type, "MOON")
-
-        sut = ObservationSettings.from_json(
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", '
-            '"antenna_field": "HBA", '
-            '"antenna_set": "ALL", "filter": "HBA_110_190",'
-            '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],'
-            '"HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}'
+            """
+            {
+                "station": "cs0001",
+                "antenna_fields": [{
+                    "observation_id": 3,
+                    "stop_time": "2012-04-23T18:25:43",
+                    "antenna_field": "HBA",
+                    "antenna_set": "ALL",
+                    "filter": "HBA_110_190",
+                    "SAPs": [{
+                        "subbands": [3, 2, 1],
+                        "pointing": {
+                            "angle1":1.2, "angle2": 2.1, "direction_type":"LMN"
+                        }
+                    }]
+                }]
+            }"""
         )
 
-        self.assertEqual(sut.first_beamlet, 2)
-
-    def test_from_json_type_missmatch(self):
-        for json_str in [
-            # observation_id
-            '{"observation_id": "3", "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
-            # stop_time
-            '{"observation_id": 3, "stop_time": "test", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
-            # antenna_set
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": 4, "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
-            # filter
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": 1,"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": 2}',
-            # SAPs
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": {"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}',
-            # first_beamlet
-            '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "first_beamlet": "2"}',
-        ]:
-            with self.assertRaises((ValidationError, ValueError), msg=f"{json_str}"):
-                ObservationSettings.from_json(json_str)
-
-    def test_from_json_missing_fields(self):
-        complete_json = '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", "antenna_field": "HBA", "antenna_set": "ALL", "filter": "HBA_110_190","SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}], "HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}, "first_beamlet": 2}'
-
-        for field in (
-            "observation_id",
-            "stop_time",
-            "antenna_field",
-            "antenna_set",
-            "filter",
-            "SAPs",
-        ):
-            # construct a JSON string with the provided field missing
-            json_dict = json.loads(complete_json)
-            del json_dict[field]
-            json_str = json.dumps(json_dict)
-
-            # trigger validation error
-            with self.assertRaises(
-                (ValidationError, ValueError), msg=f"Omitted field {field}: {json_str}"
-            ):
-                ObservationSettings.from_json(json_str)
+        self.assertEqual(sut.station, "cs0001")
+        self.assertEqual(sut.antenna_fields[0].observation_id, 3)
+        self.assertEqual(sut.antenna_fields[0].stop_time, "2012-04-23T18:25:43")
+        self.assertEqual(sut.antenna_fields[0].antenna_set, "ALL")
+        self.assertEqual(sut.antenna_fields[0].filter, "HBA_110_190")
+        self.assertEqual(len(sut.antenna_fields[0].SAPs), 1)
 
     def test_to_json(self):
         sut = ObservationSettings(
-            5,
-            datetime.fromisoformat("2022-10-26T11:35:54.704150"),
-            datetime.fromisoformat("2022-10-27T11:35:54.704150"),
-            "HBA",
-            "ALL",
-            "filter settings",
-            [
-                Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
-                Sap([1], Pointing(3.3, 4.4, "MOON")),
+            station="cs001",
+            antenna_fields=[
+                ObservationFieldSettings(
+                    5,
+                    "2022-10-26T11:35:54.704150",
+                    "2022-10-27T11:35:54.704150",
+                    "HBA",
+                    "ALL",
+                    "filter settings",
+                    [
+                        Sap([3, 2], Pointing(1.2, 2.1, "LMN")),
+                        Sap([1], Pointing(3.3, 4.4, "MOON")),
+                    ],
+                )
             ],
         )
         self.assertEqual(
             sut.to_json(),
-            '{"observation_id": 5, "start_time": "2022-10-26T11:35:54.704150", '
-            '"stop_time": "2022-10-27T11:35:54.704150", '
-            '"antenna_field": "HBA", '
-            '"antenna_set": "ALL", "filter": "filter settings", "SAPs": '
+            '{"station": "cs001", "antenna_fields": [{"observation_id": 5, "start_time": '
+            '"2022-10-26T11:35:54.704150", "stop_time": "2022-10-27T11:35:54.704150", '
+            '"antenna_field": "HBA", "antenna_set": "ALL", '
+            '"filter": "filter settings", "SAPs": '
             '[{"subbands": [3, 2], "pointing": {"angle1": 1.2, "angle2": 2.1, '
             '"direction_type": "LMN"}}, {"subbands": [1], "pointing": {"angle1": 3.3, '
-            '"angle2": 4.4, "direction_type": "MOON"}}], "first_beamlet": 0}',
+            '"angle2": 4.4, "direction_type": "MOON"}}], "first_beamlet": 0}]}',
         )
 
     def test_throw_wrong_instance(self):
diff --git a/tangostationcontrol/test/devices/test_observation_control_device.py b/tangostationcontrol/test/devices/test_observation_control_device.py
index 7b5c70be28c9bf76920a2e32dabbb081d2b03e40..535ebe62dd706b92d60216c82dbebfb6727f16d7 100644
--- a/tangostationcontrol/test/devices/test_observation_control_device.py
+++ b/tangostationcontrol/test/devices/test_observation_control_device.py
@@ -1,15 +1,11 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from tangostationcontrol.test.devices import test_observation_base
-
 from test import base
 
 
-class TestObservationControlDevice(
-    base.TestCase, test_observation_base.TestObservationBase
-):
+class TestObservationControlDevice(base.TestCase):
     def setUp(self):
         super(TestObservationControlDevice, self).setUp()
 
-    # Moved to Integration Test
+    # TODO(Corne): Either make this do something or remove this file, this makes 0 sense
diff --git a/tangostationcontrol/test/devices/test_observation_device.py b/tangostationcontrol/test/devices/test_observation_device.py
index 78392c400fb406fc0d1cdd569eb7c09b97698766..c70e7f8fc78f3da4fce0c011ab93eb4f433e05fb 100644
--- a/tangostationcontrol/test/devices/test_observation_device.py
+++ b/tangostationcontrol/test/devices/test_observation_device.py
@@ -1,14 +1,12 @@
 # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
 # SPDX-License-Identifier: Apache-2.0
 
-from tangostationcontrol.test.devices import test_observation_base
-
 from test.devices import device_base
 
 
-class TestObservationDevice(
-    device_base.DeviceTestCase, test_observation_base.TestObservationBase
-):
+class TestObservationDevice(device_base.DeviceTestCase):
     def setUp(self):
         # DeviceTestCase setUp patches lofar_device DeviceProxy
         super(TestObservationDevice, self).setUp()
+
+    # TODO(Corne): Either make this do something or remove this file, this makes 0 sense
diff --git a/tangostationcontrol/tangostationcontrol/test/devices/__init__.py b/tangostationcontrol/test/observation/__init__.py
similarity index 100%
rename from tangostationcontrol/tangostationcontrol/test/devices/__init__.py
rename to tangostationcontrol/test/observation/__init__.py
diff --git a/tangostationcontrol/test/observation/test_observation.py b/tangostationcontrol/test/observation/test_observation.py
new file mode 100644
index 0000000000000000000000000000000000000000..fae464f015e8cd2fc20e907b26e69e09817870b1
--- /dev/null
+++ b/tangostationcontrol/test/observation/test_observation.py
@@ -0,0 +1,150 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import copy
+import datetime
+from unittest import mock
+
+from tango import DevFailed, DevState
+from tangostationcontrol.observation.observation import Observation
+from tangostationcontrol.observation import observation_field
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_two_fields,
+)
+
+from test import base
+
+
+@mock.patch("tango.Util.instance")
+class TestObservation(base.TestCase):
+    def test_properties(self, _):
+        sut = Observation("DMR", get_observation_settings_two_fields())
+        self.assertEqual(5, sut.observation_id)
+        self.assertEqual(["HBA0", "LBA"], sut.antenna_fields)
+
+    @staticmethod
+    def mocked_observation_field_base(m_obs_field) -> Observation:
+        """Base function for tests using mocked ObservationField instances"""
+
+        obs_settings = get_observation_settings_two_fields()
+        sut = Observation("DMR", obs_settings)
+
+        for antenna_field in obs_settings.antenna_fields:
+            sut._observation_fields.append(
+                copy.deepcopy(m_obs_field(tango_domain="DMR", parameters=antenna_field))
+            )
+
+        return sut
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_is_running_none(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        for obs_field in sut._observation_fields:
+            obs_field.is_running.return_value = False
+
+        self.assertFalse(sut.is_running())
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_is_running(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        for obs_field in sut._observation_fields:
+            obs_field.is_running.return_value = True
+
+        self.assertTrue(sut.is_running())
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_is_partially_running(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        for obs_field in sut._observation_fields:
+            obs_field.is_running.return_value = False
+
+        sut._observation_fields[0].is_running = True
+
+        self.assertTrue(sut.is_partially_running())
+
+    def test_create_devices(self, tu_mock):
+        sut = Observation("DMR", get_observation_settings_two_fields())
+        sut.create_devices()
+
+        self.assertEqual(
+            len(get_observation_settings_two_fields().antenna_fields),
+            len(sut._observation_fields),
+        )
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_destroy_devices(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+        sut.destroy_devices()
+
+        for obs_field in sut._observation_fields:
+            obs_field.destroy_observation_field_device.assert_called_once()
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_initialise_observation(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        sut.initialise_observation()
+
+        for obs_field in sut._observation_fields:
+            obs_field.initialise_observation_field.assert_called_once()
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_start(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        sut.start()
+
+        for obs_field in sut._observation_fields:
+            obs_field.start.assert_called_once()
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_create_subscriptions(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        sut.create_subscriptions()
+
+        for obs_field in sut._observation_fields:
+            obs_field.subscribe.assert_called_once_with(sut.observation_callback)
+
+    def test_update_observation_state(self, tu_mock):
+        sut = Observation("DMR", get_observation_settings_two_fields())
+
+        sut._stop_antenna_field = mock.Mock()
+        sut._start_antenna_field = mock.Mock()
+
+        m_device_proxy = mock.Mock(
+            antenna_field_R="HBA",
+            start_time_R=datetime.datetime.now().timestamp(),
+            stop_time_R=(
+                datetime.datetime.now() + datetime.timedelta(hours=1)
+            ).timestamp(),
+            lead_time_R=5,
+            state=mock.Mock(return_value=DevState.STANDBY),
+        )
+
+        sut._update_observation_state(m_device_proxy)
+        sut._start_antenna_field.assert_called_once_with(m_device_proxy.antenna_field_R)
+
+        m_device_proxy.stop_time_R = datetime.datetime.now().timestamp()
+        sut._update_observation_state(m_device_proxy)
+        sut._stop_antenna_field.assert_called_once_with(m_device_proxy.antenna_field_R)
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_create_subscriptions_failed(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        sut._observation_fields[0].subscribe.side_effect = [DevFailed]
+
+        self.assertRaises(DevFailed, sut.create_subscriptions)
+
+    @mock.patch.object(observation_field, "ObservationField", autospec=True)
+    def test_stop(self, m_obs_field, tu_mock):
+        sut = self.mocked_observation_field_base(m_obs_field)
+
+        sut.stop()
+
+        for obs_field in sut._observation_fields:
+            obs_field.stop.assert_called_once()
diff --git a/tangostationcontrol/test/observation/test_observation_controller.py b/tangostationcontrol/test/observation/test_observation_controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f2ef4dcd8e802691b1ef954f276733b3949daf5
--- /dev/null
+++ b/tangostationcontrol/test/observation/test_observation_controller.py
@@ -0,0 +1,205 @@
+# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import copy
+from datetime import datetime, timedelta
+from typing import Dict
+from typing import List
+from unittest import mock
+
+from tango import DevFailed
+from tangostationcontrol.observation import observation_controller as obs_module
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_two_fields,
+)
+
+from test import base
+
+
+@mock.patch("tango.Util.instance")
+class TestObservationController(base.TestCase):
+    """Test Observation Controller main operations"""
+
+    def generate_observation(
+        self, running: bool, antenna_fields: list[str] = None
+    ) -> obs_module.Observation:
+        if not antenna_fields:
+            antenna_fields = ["HBA"]
+
+        m_obs_field = self.m_observation(tango_domain="DMR", parameters={})
+        m_obs_field.is_running.return_value = running
+        m_obs_field.antenna_fields = antenna_fields
+        return copy.deepcopy(m_obs_field)
+
+    def setUp(self):
+        super().setUp()
+        proxy_patcher = mock.patch.object(obs_module, "Observation", autospec=True)
+        self.m_observation = proxy_patcher.start()
+        self.addCleanup(proxy_patcher.stop)
+
+    def observation_setup(
+        self,
+        observations: Dict[int, obs_module.Observation],
+    ) -> obs_module.ObservationController:
+        sut = obs_module.ObservationController("DMR")
+        self.assertListEqual([], sut.running_observations)
+        for obs_id, observation in observations.items():
+            sut[obs_id] = observation
+        return sut
+
+    def observations_running(
+        self,
+        observations: Dict[int, obs_module.Observation],
+        expected_result: List[int],
+    ):
+        sut = self.observation_setup(observations)
+        self.assertListEqual(expected_result, sut.running_observations)
+
+    def test_observations_running(self, _m_tango_util):
+        self.observations_running(
+            observations={1: self.generate_observation(True)}, expected_result=[1]
+        )
+
+    def test_observations_not_running(self, _m_tango_util):
+        self.observations_running({2: self.generate_observation(False)}, [])
+
+    def test_observations_running_mix(self, _m_tango_util):
+        self.observations_running(
+            {
+                1: self.generate_observation(True),
+                2: self.generate_observation(False),
+                3: self.generate_observation(True),
+                5: self.generate_observation(False),
+            },
+            [1, 3],
+        )
+
+    def observations_antenna_fields(
+        self,
+        observations: Dict[int, obs_module.Observation],
+        expected_result: List[str],
+    ):
+        sut = self.observation_setup(observations)
+        self.assertListEqual(expected_result, sut.active_antenna_fields)
+
+    def test_active_field_single(self, _m_tango_util):
+        self.observations_antenna_fields(
+            {1: self.generate_observation(True, ["HBA"])}, ["HBA"]
+        )
+
+    def test_active_field_mix_multi(self, _m_tango_util):
+        self.observations_antenna_fields(
+            {
+                1: self.generate_observation(False, ["HBA"]),
+                2: self.generate_observation(True, ["HBA0"]),
+                3: self.generate_observation(True, ["LBA"]),
+                5: self.generate_observation(False, ["LBA"]),
+            },
+            ["HBA0", "LBA"],
+        )
+
+    def test_add_observation(self, _m_tango_util):
+        settings = get_observation_settings_two_fields()
+        for antenna_field in settings.antenna_fields:
+            antenna_field.stop_time = (datetime.now() + timedelta(days=1)).isoformat()
+
+        sut = obs_module.ObservationController("DMR")
+
+        self.m_observation.return_value.observation_id = "5"
+
+        sut.add_observation(settings)
+
+        self.m_observation.assert_called_once_with("DMR", settings)
+        self.m_observation.return_value.create_devices.assert_called_once()
+        self.m_observation.return_value.initialise_observation.assert_called_once()
+        self.m_observation.return_value.create_subscriptions.assert_called_once()
+        self.m_observation.return_value.update.assert_called_once()
+
+        self.assertEqual(
+            sut[self.m_observation.return_value.observation_id],
+            self.m_observation.return_value,
+        )
+
+    def test_add_observation_failed(self, _m_tango_util):
+        settings = get_observation_settings_two_fields()
+        for antenna_field in settings.antenna_fields:
+            antenna_field.stop_time = (datetime.now() + timedelta(days=1)).isoformat()
+
+        sut = obs_module.ObservationController("DMR")
+
+        self.m_observation.return_value.observation_id = "5"
+        self.m_observation.return_value.create_devices.side_effect = [DevFailed]
+
+        self.assertRaises(RuntimeError, sut.add_observation, settings)
+
+        self.m_observation.return_value.destroy_devices.assert_called_once()
+
+    def test_start_observation(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+
+        sut["5"] = mock.Mock()
+
+        sut.start_observation("5")
+
+        sut["5"].start.assert_called_once()
+
+    def test_start_observation_key_error(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+
+        self.assertRaises(KeyError, sut.start_observation, "12554812435")
+
+    def test_stop_observation(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+
+        m_observation = mock.Mock()
+        sut["5"] = m_observation
+
+        sut.stop_observation_now("5")
+
+        m_observation.stop.assert_called_once()
+
+    def test_stop_observation_key_error(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+
+        self.assertRaises(KeyError, sut.stop_observation_now, "12554812435")
+
+    def test_stop_all_observations_now_no_running(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+        sut._destroy_all_observation_field_devices = mock.Mock()
+
+        sut.stop_all_observations_now()
+
+        sut._destroy_all_observation_field_devices.assert_called_once()
+
+    def test_stop_all_observations_now_running(self, _m_tango_util):
+        sut = obs_module.ObservationController("DMR")
+        sut._destroy_all_observation_field_devices = mock.Mock()
+        sut.stop_observation_now = mock.Mock()
+
+        sut["5"] = mock.Mock()
+        sut["9"] = mock.Mock()
+
+        sut.stop_all_observations_now()
+
+        sut._destroy_all_observation_field_devices.assert_called_once()
+        self.assertEqual((("5",),), sut.stop_observation_now.call_args_list[0])
+        self.assertEqual((("9",),), sut.stop_observation_now.call_args_list[1])
+
+    @mock.patch.object(obs_module, "Database")
+    def test_destroy_all_observation_field_devices_errors(
+        self, m_database, _m_tango_util
+    ):
+        """Test that all exported devices for the class are destroyed"""
+        devices = [mock.Mock(), mock.Mock()]
+        m_database.return_value.get_device_exported_for_class.return_value = devices
+
+        m_database.return_value.delete_device.side_effect = [Exception]
+
+        obs_module.ObservationController._destroy_all_observation_field_devices()
+
+        self.assertEqual(
+            ((devices[0],),), m_database.return_value.delete_device.call_args_list[0]
+        )
+        self.assertEqual(
+            ((devices[1],),), m_database.return_value.delete_device.call_args_list[1]
+        )
diff --git a/tangostationcontrol/test/observation/test_observation_field.py b/tangostationcontrol/test/observation/test_observation_field.py
new file mode 100644
index 0000000000000000000000000000000000000000..29509ebe7b7cca5982df0e6a47b16ed4927ec3e1
--- /dev/null
+++ b/tangostationcontrol/test/observation/test_observation_field.py
@@ -0,0 +1,111 @@
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+
+import importlib
+import sys
+from unittest import mock
+from unittest.mock import Mock
+
+from tango import DevState, DevFailed
+
+from tangostationcontrol.observation import observation_field
+from tangostationcontrol.common.proxy import create_device_proxy
+from tangostationcontrol.test.dummy_observation_settings import (
+    get_observation_settings_two_fields,
+)
+
+from test import base
+
+
+@mock.patch("tango.Util.instance")
+class TestObservationField(base.TestCase):
+    def test_properties(self, _):
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+        self.assertEqual(5, sut.observation_id)
+        self.assertEqual("HBA0", sut.antenna_field)
+        self.assertEqual("ObservationField", sut.class_name)
+        self.assertEqual("DMR/ObservationField/5-HBA0", sut.device_name)
+        self.assertEqual("DMR/ObservationField/5-HBA0/alive_R", sut.attribute_name)
+
+    def test_create_observation_device(self, tu_mock):
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+        sut.create_observation_field_device()
+
+        tu_mock.return_value.create_device.assert_called_with(
+            sut.class_name, sut.device_name
+        )
+
+    def test_create_observation_device_fail(self, tu_mock):
+        """Test creation failed"""
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+
+        with mock.patch.object(observation_field, "logger") as m_logger:
+            tu_mock.return_value.create_device.side_effect = [DevFailed]
+
+            self.assertRaises(DevFailed, sut.create_observation_field_device)
+
+            m_logger.exception.assert_called()
+
+    @mock.patch("tango.DeviceProxy")
+    def test_initialise_observation_field(self, dp_mock, tu_mock):
+        importlib.reload(sys.modules[create_device_proxy.__module__])
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+        sut.initialise_observation_field()
+
+        self.assertEqual(
+            dp_mock.return_value.observation_field_settings_RW,
+            get_observation_settings_two_fields().antenna_fields[0].to_json(),
+        )
+        dp_mock.return_value.Initialise.assert_called()
+
+    @mock.patch("tango.DeviceProxy")
+    def test_start(self, dp_mock, tu_mock):
+        importlib.reload(sys.modules[create_device_proxy.__module__])
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+        sut.initialise_observation_field()
+        sut.start()
+
+        dp_mock.return_value.On.assert_called()
+
+    def test_subscribe(self, _):
+        def dummy():
+            pass
+
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+        dp_mock = Mock()
+        sut._device_proxy = dp_mock
+        sut.subscribe(dummy)
+
+        dp_mock.poll_attribute.assert_called()
+        dp_mock.subscribe_event.assert_called()
+
+    def test_stop(self, tu_mock):
+        importlib.reload(sys.modules[observation_field.ObservationField.__module__])
+        sut = observation_field.ObservationField(
+            "DMR", get_observation_settings_two_fields().antenna_fields[0]
+        )
+
+        dp_mock = Mock()
+        dp_mock.state.return_value = DevState.OFF
+
+        sut._device_proxy = dp_mock
+
+        sut.stop()
+
+        dp_mock.ping.assert_called()
+        dp_mock.unsubscribe_event.assert_called()
+        dp_mock.Off.assert_called()
+
+        tu_mock.return_value.delete_device.assert_called()
diff --git a/tangostationcontrol/tox.ini b/tangostationcontrol/tox.ini
index 2d716d729d35128024837720c32f8f99f509e3e4..3e4f13eb407b5df5a4724ea4c7fa27cc83ace5db 100644
--- a/tangostationcontrol/tox.ini
+++ b/tangostationcontrol/tox.ini
@@ -13,13 +13,11 @@ wheel_build_env = .pkg
 install_command = {envbindir}/pip3 install {opts} {packages}
 passenv = HOME
 setenv =
-   VIRTUAL_ENV={envdir}
    PYTHONWARNINGS=default::DeprecationWarning
 ; Share the same envdir with as many jobs as possible due to extensive time it
 ; takes to compile the pytango wheel, in addition to its large install size.
 ; should the environment change (such as the Python version) the environment
 ; will automatically be recreated.
-envdir = {toxworkdir}/testenv
 deps =
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/test-requirements.txt