diff --git a/.pylintrc b/.pylintrc
index b525115dfdbae35c577421c23b5d06fe6dd748f8..ddbbd9ecc51865295421a6d8a49fdfd06e0633de 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -258,7 +258,7 @@ contextmanager-decorators=contextlib.contextmanager
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E1101 when accessed. Python regular
 # expressions are accepted.
-generated-members=
+generated-members=target|logger
 
 # Tells whether missing members accessed in mixin class should be ignored. A
 # mixin class is detected if its name ends with "mixin" (case insensitive).
diff --git a/.release b/.release
index 159363b523554e5e4d3309b437f0a849c92efd69..05e9522e520fd7378d36f9906b34967bdedef8ed 100644
--- a/.release
+++ b/.release
@@ -1,2 +1,2 @@
-release=0.5.4
-tag=lmcbaseclasses-0.5.4
+release=0.6.0
+tag=lmcbaseclasses-0.6.0
diff --git a/README.md b/README.md
index cd8bc1314002d87e95ddb86444bfd61b4722a0be..aab1c1081d7b94f89247294ebae4bc66723b3d6f 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,36 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA
 
 ## Version History
 
+#### 0.6.0
+- Breaking change: State management
+  - SKABaseDevice implements a simple state machine with states
+    `DISABLED`, `OFF`, `ON`, `INIT` and `FAULT`, along with transitions
+    between them.
+  - SKASubarray implements full subarray state machine in accordance
+    with ADR-8 (the underlying state model supports all states and
+    transitions, including transitions through transient states; the
+    subarray device uses this state model but currently provide a
+    simple, purely synchronous implementation)
+  - Base classes provide subclassing code hooks that separate management
+    of device state from other device functionality. Thus, subclasses
+    are encouraged to leave state management in the care of the base
+    classes by:
+    - leaving `init_device()` alone and placing their (stateless)
+      initialisation code in the `do()` method of the `InitCommand` object instead. The base `init_device()` implementation will ensure that
+      the `do()` method is called, whilst ensuring state is managed e.g.
+      the device is put into state `IDLE` beforehand, and put into the
+      right state afterwards.
+    - leaving commands like `Configure()` alone and placing their
+      (stateless) implementation code in `ConfigureCommand.do()`
+      instead. This applies to all commands that affect device state:
+       `Off()`, `On()`, `AssignResources()`, `ReleaseResources()`,
+       `ReleaseAllResources()`, `Configure()`, `Scan()`, `EndScan()`,
+       `End()`, `Abort()`, `Reset()`, `Restart()`.
+    - leaving the base device to handle reads from and writes to the
+      state attributes `adminMode`, `obsState` and device `state`. For
+      example, do not call `Device.set_state()` directly; and do not
+      override methods like `write_adminMode()`.
+
 #### 0.5.4
 - Remove `ObsState` command from SKACapability, SKAObsDevice and SKASubarray Pogo XMI files.  It should not
   have been included - the `obsState` attribute provides this information. The command was not in the Python
diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst
new file mode 100644
index 0000000000000000000000000000000000000000..63c5d7100961e1c3cfa3970473916c840f4360d1
--- /dev/null
+++ b/docs/source/Commands.rst
@@ -0,0 +1,11 @@
+
+SKA Commands
+============================================
+
+.. toctree::
+   :maxdepth: 2
+
+.. automodule:: ska.base.commands
+    :members:
+    :undoc-members:
+
diff --git a/docs/source/SKASubarray.rst b/docs/source/SKASubarray.rst
index 4db79893338aa3fbdf320ff54c852e1896d79b03..93f320456ed91dd4d416971c7368675030a78213 100644
--- a/docs/source/SKASubarray.rst
+++ b/docs/source/SKASubarray.rst
@@ -6,6 +6,13 @@
 SKA Subarray
 ============================================
 
+The SKA Subarray device implements the observation state machine as defined
+in ADR-8:
+
+.. image:: images/ADR-8.png
+  :width: 400
+  :alt: Diagram of the observation state machine showing states and transitions
+
 .. toctree::
    :maxdepth: 2
 
diff --git a/docs/source/images/ADR-8.png b/docs/source/images/ADR-8.png
new file mode 100644
index 0000000000000000000000000000000000000000..620ef1226e29f34cb596eace4f6e4e0bcd5b2fa1
Binary files /dev/null and b/docs/source/images/ADR-8.png differ
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 68e1c1ff4a1ff5f049fe46fe154474b41d7f2609..adec635929c105702b9a76a53ba22effc83ddd0f 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -21,6 +21,7 @@ Welcome to LMC Base Classes documentation!
    Device:  SKA Subarray<SKASubarray>
 
    SKA Control Model<Control_Model>
+   SKA Commands<Commands>
 
 Indices and tables
 ==================
diff --git a/pogo/SKAAlarmHandler.xmi b/pogo/SKAAlarmHandler.xmi
index dd03cbdc27904a443c912d3e329ed0456cb5338b..a6813c57a0939f69dc2fe50e4c5e48efdb163804 100644
--- a/pogo/SKAAlarmHandler.xmi
+++ b/pogo/SKAAlarmHandler.xmi
@@ -100,7 +100,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true" concreteHere="false"/>
     </commands>
diff --git a/pogo/SKABaseDevice.xmi b/pogo/SKABaseDevice.xmi
index ef62353029e7084f3431e35b36f4566ecaa5d3fb..a04e519cfccea1c30e0cf38d025f9edfbe4b1874 100644
--- a/pogo/SKABaseDevice.xmi
+++ b/pogo/SKABaseDevice.xmi
@@ -55,7 +55,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
diff --git a/pogo/SKACapability.xmi b/pogo/SKACapability.xmi
index 22dcbeae281cbccbdc66718538bf74ba738a9c6e..7ffc297955514b6c6f9c5207d4f2d7e94a609a32 100644
--- a/pogo/SKACapability.xmi
+++ b/pogo/SKACapability.xmi
@@ -68,7 +68,7 @@
         <type xsi:type="pogoDsl:UShortType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -77,7 +77,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true"/>
     </commands>
diff --git a/pogo/SKALogger.xmi b/pogo/SKALogger.xmi
index d7c3d9d066122948e2fff94f51719c5c363cdc24..f5b5b10f65b4464193c8b2d5de6bc40760be2115 100644
--- a/pogo/SKALogger.xmi
+++ b/pogo/SKALogger.xmi
@@ -46,8 +46,8 @@
       <argin description="Logging level for selected devices:&#xA;(0=OFF, 1=FATAL, 2=ERROR, 3=WARNING, 4=INFO, 5=DEBUG).&#xA;Example: [[4, 5], ['my/dev/1', 'my/dev/2']].">
         <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argin>
-      <argout description="No output arguments">
-        <type xsi:type="pogoDsl:VoidType"/>
+      <argout description="">
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -65,7 +65,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true"/>
     </commands>
diff --git a/pogo/SKAMaster.xmi b/pogo/SKAMaster.xmi
index 4849959f8ca42e2a6c19c5744f07817354d53322..6b37e806bf8709964adc1df9b04979fe1f688f59 100644
--- a/pogo/SKAMaster.xmi
+++ b/pogo/SKAMaster.xmi
@@ -78,7 +78,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true"/>
     </commands>
diff --git a/pogo/SKAObsDevice.xmi b/pogo/SKAObsDevice.xmi
index a583296bea9b4436ff8c79cf26efc4ba76382619..866600078e08168344fe9f256666f1229bde28f7 100644
--- a/pogo/SKAObsDevice.xmi
+++ b/pogo/SKAObsDevice.xmi
@@ -56,7 +56,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true"/>
     </commands>
@@ -65,13 +65,17 @@
       <dataReadyEvent fire="false" libCheckCriteria="true"/>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
       <properties description="Observing State" label="" unit="" standardUnit="" displayUnit="" format="" maxValue="" minValue="" maxAlarm="" minAlarm="" maxWarning="" minWarning="" deltaTime="" deltaValue=""/>
+      <enumLabels>EMPTY</enumLabels>
+      <enumLabels>RESOURCING</enumLabels>
       <enumLabels>IDLE</enumLabels>
       <enumLabels>CONFIGURING</enumLabels>
       <enumLabels>READY</enumLabels>
       <enumLabels>SCANNING</enumLabels>
-      <enumLabels>PAUSED</enumLabels>
+      <enumLabels>ABORTING</enumLabels>
       <enumLabels>ABORTED</enumLabels>
+      <enumLabels>RESETTING</enumLabels>
       <enumLabels>FAULT</enumLabels>
+      <enumLabels>RESTARTING</enumLabels>
     </attributes>
     <attributes name="obsMode" attType="Scalar" rwType="READ" displayLevel="OPERATOR" polledPeriod="1000" maxX="" maxY="" allocReadMember="true" isDynamic="false">
       <dataType xsi:type="pogoDsl:EnumType"/>
diff --git a/pogo/SKASubarray.xmi b/pogo/SKASubarray.xmi
index fee6b291a6318210840eb132e1a1110bed7cad57..3622127756242079c7f33346303d1722c8daaee9 100644
--- a/pogo/SKASubarray.xmi
+++ b/pogo/SKASubarray.xmi
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="ASCII"?>
 <pogoDsl:PogoSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pogoDsl="http://www.esrf.fr/tango/pogo/PogoDsl">
   <classes name="SKASubarray" pogoRevision="9.6">
-    <description description="SubArray handling device" title="SKASubarray" sourcePath="/home/tango/src/lmc-base-classes/pogo" language="PythonHL" filestogenerate="XMI   file,Code files,Python Package,Protected Regions" license="none" copyright="" hasMandatoryProperty="false" hasConcreteProperty="true" hasAbstractCommand="false" hasAbstractAttribute="false">
+    <description description="SubArray handling device" title="SKASubarray" sourcePath="/home/tango/src/lmc-base-classes/pogo" language="PythonHL" filestogenerate="XMI   file" license="none" copyright="" hasMandatoryProperty="false" hasConcreteProperty="true" hasAbstractCommand="false" hasAbstractAttribute="false">
       <inheritances classname="Device_Impl" sourcePath=""/>
       <inheritances classname="SKAObsDevice" sourcePath="./"/>
       <identification contact="at ska.ac.za - cam" author="cam" emailDomain="ska.ac.za" classFamily="SkaBase" siteSpecific="" platform="All Platforms" bus="Not Applicable" manufacturer="none" reference=""/>
@@ -37,34 +37,16 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
-      </argout>
-      <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
-    </commands>
-    <commands name="ConfigureCapability" description="Configures number of instances for each capability. If the capability exists, &#xA;it increments the configured instances by the number of instances requested, &#xA;otherwise an exception will be raised.&#xA;Note: The two lists arguments must be of equal length or an exception will be raised." execMethod="configure_capability" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
-      <argin description="[Number of instances to add][Capability types]">
         <type xsi:type="pogoDsl:LongStringArrayType"/>
-      </argin>
-      <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
-    <commands name="DeconfigureAllCapabilities" description="Deconfigure all instances of the given Capability type. If the capability type does not exist an exception will be raised, &#xA;otherwise it sets the configured instances for that capability type to zero." execMethod="deconfigure_all_capabilities" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
-      <argin description="Capability type">
+    <commands name="Configure" description="Configures the scan for this subarray" execMethod="configure" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
+      <argin description="The scan configuration, formatted as a JSON string">
         <type xsi:type="pogoDsl:StringType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
-      </argout>
-      <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
-    </commands>
-    <commands name="DeconfigureCapability" description="Deconfigures a given number of instances for each capability. If the capability exists, &#xA;it decrements the configured instances by the number of instances requested,&#xA;otherwise an exceptioin will be raised.&#xA;Note: The two lists arguments must be of equal length or an exception will be raised" execMethod="deconfigure_capability" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
-      <argin description="[Number of instances to remove][Capability types]">
         <type xsi:type="pogoDsl:LongStringArrayType"/>
-      </argin>
-      <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -99,35 +81,26 @@
       <argin description="List of Resources to add to subarray.">
         <type xsi:type="pogoDsl:StringArrayType"/>
       </argin>
-      <argout description="A list of Resources added to the subarray.">
-        <type xsi:type="pogoDsl:StringArrayType"/>
-      </argout>
-      <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
-    </commands>
-    <commands name="EndSB" description="Change obsState to IDLE." execMethod="end_sb" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
-      <argin description="">
-        <type xsi:type="pogoDsl:VoidType"/>
-      </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
-    <commands name="EndScan" description="Terminate scan." execMethod="end_scan" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
+    <commands name="End" description="Change obsState to IDLE." execMethod="end" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
       <argin description="">
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
-    <commands name="Pause" description="Pause scan." execMethod="pause" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
+    <commands name="EndScan" description="Terminate scan." execMethod="end_scan" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
       <argin description="">
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -135,8 +108,8 @@
       <argin description="">
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
-      <argout description="List of resources removed from the subarray.">
-        <type xsi:type="pogoDsl:StringArrayType"/>
+      <argout description="">
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -144,8 +117,8 @@
       <argin description="List of resources to remove from the subarray.">
         <type xsi:type="pogoDsl:StringArrayType"/>
       </argin>
-      <argout description="List of resources removed from the subarray.">
-        <type xsi:type="pogoDsl:StringArrayType"/>
+      <argout description="">
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -154,16 +127,25 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true" concreteHere="false"/>
     </commands>
-    <commands name="Resume" description="Resume scan." execMethod="resume" displayLevel="OPERATOR" polledPeriod="0" isDynamic="false">
+    <commands name="ObsReset" description="Reset observation state machine to its default state" execMethod="obsreset" displayLevel="OPERATOR" polledPeriod="0">
       <argin description="">
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
+      </argout>
+      <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
+    </commands>
+    <commands name="Restart" description="Restart the observation state machine" execMethod="restart" displayLevel="OPERATOR" polledPeriod="0">
+      <argin description="">
         <type xsi:type="pogoDsl:VoidType"/>
+      </argin>
+      <argout description="">
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -172,7 +154,7 @@
         <type xsi:type="pogoDsl:StringArrayType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="false" concrete="true" concreteHere="true"/>
     </commands>
@@ -293,7 +275,7 @@
     <states name="DISABLE" description="The device cannot be switched ON for an external reason. E.g. the power supply has its door open, the safety conditions are not satisfactory to allow the device to operate.">
       <status abstract="false" inherited="true" concrete="true"/>
     </states>
-    <preferences docHome="./doc_html" makefileHome="/usr/local/share/pogo/preferences"/>
+    <preferences docHome="./doc_html" makefileHome="$(TANGO_HOME)"/>
     <overlodedPollPeriodObject name="adminMode" type="attribute" pollPeriod="0"/>
     <overlodedPollPeriodObject name="configurationDelayExpected" type="attribute" pollPeriod="0"/>
     <overlodedPollPeriodObject name="configurationProgress" type="attribute" pollPeriod="0"/>
diff --git a/pogo/SKATelState.xmi b/pogo/SKATelState.xmi
index 90b2540108106243177c391e5a49a40d4a8d3a05..0aebc36fa558f5f0a056b05f4c07f5b4cdb86fd3 100644
--- a/pogo/SKATelState.xmi
+++ b/pogo/SKATelState.xmi
@@ -60,7 +60,7 @@
         <type xsi:type="pogoDsl:VoidType"/>
       </argin>
       <argout description="">
-        <type xsi:type="pogoDsl:VoidType"/>
+        <type xsi:type="pogoDsl:LongStringArrayType"/>
       </argout>
       <status abstract="false" inherited="true" concrete="true"/>
     </commands>
diff --git a/src/ska/base/__init__.py b/src/ska/base/__init__.py
index 4478f8e6a34147e695a30f5fac561273522b976d..cebd343850fee474796a5d3e1514374c0fa1dc40 100644
--- a/src/ska/base/__init__.py
+++ b/src/ska/base/__init__.py
@@ -1,25 +1,28 @@
 __all__ = (
+    "commands",
     "control_model",
     "SKAAlarmHandler",
-    "SKABaseDevice",
+    "SKABaseDevice", "SKABaseDeviceStateModel",
     "SKACapability",
     "SKALogger",
     "SKAMaster",
-    "SKAObsDevice",
-    "SKASubarray",
-    "SKATelState"
+    "SKAObsDevice", "SKAObsDeviceStateModel",
+    "SKASubarray", "SKASubarrayStateModel", "SKASubarrayResourceManager",
+    "SKATelState",
 )
 
 # Note: order of imports is important - start with lowest in the hierarchy
 
 # SKABaseDevice, and then classes that inherit from it
-from .base_device import SKABaseDevice
+from .base_device import SKABaseDevice, SKABaseDeviceStateModel
 from .alarm_handler_device import SKAAlarmHandler
 from .logger_device import SKALogger
 from .master_device import SKAMaster
 from .tel_state_device import SKATelState
 
 # SKAObsDevice, and then classes that inherit from it
-from .obs_device import SKAObsDevice
+from .obs_device import SKAObsDevice, SKAObsDeviceStateModel
 from .capability_device import SKACapability
-from .subarray_device import SKASubarray
+from .subarray_device import (
+    SKASubarray, SKASubarrayStateModel, SKASubarrayResourceManager
+)
diff --git a/src/ska/base/alarm_handler_device.py b/src/ska/base/alarm_handler_device.py
index 2a05b700db001bf509c84c16306d78f5067d096a..848c479307980e2650f43f84c822a5847d47e9de 100644
--- a/src/ska/base/alarm_handler_device.py
+++ b/src/ska/base/alarm_handler_device.py
@@ -4,23 +4,21 @@
 #
 #
 #
-""" SKAAlarmHandler
-
-A generic base device for Alarms for SKA. It exposes SKA alarms and SKA alerts as TANGO attributes.
-SKA Alarms and SKA/Element Alerts are rules-based configurable conditions that can be defined over multiple
-attribute values and quality factors, and are separate from the "built-in" TANGO attribute alarms.
+"""
+This module implements SKAAlarmHandler, a generic base device for Alarms
+for SKA. It exposes SKA alarms and SKA alerts as TANGO attributes. SKA
+Alarms and SKA/Element Alerts are rules-based configurable conditions
+that can be defined over multiple attribute values and quality factors,
+and are separate from the "built-in" TANGO attribute alarms.
 """
 # PROTECTED REGION ID(SKAAlarmHandler.additionnal_import) ENABLED START #
-# Standard imports
-import os
-import sys
-
 # Tango imports
 from tango import DebugIt
 from tango.server import run, attribute, command, device_property
 
 # SKA specific imports
-from . import SKABaseDevice, release
+from ska.base import SKABaseDevice
+from ska.base.commands import BaseCommand
 
 # PROTECTED REGION END #    //  SKAAlarmHandler.additionnal_import
 
@@ -91,13 +89,31 @@ class SKAAlarmHandler(SKABaseDevice):
     # General methods
     # ---------------
 
-    def init_device(self):
-        SKABaseDevice.init_device(self)
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # PROTECTED REGION ID(SKAAlarmHandler.init_device) ENABLED START #
-        # PROTECTED REGION END #    //  SKAAlarmHandler.init_device
+    def init_command_objects(self):
+        """
+        Sets up the command objects
+        """
+        super().init_command_objects()
+        self.register_command_object(
+            "GetAlarmRule",
+            self.GetAlarmRuleCommand(self, self.state_model, self.logger)
+        )
+        self.register_command_object(
+            "GetAlarmData",
+            self.GetAlarmDataCommand(self, self.state_model, self.logger)
+        )
+        self.register_command_object(
+            "GetAlarmAdditionalInfo",
+            self.GetAlarmAdditionalInfoCommand(self, self.state_model, self.logger)
+        )
+        self.register_command_object(
+            "GetAlarmStats",
+            self.GetAlarmStatsCommand(self, self.state_model, self.logger)
+        )
+        self.register_command_object(
+            "GetAlertStats",
+            self.GetAlertStatsCommand(self, self.state_model, self.logger)
+        )
 
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKAAlarmHandler.always_executed_hook) ENABLED START #
@@ -180,16 +196,88 @@ class SKAAlarmHandler(SKABaseDevice):
     # Commands
     # --------
 
+    class GetAlarmRuleCommand(BaseCommand):
+        """
+        A class for the SKAAlarmHandler's GetAlarmRule() command.
+        """
+        def do(self, argin):
+            """
+            Stateless hook for SKAAlarmHandler GetAlarmRule() command.
+
+            :return: Alarm configuration info: rule, actions, etc.
+            :rtype: JSON string
+            """
+            return ""
+
+    class GetAlarmDataCommand(BaseCommand):
+        """
+        A class for the SKAAlarmHandler's GetAlarmData() command.
+        """
+        def do(self, argin):
+            """
+            Stateless hook for SKAAlarmHandler GetAlarmData() command.
+
+            :return: Alarm data
+            :rtype: JSON string
+            """
+            return ""
+
+    class GetAlarmAdditionalInfoCommand(BaseCommand):
+        """
+        A class for the SKAAlarmHandler's GetAlarmAdditionalInfo()
+        command.
+        """
+        def do(self, argin):
+            """
+            Stateless hook for SKAAlarmHandler GetAlarmAdditionalInfo()
+            command.
+
+            :return: Alarm additional info
+            :rtype: JSON string
+            """
+            return ""
+
+    class GetAlarmStatsCommand(BaseCommand):
+        """
+        A class for the SKAAlarmHandler's GetAlarmStats() command.
+        """
+        def do(self):
+            """
+            Stateless hook for SKAAlarmHandler GetAlarmStats() command.
+
+            :return: Alarm stats
+            :rtype: JSON string
+            """
+            return ""
+
+    class GetAlertStatsCommand(BaseCommand):
+        """
+        A class for the SKAAlarmHandler's GetAlertStats() command.
+        """
+        def do(self):
+            """
+            Stateless hook for SKAAlarmHandler GetAlertStats() command.
+
+            :return: Alert stats
+            :rtype: JSON string
+            """
+            return ""
+
     @command(dtype_in='str', doc_in="Alarm name", dtype_out='str', doc_out="JSON string",)
     @DebugIt()
     def GetAlarmRule(self, argin):
         # PROTECTED REGION ID(SKAAlarmHandler.GetAlarmRule) ENABLED START #
         """
         Get all configuration info of the alarm, e.g. rule, defined action, etc.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :param argin: Name of the alarm
         :return: JSON string containing configuration information of the alarm
         """
-        return ""
+        command = self.get_command_object("GetAlarmRule")
+        return command(argin)
         # PROTECTED REGION END #    //  SKAAlarmHandler.GetAlarmRule
 
     @command(dtype_in='str', doc_in="Alarm name", dtype_out='str', doc_out="JSON string",)
@@ -199,10 +287,15 @@ class SKAAlarmHandler(SKABaseDevice):
         """
         Get list of current value, quality factor and status of
         all attributes participating in the alarm rule.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :param argin: Name of the alarm
         :return: JSON string containing alarm data
         """
-        return ""
+        command = self.get_command_object("GetAlarmData")
+        return command(argin)
         # PROTECTED REGION END #    //  SKAAlarmHandler.GetAlarmData
 
     @command(dtype_in='str', doc_in="Alarm name", dtype_out='str', doc_out="JSON string", )
@@ -211,10 +304,15 @@ class SKAAlarmHandler(SKABaseDevice):
         # PROTECTED REGION ID(SKAAlarmHandler.GetAlarmAdditionalInfo) ENABLED START #
         """
         Get additional alarm information.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :param argin: Name of the alarm
         :return: JSON string containing additional alarm information
         """
-        return ""
+        command = self.get_command_object("GetAlarmAdditionalInfo")
+        return command(argin)
         # PROTECTED REGION END #    //  SKAAlarmHandler.GetAlarmAdditionalInfo
 
     @command(dtype_out='str', doc_out="JSON string",)
@@ -223,9 +321,14 @@ class SKAAlarmHandler(SKABaseDevice):
         # PROTECTED REGION ID(SKAAlarmHandler.GetAlarmStats) ENABLED START #
         """
         Get current alarm stats.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :return: JSON string containing alarm statistics
         """
-        return ""
+        command = self.get_command_object("GetAlarmStats")
+        return command()
         # PROTECTED REGION END #    //  SKAAlarmHandler.GetAlarmStats
 
     @command(dtype_out='str', doc_out="JSON string",)
@@ -234,9 +337,14 @@ class SKAAlarmHandler(SKABaseDevice):
         # PROTECTED REGION ID(SKAAlarmHandler.GetAlertStats) ENABLED START #
         """
         Get current alert stats.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :return: JSON string containing alert statistics
         """
-        return ""
+        command = self.get_command_object("GetAlertStats")
+        return command()
         # PROTECTED REGION END #    //  SKAAlarmHandler.GetAlertStats
 
 # ----------
@@ -255,5 +363,6 @@ def main(args=None, **kwargs):
     return run((SKAAlarmHandler,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKAAlarmHandler.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/base_device.py b/src/ska/base/base_device.py
index effa193670c5acd1f7adde2632dabf5612252300..63f9c63b21059ac4307104354eaeb83943248ea0 100644
--- a/src/ska/base/base_device.py
+++ b/src/ska/base/base_device.py
@@ -5,16 +5,16 @@
 #
 #
 
-"""A generic base device for SKA. It exposes the generic attributes,
-properties and commands of an SKA device.
+"""
+This module implements a generic base model and device for SKA. It
+exposes the generic attributes, properties and commands of an SKA
+device.
 """
 # PROTECTED REGION ID(SKABaseDevice.additionnal_import) ENABLED START #
 # Standard imports
 import enum
-import json
 import logging
 import logging.handlers
-import os
 import socket
 import sys
 import threading
@@ -24,26 +24,24 @@ from urllib.parse import urlparse
 from urllib.request import url2pathname
 
 # Tango imports
-import tango
-from tango import DebugIt
+from tango import AttrWriteType, DebugIt, DevState
 from tango.server import run, Device, attribute, command, device_property
-from tango import AttrQuality, AttrWriteType
-from tango import DeviceProxy, DevFailed
 
 # SKA specific imports
 import ska.logging as ska_logging
-from . import release
-from .control_model import (
-    AdminMode, ControlMode, HealthState, LoggingLevel, SimulationMode, TestMode
+from ska.base import release
+from ska.base.commands import (
+    ActionCommand, BaseCommand, ResultCode
+)
+from ska.base.control_model import (
+    AdminMode, ControlMode, SimulationMode, TestMode, HealthState,
+    LoggingLevel, DeviceStateModel
 )
 
-from .utils import (get_dp_command,
-                    coerce_value,
-                    get_groups_from_json,
-                    get_tango_device_type_id)
-from .faults import (GroupDefinitionsError,
-                     LoggingTargetError,
-                     LoggingLevelError)
+from ska.base.utils import get_groups_from_json
+from ska.base.faults import (GroupDefinitionsError,
+                             LoggingTargetError,
+                             LoggingLevelError)
 
 LOG_FILE_SIZE = 1024 * 1024  # Log file size 1MB.
 
@@ -309,25 +307,314 @@ class LoggingUtils:
 # PROTECTED REGION END #    //  SKABaseDevice.additionnal_import
 
 
-__all__ = ["SKABaseDevice", "main"]
+__all__ = ["SKABaseDevice", "SKABaseDeviceStateModel", "main"]
+
+
+class SKABaseDeviceStateModel(DeviceStateModel):
+    """
+    Implements the state model for the SKABaseDevice
+    """
+
+    __transitions = {
+        ('UNINITIALISED', 'init_started'): (
+            "INIT (ENABLED)",
+            lambda self: (
+                self._set_admin_mode(AdminMode.MAINTENANCE),
+                self._set_dev_state(DevState.INIT),
+            )
+        ),
+        ('INIT (ENABLED)', 'init_succeeded'): (
+            'OFF',
+            lambda self: self._set_dev_state(DevState.OFF)
+        ),
+        ('INIT (ENABLED)', 'init_failed'): (
+            'FAULT (ENABLED)',
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('INIT (ENABLED)', 'fatal_error'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('INIT (ENABLED)', 'to_notfitted'): (
+            "INIT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.NOT_FITTED)
+        ),
+        ('INIT (ENABLED)', 'to_offline'): (
+            "INIT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.OFFLINE)
+        ),
+        ('INIT (ENABLED)', 'to_maintenance'): (
+            "INIT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.MAINTENANCE)
+        ),
+        ('INIT (ENABLED)', 'to_online'): (
+            "INIT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.ONLINE)
+        ),
+        ('INIT (DISABLED)', 'init_succeeded'): (
+            'DISABLED',
+            lambda self: self._set_dev_state(DevState.DISABLE)
+        ),
+        ('INIT (DISABLED)', 'init_failed'): (
+            'FAULT (DISABLED)',
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('INIT (DISABLED)', 'fatal_error'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('INIT (DISABLED)', 'to_notfitted'): (
+            "INIT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.NOT_FITTED)
+        ),
+        ('INIT (DISABLED)', 'to_offline'): (
+            "INIT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.OFFLINE)
+        ),
+        ('INIT (DISABLED)', 'to_maintenance'): (
+            "INIT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.MAINTENANCE)
+        ),
+        ('INIT (DISABLED)', 'to_online'): (
+            "INIT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.ONLINE)
+        ),
+        ('FAULT (DISABLED)', 'reset_succeeded'): (
+            "DISABLED",
+            lambda self: self._set_dev_state(DevState.DISABLE)
+        ),
+        ('FAULT (DISABLED)', 'reset_failed'): ("FAULT (DISABLED)", None),
+        ('FAULT (DISABLED)', 'fatal_error'): ("FAULT (DISABLED)", None),
+        ('FAULT (DISABLED)', 'to_notfitted'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.NOT_FITTED)
+        ),
+        ('FAULT (DISABLED)', 'to_offline'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.OFFLINE)
+        ),
+        ('FAULT (DISABLED)', 'to_maintenance'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.MAINTENANCE)
+        ),
+        ('FAULT (DISABLED)', 'to_online'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.ONLINE)
+        ),
+        ('FAULT (ENABLED)', 'reset_succeeded'): (
+            "OFF",
+            lambda self: self._set_dev_state(DevState.OFF)
+        ),
+        ('FAULT (ENABLED)', 'reset_failed'): ("FAULT (ENABLED)", None),
+        ('FAULT (ENABLED)', 'fatal_error'): ("FAULT (ENABLED)", None),
+        ('FAULT (ENABLED)', 'to_notfitted'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.NOT_FITTED)),
+        ('FAULT (ENABLED)', 'to_offline'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_admin_mode(AdminMode.OFFLINE)),
+        ('FAULT (ENABLED)', 'to_maintenance'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.MAINTENANCE)
+        ),
+        ('FAULT (ENABLED)', 'to_online'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_admin_mode(AdminMode.ONLINE)
+        ),
+        ('DISABLED', 'to_offline'): (
+            "DISABLED",
+            lambda self: self._set_admin_mode(AdminMode.OFFLINE)
+        ),
+        ('DISABLED', 'to_online'): (
+            "OFF",
+            lambda self: (
+                self._set_admin_mode(AdminMode.ONLINE),
+                self._set_dev_state(DevState.OFF)
+            )
+        ),
+        ('DISABLED', 'to_maintenance'): (
+            "OFF",
+            lambda self: (
+                self._set_admin_mode(AdminMode.MAINTENANCE),
+                self._set_dev_state(DevState.OFF)
+            )
+        ),
+        ('DISABLED', 'to_notfitted'): (
+            "DISABLED",
+            lambda self: self._set_admin_mode(AdminMode.NOT_FITTED)
+        ),
+        ('DISABLED', 'fatal_error'): (
+            "FAULT (DISABLED)",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('OFF', 'to_notfitted'): (
+            "DISABLED",
+            lambda self: (
+                self._set_admin_mode(AdminMode.NOT_FITTED),
+                self._set_dev_state(DevState.DISABLE)
+            )
+        ),
+        ('OFF', 'to_offline'): (
+            "DISABLED", lambda self: (
+                self._set_admin_mode(AdminMode.OFFLINE),
+                self._set_dev_state(DevState.DISABLE)
+            )
+        ),
+        ('OFF', 'to_online'): (
+            "OFF",
+            lambda self: self._set_admin_mode(AdminMode.ONLINE)
+        ),
+        ('OFF', 'to_maintenance'): (
+            "OFF",
+            lambda self: self._set_admin_mode(AdminMode.MAINTENANCE)
+        ),
+        ('OFF', 'fatal_error'): (
+            "FAULT (ENABLED)",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+    }
+
+    def __init__(self, dev_state_callback=None):
+        """
+        Initialises the state model.
+
+        :param dev_state_callback: A callback to be called when a
+            transition implies a change to device state
+        :type dev_state_callback: tango.DevState
+        """
+        super().__init__(self.__transitions, "UNINITIALISED")
+
+        self._admin_mode = None
+        self._dev_state = None
+        self._dev_state_callback = dev_state_callback
+
+    @property
+    def admin_mode(self):
+        """
+        Returns the admin_mode
+
+        :returns: admin_mode of this state model
+        :rtype: AdminMode
+        """
+        return self._admin_mode
+
+    def _set_admin_mode(self, admin_mode):
+        """
+        Helper method: calls the admin_mode callback if one exists
+
+        :param admin_mode: the new admin_mode value
+        :type admin_mode: AdminMode
+        """
+        self._admin_mode = admin_mode
+
+    @property
+    def dev_state(self):
+        """
+        Returns the dev_state
+
+        :returns: dev_state of this state model
+        :rtype: tango.DevState
+        """
+        return self._dev_state
+
+    def _set_dev_state(self, dev_state):
+        """
+        Helper method: sets this state models dev_state, and calls the
+        dev_state callback if one exists
+
+        :param dev_state: the new state value
+        :type admin_mode: DevState
+        """
+        if self._dev_state != dev_state:
+            self._dev_state = dev_state
+            if self._dev_state_callback is not None:
+                self._dev_state_callback(self._dev_state)
 
 
 class SKABaseDevice(Device):
     """
     A generic base device for SKA.
     """
-    # PROTECTED REGION ID(SKABaseDevice.class_variable) ENABLED START #
+    class InitCommand(ActionCommand):
+        """
+        A class for the SKABaseDevice's init_device() "command".
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Create a new InitCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "init", start_action=True, logger=logger
+            )
+
+        def do(self):
+            """
+            Stateless hook for device initialisation.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+
+            device._health_state = HealthState.OK
+            device._control_mode = ControlMode.REMOTE
+            device._simulation_mode = SimulationMode.FALSE
+            device._test_mode = TestMode.NONE
+
+            device._build_state = '{}, {}, {}'.format(release.name,
+                                                      release.version,
+                                                      release.description)
+            device._version_id = release.version
+
+            try:
+                # create TANGO Groups dict, according to property
+                self.logger.debug(
+                    "Groups definitions: {}".format(
+                        device.GroupDefinitions
+                    )
+                )
+                device.groups = get_groups_from_json(
+                    device.GroupDefinitions
+                )
+                self.logger.info(
+                    "Groups loaded: {}".format(
+                        sorted(device.groups.keys())
+                    )
+                )
+            except GroupDefinitionsError:
+                self.logger.debug(
+                    "No Groups loaded for device: {}".format(
+                        device.get_name()
+                    )
+                )
+
+            message = "SKABaseDevice Init command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
 
     _logging_config_lock = threading.Lock()
     _logging_configured = False
 
     def _init_logging(self):
         """
-        This method initializes the logging mechanism, based on default properties.
-
-        :param: None.
-
-        :return: None.
+        This method initializes the logging mechanism, based on default
+        properties.
         """
 
         class EnsureTagsFilter(logging.Filter):
@@ -481,36 +768,73 @@ class SKABaseDevice(Device):
     # General methods
     # ---------------
 
+    def _update_state(self, state):
+        """
+        Helper method for changing state; passed to the state model as a
+        callback
+
+        :param state: the new state value
+        :type state: DevState
+        """
+        if state != self.get_state():
+            self.logger.info(
+                f"Device state changed from {self.get_state()} to {state}"
+            )
+            self.set_state(state)
+            self.set_status(f"The device is in {state} state.")
+
     def init_device(self):
         """
-        Method that initializes the tango device after startup.
+        Initializes the tango device after startup.
+
+        Subclasses that have no need to override the default
+        default implementation of state management may leave
+        ``init_device()`` alone.  Override the ``do()`` method
+        on the nested class ``InitCommand`` instead.
+
         :return: None
         """
-        Device.init_device(self)
-        # PROTECTED REGION ID(SKABaseDevice.init_device) ENABLED START #
+        try:
+            super().init_device()
 
-        self._init_logging()
+            self._init_logging()
+            self._init_state_model()
 
-        # Initialize attribute values.
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        self._health_state = HealthState.OK
-        self._admin_mode = AdminMode.ONLINE
-        self._control_mode = ControlMode.REMOTE
-        self._simulation_mode = SimulationMode.FALSE
-        self._test_mode = TestMode.NONE
+            self._command_objects = {}
 
-        try:
-            # create TANGO Groups objects dict, according to property
-            self.logger.debug("Groups definitions: {}".format(self.GroupDefinitions))
-            self.groups = get_groups_from_json(self.GroupDefinitions)
-            self.logger.info("Groups loaded: {}".format(sorted(self.groups.keys())))
-        except GroupDefinitionsError:
-            self.logger.info("No Groups loaded for device: {}".format(self.get_name()))
+            self.InitCommand(self, self.state_model, self.logger)()
+
+            self.init_command_objects()
+        except Exception as exc:
+            self.set_state(DevState.FAULT)
+            self.set_status("The device is in FAULT state - init_device failed.")
+            if hasattr(self, "logger"):
+                self.logger.exception("init_device() failed.")
+            else:
+                print(f"ERROR: init_device failed, and no logger: {exc}.")
 
-        self.logger.info("Completed SKABaseDevice.init_device")
-        # PROTECTED REGION END #    //  SKABaseDevice.init_device
+    def _init_state_model(self):
+        """
+        Creates the state model for the device
+        """
+        self.state_model = SKABaseDeviceStateModel(
+            dev_state_callback=self._update_state
+        )
+
+    def register_command_object(self, command_name, command_object):
+        self._command_objects[command_name] = command_object
+
+    def get_command_object(self, command_name):
+        return self._command_objects[command_name]
+
+    def init_command_objects(self):
+        self.register_command_object(
+            "Reset", self.ResetCommand(self, self.state_model, self.logger)
+        )
+        self.register_command_object(
+            "GetVersionInfo",
+            self.GetVersionInfoCommand(self, self.state_model, self.logger)
+        )
 
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKABaseDevice.always_executed_hook) ENABLED START #
@@ -583,8 +907,11 @@ class SKABaseDevice(Device):
 
         self._logging_level = lmc_logging_level
         self.logger.setLevel(_LMC_TO_PYTHON_LOGGING_LEVEL[lmc_logging_level])
-        self.logger.tango_logger.set_level(_LMC_TO_TANGO_LOGGING_LEVEL[lmc_logging_level])
-        self.logger.info('Logging level set to %s on Python and Tango loggers', lmc_logging_level)
+        self.logger.tango_logger.set_level(
+            _LMC_TO_TANGO_LOGGING_LEVEL[lmc_logging_level]
+        )
+        self.logger.info('Logging level set to %s on Python and Tango loggers',
+                         lmc_logging_level)
         # PROTECTED REGION END #    //  SKABaseDevice.loggingLevel_write
 
     def read_loggingTargets(self):
@@ -613,7 +940,8 @@ class SKABaseDevice(Device):
         :return: None.
         """
         device_name = self.get_name()
-        valid_targets = LoggingUtils.sanitise_logging_targets(value, device_name)
+        valid_targets = LoggingUtils.sanitise_logging_targets(value,
+                                                              device_name)
         LoggingUtils.update_logging_handlers(valid_targets, self.logger)
         # PROTECTED REGION END #    //  SKABaseDevice.loggingTargets_write
 
@@ -633,8 +961,9 @@ class SKABaseDevice(Device):
         Reads Admin Mode of the device.
 
         :return: Admin Mode of the device
+        :rtype: AdminMode
         """
-        return self._admin_mode
+        return self.state_model.admin_mode
         # PROTECTED REGION END #    //  SKABaseDevice.adminMode_read
 
     def write_adminMode(self, value):
@@ -643,10 +972,20 @@ class SKABaseDevice(Device):
         Sets Admin Mode of the device.
 
         :param value: Admin Mode of the device.
+        :type value: AdminMode
 
         :return: None
         """
-        self._admin_mode = value
+        if value == AdminMode.NOT_FITTED:
+            self.state_model.perform_action("to_notfitted")
+        elif value == AdminMode.OFFLINE:
+            self.state_model.perform_action("to_offline")
+        elif value == AdminMode.MAINTENANCE:
+            self.state_model.perform_action("to_maintenance")
+        elif value == AdminMode.ONLINE:
+            self.state_model.perform_action("to_online")
+        else:
+            raise ValueError(f"Unknown adminMode {value}")
         # PROTECTED REGION END #    //  SKABaseDevice.adminMode_write
 
     def read_controlMode(self):
@@ -719,6 +1058,22 @@ class SKABaseDevice(Device):
     # Commands
     # --------
 
+    class GetVersionInfoCommand(BaseCommand):
+        """
+        A class for the SKABaseDevice's Reset() command.
+        """
+        def do(self):
+            """
+            Stateless hook for device GetVersionInfo() command.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+            return [f"{device.__class__.__name__}, {device.read_buildState()}"]
+
     @command(dtype_out=('str',), doc_out="Version strings",)
     @DebugIt()
     def GetVersionInfo(self):
@@ -726,22 +1081,88 @@ class SKABaseDevice(Device):
         """
         Returns the version information of the device.
 
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :return: Version details of the device.
         """
-        return ['{}, {}'.format(self.__class__.__name__, self.read_buildState())]
+        command = self.get_command_object("GetVersionInfo")
+        return command()
         # PROTECTED REGION END #    //  SKABaseDevice.GetVersionInfo
 
+    class ResetCommand(ActionCommand):
+        """
+        A class for the SKABaseDevice's Reset() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Create a new ResetCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "reset", logger=logger)
+
+        def do(self):
+            """
+            Stateless hook for device reset.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+            device._health_state = HealthState.OK
+            device._control_mode = ControlMode.REMOTE
+            device._simulation_mode = SimulationMode.FALSE
+            device._test_mode = TestMode.NONE
+
+            message = "Reset command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    def is_Reset_allowed(self):
+        """
+        Whether the ``Reset()`` command is allowed to be run in the
+        current state
+
+        :returns: whether the ``Reset()`` command is allowed to be run in the
+            current state
+        :rtype: boolean
+        """
+        command = self.get_command_object("Reset")
+        return command.is_allowed()
+
     @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
     def Reset(self):
-        # PROTECTED REGION ID(SKABaseDevice.Reset) ENABLED START #
         """
-        Reset device to its default state.
+        Reset the device from the FAULT state.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
 
         :return: None
         """
-        # PROTECTED REGION END #    //  SKABaseDevice.Reset
+        command = self.get_command_object("Reset")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
 
 # ----------
 # Run server
@@ -754,10 +1175,7 @@ def main(args=None, **kwargs):
     Main function of the SKABaseDevice module.
 
     :param args: None
-
     :param kwargs:
-
-    :return:
     """
     return run((SKABaseDevice,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKABaseDevice.main
diff --git a/src/ska/base/capability_device.py b/src/ska/base/capability_device.py
index 930905aaa781bf1ebd0b9a73fa67d8198a04e2fe..dedbb54f58ed57c29ca3a5ca5297741edf79f28a 100644
--- a/src/ska/base/capability_device.py
+++ b/src/ska/base/capability_device.py
@@ -9,16 +9,13 @@
 Capability handling device
 """
 # PROTECTED REGION ID(SKACapability.additionnal_import) ENABLED START #
-# Standard import
-import os
-import sys
-
 # Tango imports
 from tango import DebugIt
 from tango.server import run, attribute, command, device_property
 
 # SKA specific imports
-from . import SKAObsDevice, release
+from ska.base import SKAObsDevice
+from ska.base.commands import ResponseCommand, ResultCode
 # PROTECTED REGION END #    //  SKACapability.additionnal_imports
 
 __all__ = ["SKACapability", "main"]
@@ -28,6 +25,38 @@ class SKACapability(SKAObsDevice):
     """
     A Subarray handling device. It exposes the instances of configured capabilities.
     """
+    def init_command_objects(self):
+        """
+        Sets up the command objects
+        """
+        super().init_command_objects()
+        self.register_command_object(
+            "ConfigureInstances", self.ConfigureInstancesCommand(
+                self, self.state_model, self.logger
+            )
+        )
+
+    class InitCommand(SKAObsDevice.InitCommand):
+        def do(self):
+            """
+            Stateless hook for device initialisation.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            super().do()
+
+            device = self.target
+            device._activation_time = 0.0
+            device._configured_instances = 0
+            device._used_components = [""]
+
+            message = "SKACapability Init command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
     # PROTECTED REGION ID(SKACapability.class_variable) ENABLED START #
     # PROTECTED REGION END #    //  SKACapability.class_variable
 
@@ -74,17 +103,6 @@ class SKACapability(SKAObsDevice):
     # General methods
     # ---------------
 
-    def init_device(self):
-        SKAObsDevice.init_device(self)
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # PROTECTED REGION ID(SKACapability.init_device) ENABLED START #
-        self._activation_time = 0.0
-        self._configured_instances = 0
-        self._used_components = [""]
-        # PROTECTED REGION END #    //  SKACapability.init_device
-
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKACapability.always_executed_hook) ENABLED START #
         pass
@@ -126,34 +144,66 @@ class SKACapability(SKAObsDevice):
         return self._used_components
         # PROTECTED REGION END #    //  SKACapability.usedComponents_read
 
-
     # --------
     # Commands
     # --------
 
-    @command(dtype_in='uint16', doc_in="The number of instances to configure for this Capability.",)
+    class ConfigureInstancesCommand(ResponseCommand):
+        """
+        A class for the SKALoggerDevice's SetLoggingLevel() command.
+        """
+        def do(self, argin):
+            """
+            Stateless hook for ConfigureInstances()) command
+            functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+            device._configured_instances = argin
+
+            message = "ConfigureInstances command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    @command(
+        dtype_in='uint16',
+        doc_in="The number of instances to configure for this Capability.",
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
     def ConfigureInstances(self, argin):
         # PROTECTED REGION ID(SKACapability.ConfigureInstances) ENABLED START #
         """
         This function indicates how many number of instances of the current capacity
         should to be configured.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
         :param argin: Number of instances to configure
         :return: None.
         """
-        self._configured_instances = argin
+        command = self.get_command_object("ConfigureInstances")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
         # PROTECTED REGION END #    //  SKACapability.ConfigureInstances
 
+
 # ----------
 # Run server
 # ----------
 
-
 def main(args=None, **kwargs):
     # PROTECTED REGION ID(SKACapability.main) ENABLED START #
     """Main function of the SKACapability module."""
     return run((SKACapability,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKACapability.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/commands.py b/src/ska/base/commands.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d6fc20d3cc00bfb3bf5d4c85fe864708574d773
--- /dev/null
+++ b/src/ska/base/commands.py
@@ -0,0 +1,336 @@
+"""
+This module provides abstract base classes for device commands, and a
+ResultCode enum.
+"""
+import enum
+import logging
+from ska.base.faults import CommandError, ResultCodeError, StateModelError
+
+module_logger = logging.getLogger(__name__)
+
+
+class ResultCode(enum.IntEnum):
+    """
+    Python enumerated type for command return codes.
+    """
+
+    OK = 0
+    """
+    The command was executed successfully.
+    """
+
+    STARTED = 1
+    """
+    The command has been accepted and will start immediately.
+    """
+
+    QUEUED = 2
+    """
+    The command has been accepted and will be executed at a future time
+    """
+
+    FAILED = 3
+    """
+    The command could not be executed.
+    """
+
+    UNKNOWN = 4
+    """
+    The status of the command is not known.
+    """
+
+
+class BaseCommand:
+    """
+    Abstract base class for Tango device server commands. Ensures the
+    command is run, and that if the command errors, the "fatal_error"
+    action will be called on the state model.
+    """
+
+    def __init__(self, target, state_model, logger=None):
+        """
+        Creates a new BaseCommand object for a device.
+
+        :param state_model: the state model that this command uses, for
+             example to raise a fatal error if the command errors out.
+        :type state_model: SKABaseClassStateModel or a subclass of same
+        :param target: the object that this base command acts upon. For
+            example, the device that this BaseCommand implements the
+            command for.
+        :type target: object
+        :param logger: the logger to be used by this Command. If not
+            provided, then a default module logger will be used.
+        :type logger: a logger that implements the standard library
+            logger interface
+        """
+        self.name = self.__class__.__name__
+        self.target = target
+        self.state_model = state_model
+        self.logger = logger or module_logger
+
+    def __call__(self, argin=None):
+        """
+        What to do when the command is called. This base class simply
+        calls ``do()`` or ``do(argin)``, depending on whether the
+        ``argin`` argument is provided.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        try:
+            return self._call_do(argin)
+        except Exception:
+            self.logger.exception(
+                f"Error executing command {self.name} with argin '{argin}'"
+            )
+            self.fatal_error()
+            raise
+
+    def _call_do(self, argin=None):
+        """
+        Helper method that ensures the ``do`` method is called with the
+        right arguments, and that the call is logged.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        if argin is None:
+            returned = self.do()
+        else:
+            returned = self.do(argin=argin)
+
+        self.logger.info(
+            f"Exiting command {self.name}"
+        )
+        return returned
+
+    def do(self, argin=None):
+        """
+        Hook for the functionality that the command implements. This
+        class provides stub functionality; subclasses should subclass
+        this method with their command functionality.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        raise NotImplementedError(
+            "BaseCommand is abstract; do() must be subclassed not called."
+        )
+
+    def fatal_error(self):
+        """
+        Callback for a fatal error in the command, such as an unhandled
+        exception.
+        """
+        self._perform_action("fatal_error")
+
+    def _is_action_allowed(self, action):
+        """
+        Helper method; whether a given action is permitted in the
+        current state of the state model.
+
+        :param action: the action on the state model that is being
+            scrutinised
+        :type action: string
+        :returns: whether the action is allowed
+        :rtype: boolean
+        """
+        return self.state_model.is_action_allowed(action)
+
+    def _try_action(self, action):
+        """
+        Helper method; "tries" an action on the state model.
+
+        :param action: the action to perform on the state model
+        :type action: string
+        :raises: CommandError if the action is not allowed in current state
+        :returns: True is the action is allowed
+        """
+        try:
+            return self.state_model.try_action(action)
+        except StateModelError as exc:
+            raise CommandError(
+                f"Error executing command {self.name}") from exc
+
+    def _perform_action(self, action):
+        """
+        Helper method; performs an action on the state model, thus
+        driving state
+
+        :param action: the action to perform on the state model
+        :type action: string
+        """
+        self.state_model.perform_action(action)
+
+
+class ResponseCommand(BaseCommand):
+    def __call__(self, argin=None):
+        """
+        What to do when the command is called. This base class simply
+        calls ``do()`` or ``do(argin)``, depending on whether the
+        ``argin`` argument is provided.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        try:
+            (return_code, message) = self._call_do(argin)
+        except Exception:
+            self.logger.exception(
+                f"Error executing command {self.name} with argin '{argin}'"
+            )
+            self.fatal_error()
+            raise
+        return (return_code, message)
+
+    def _call_do(self, argin=None):
+        """
+        Helper method that ensures the ``do`` method is called with the
+        right arguments, and that the call is logged.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        if argin is None:
+            (return_code, message) = self.do()
+        else:
+            (return_code, message) = self.do(argin=argin)
+
+        self.logger.info(
+            f"Exiting command {self.name} with return_code {return_code!s}, "
+            f"message: '{message}'"
+        )
+        return (return_code, message)
+
+
+class ActionCommand(ResponseCommand):
+    """
+    Abstract base class for a tango command, which checks a state model
+    to find out whether the command is allowed to be run, and after
+    running, sends an action to that state model, thus driving device
+    state.
+    """
+    def __init__(
+        self, target, state_model, action_hook, start_action=False, logger=None
+    ):
+        """
+        Create a new ActionCommand for a device.
+
+        :param target: the object that this base command acts upon. For
+            example, the device that this ActionCommand implements the
+            command for.
+        :type target: object
+        :param action_hook: a hook for the command, used to build
+            actions that will be sent to the state model; for example,
+            if the hook is "scan", then success of the command will
+            result in action "scan_succeeded" being sent to the state
+            model.
+        :type action_hook: string
+        :param start_action: whether the state model supports a start
+            action (i.e. to put the state model into an transient state
+            while the command is running); default False
+        :type start_action: boolean
+        :param logger: the logger to be used by this Command. If not
+            provided, then a default module logger will be used.
+        :type logger: a logger that implements the standard library
+            logger interface
+        """
+        super().__init__(target, state_model, logger=logger)
+        self._succeeded_hook = f"{action_hook}_succeeded"
+        self._failed_hook = f"{action_hook}_failed"
+
+        self._started_hook = None
+        if start_action:
+            self._started_hook = f"{action_hook}_started"
+
+    def __call__(self, argin=None):
+        """
+        What to do when the command is called. This is implemented to
+        check that the command is allowed to run, then run the command,
+        then send an action to the state model advising whether the
+        command succeeded or failed.
+
+        :param argin: the argument passed to the Tango command, if
+            present
+        :type argin: ANY
+        """
+        self.check_allowed()
+        try:
+            self.started()
+            (return_code, message) = self._call_do(argin)
+            self._returned(return_code)
+        except Exception:
+            self.logger.exception(
+                f"Error executing command {self.name} with argin '{argin}'"
+            )
+            self.fatal_error()
+            raise
+        return (return_code, message)
+
+    def _returned(self, return_code):
+        """
+        Helper method that handles the return of the ``do()`` method.
+        If the return code is OK or FAILED, then it performs an
+        appropriate action on the state model. Otherwise it raises an
+        error.
+
+        :param return_code: The return_code returned by the ``do()``
+            method
+        :type return_code: ResultCode
+        """
+        if return_code == ResultCode.OK:
+            self.succeeded()
+        elif return_code == ResultCode.FAILED:
+            self.failed()
+        else:
+            if self._started_hook is None:
+                raise ResultCodeError(
+                    f"ActionCommands that do not have a started action may"
+                    f"only return with code OK or FAILED, not {return_code!s}."
+                )
+
+    def check_allowed(self):
+        """
+        Checks whether the command is allowed to be run in the current
+        state of the state model.
+
+        :returns: True if the command is allowed to be run
+        :raises StateModelError: if the command is not allowed to be run
+        """
+        return self._try_action(self._started_hook or self._succeeded_hook)
+
+    def is_allowed(self):
+        """
+        Whether this command is allowed to run in the current state of
+        the state model.
+
+        :returns: whether this command is allowed to run
+        :rtype: boolean
+        """
+        return self._is_action_allowed(
+            self._started_hook or self._succeeded_hook
+        )
+
+    def started(self):
+        """
+        Action to perform upon starting the comand.
+        """
+        if self._started_hook is not None:
+            self._perform_action(self._started_hook)
+
+    def succeeded(self):
+        """
+        Callback for the successful completion of the command.
+        """
+        self._perform_action(self._succeeded_hook)
+
+    def failed(self):
+        """
+        Callback for the failed completion of the command.
+        """
+        self._perform_action(self._failed_hook)
diff --git a/src/ska/base/control_model.py b/src/ska/base/control_model.py
index 36482485f8acb0dde6d322879a8e1324b7c60b42..52328e8ade3cb7d5c2ca2a476fd5bfd8e6e66ca0 100644
--- a/src/ska/base/control_model.py
+++ b/src/ska/base/control_model.py
@@ -4,6 +4,9 @@ Module for SKA Control Model (SCM) related code.
 
 For further details see the SKA1 CONTROL SYSTEM GUIDELINES (CS_GUIDELINES MAIN VOLUME)
 Document number:  000-000000-010 GDL
+And architectural updates:
+https://jira.skatelescope.org/browse/ADR-8
+https://confluence.skatelescope.org/pages/viewpage.action?pageId=105416556
 
 The enumerated types mapping to the states and modes are included here, as well as
 other useful enumerations.
@@ -11,6 +14,7 @@ other useful enumerations.
 """
 
 import enum
+from ska.base.faults import StateModelError
 
 # ---------------------------------
 # Core SKA Control Model attributes
@@ -40,9 +44,9 @@ class HealthState(enum.IntEnum):
     that belongs to a subarray is unresponsive, or may report healthState as ``FAILED``.
 
     Difference between ``DEGRADED`` and ``FAILED`` health shall be clearly identified
-    (quantified) and documented. For example, the difference between ``DEGRADED`` and ``FAILED`` 
-    subarray can be defined as the number or percent of the dishes available, the number or 
-    percent of the baselines available,   sensitivity, or some other criterion. More than one 
+    (quantified) and documented. For example, the difference between ``DEGRADED`` and ``FAILED``
+    subarray can be defined as the number or percent of the dishes available, the number or
+    percent of the baselines available,   sensitivity, or some other criterion. More than one
     criteria may be defined for a TANGO Device.
     """
 
@@ -71,8 +75,8 @@ class AdminMode(enum.IntEnum):
     """
 
     OFFLINE = 1
-    """SKA operations declared that the entity is not used for observing or other function it 
-    provides. A subset of the monitor and control functionality may be supported in this mode. 
+    """SKA operations declared that the entity is not used for observing or other function it
+    provides. A subset of the monitor and control functionality may be supported in this mode.
     ``adminMode=OFFLINE`` is also used to indicate unused Subarrays and unused Capabilities.
     TANGO devices report ``state=DISABLED`` when ``adminMode=OFFLINE``.
     """
@@ -80,13 +84,13 @@ class AdminMode(enum.IntEnum):
     MAINTENANCE = 2
     """
     SKA operations declared that the entity is reserved for maintenance and cannot
-    be part of scientific observations, but can be used for observing in a ‘Maintenance Subarray’. 
-    
+    be part of scientific observations, but can be used for observing in a ‘Maintenance Subarray’.
+
     ``MAINTENANCE`` mode has different meaning for different entities, depending on the context
     and functionality. Some entities may implement different behaviour when in ``MAINTENANCE``
     mode.
-    
-    For each TANGO Device, the difference in behaviour and functionality in ``MAINTENANCE`` mode 
+
+    For each TANGO Device, the difference in behaviour and functionality in ``MAINTENANCE`` mode
     shall be documented. ``MAINTENANCE`` is the factory default for ``adminMode``. Transition
     out of ``adminMode=NOT_FITTED`` is always via ``MAINTENANCE``; an engineer/operator has to
     verify that the  entity is operational as expected before it is set to ``ONLINE``
@@ -95,28 +99,28 @@ class AdminMode(enum.IntEnum):
 
     NOT_FITTED = 3
     """
-    SKA operations declared the entity as ``NOT_FITTED`` (and therefore cannot be used for 
-    observing or other function it provides). TM shall not send commands or queries to the 
+    SKA operations declared the entity as ``NOT_FITTED`` (and therefore cannot be used for
+    observing or other function it provides). TM shall not send commands or queries to the
     Element (entity) while in this mode.
-    
-    TANGO devices shall report ``state=DISABLE`` when ``adminMode=NOT_FITTED``; higher level 
-    entities (Element, Sub-element, component, Subarray and/or Capability) which ‘use’ 
-    ``NOT_FITTED`` equipment shall report operational ``state`` as ``DISABLE``.  If only a subset 
-    of higher-level functionality is affected, overall ``state`` of the higher-level entity that 
-    uses ``NOT_FITTED`` equipment may be reported as ``ON``, but with ``healthState=DEGRADED``. 
-    Additional queries may be necessary to identify which functionality and capabilities are 
-    available. 
-    
-    Higher-level entities shall intelligently exclude ``NOT_FITTED`` items from ``healthState`` and 
-    Element Alerts/Telescope Alarms; e.g. if a receiver band in DSH is ``NOT_FITTED`` and there 
-    is no communication to that receiver band, then DSH shall not raise Element Alerts for that 
-    entity and it should not report ``healthState=FAILED`` because of an entity that is 
+
+    TANGO devices shall report ``state=DISABLE`` when ``adminMode=NOT_FITTED``; higher level
+    entities (Element, Sub-element, component, Subarray and/or Capability) which ‘use’
+    ``NOT_FITTED`` equipment shall report operational ``state`` as ``DISABLE``.  If only a subset
+    of higher-level functionality is affected, overall ``state`` of the higher-level entity that
+    uses ``NOT_FITTED`` equipment may be reported as ``ON``, but with ``healthState=DEGRADED``.
+    Additional queries may be necessary to identify which functionality and capabilities are
+    available.
+
+    Higher-level entities shall intelligently exclude ``NOT_FITTED`` items from ``healthState`` and
+    Element Alerts/Telescope Alarms; e.g. if a receiver band in DSH is ``NOT_FITTED`` and there
+    is no communication to that receiver band, then DSH shall not raise Element Alerts for that
+    entity and it should not report ``healthState=FAILED`` because of an entity that is
     ``NOT_FITTED``.
     """
 
     RESERVED = 4
-    """This mode is used to identify additional equipment that is ready to take over when the 
-    operational equipment fails. This equipment does not take part in the operations at this 
+    """This mode is used to identify additional equipment that is ready to take over when the
+    operational equipment fails. This equipment does not take part in the operations at this
     point in time. TANGO devices report ``state=DISABLED`` when ``adminMode=RESERVED``.
     """
 
@@ -124,64 +128,79 @@ class AdminMode(enum.IntEnum):
 class ObsState(enum.IntEnum):
     """Python enumerated type for ``obsState`` attribute - the observing state."""
 
-    IDLE = 0
+    EMPTY = 0
+    """
+    The sub-array is ready to observe, but is in an undefined
+    configuration and has no resources allocated.
     """
-    Subarray, resource, Capability is not used for observing, it does not produce output
-    products.  The exact implementation is [TBD4] for each Element/Sub-element that
-    implements subarrays.
+
+    RESOURCING = 1
+    """
+    The system is allocating resources to, or deallocating resources
+    from, the subarray. This may be a complete de/allocation, or it may
+    be incremental. In both cases it is a transient state and will
+    automatically transition to IDLE when complete. For some subsystems
+    this may be a very brief state if resourcing is a quick activity.
     """
 
-    CONFIGURING = 1
+    IDLE = 2
     """
-    Subarray is being prepared for a specific scan. On entry to the state no assumptions
-    can be made about the previous conditions. This is a transient state. Subarray/Capability
-    are supposed to automatically transitions to ``obsState=READY`` when configuration is
-    successfully completed. If an error is encountered, TANGO Device may:
-    
-    * report failure and abort the configuration, waiting for additional input;
-    * proceed with reconfiguration, transition to ``obsState=READY`` and set
-      ``healthState=DEGRADED`` (if possible notify the originator of the request that
-      configuration is not 100% successful);
-    * if serious failure is encountered, transition to ``obsState=FAULT``,
-      ``healthState=FAILED``.
+    The subarray has resources allocated and is ready to be used for
+    observing. In normal science operations these will be the resources
+    required for the upcoming SBI execution.
     """
 
-    READY = 2
+    CONFIGURING = 3
     """
-    Subarray is fully prepared for the next scan, but not actually taking data or moving
-    in the observed coordinate system (i.e. it may be tracking, but not moving relative
-    to the coordinate system). 
+    The subarray is being configured ready to scan. On entry to the
+    state no assumptions can be made about the previous conditions. It
+    is a transient state and will automatically transition to READY when
+    it completes normally.
     """
 
-    SCANNING = 3
+    READY = 4
     """
-    Subarray is taking data and, if needed, all components are synchronously moving in the
-    observed coordinate system. All the M&C flows to the sub-systems are happening
-    automatically (e.g. DISHes are receiving pointing updates, CSP is receiving updates for
-    delay tracking).
+    The subarray is fully prepared to scan, but is not actually taking
+    data or moving in the observed coordinate system (it may be
+    tracking, but not moving relative to the coordinate system).
     """
 
-    PAUSED = 4
+    SCANNING = 5
     """
-    [TBC11 by SKAO SW Architects] Subarray is fully prepared for the next observation, but
-    not actually taking data or moving in the observed system. Similar to ``READY`` state.
-    If required, then functionality required by DISHes, LFAA, CSP is [TBD5]
-    (do they keep signal processing and stop transmitting output data? What happens to
-    observations that are time/position sensitive and cannot resume after a pause?) 
+    The subarray is taking data and, if needed, all components are
+    synchronously moving in the observed coordinate system. Any changes
+    to the sub-systems are happening automatically (this allows for a
+    scan to cover the case where the phase centre is moved in a
+    pre-defined pattern).
     """
 
-    ABORTED = 5
+    ABORTING = 6
     """
-    The subarray has had its previous state interrupted by the controller. Exit from
-    the ``ABORTED`` state requires the ``Reset`` command.
+    The subarray is trying to abort what it was doing due to having been
+    interrupted by the controller.
     """
 
-    FAULT = 6
+    ABORTED = 7
     """
-    Subarray has detected an internal error making it impossible to remain in the previous
-    state.
- 
-    **Note:** This shall trigger a ``healthState`` update of the Subarray/Capability.
+    The subarray has had its previous state interrupted by the
+    controller, and is now in an aborted state.
+    """
+
+    RESETTING = 8
+    """
+    The subarray device is resetting to the IDLE state.
+    """
+
+    FAULT = 9
+    """
+    The subarray has detected an error in its observing state making it
+    impossible to remain in the previous state.
+    """
+
+    RESTARTING = 10
+    """
+    The subarray device is restarting, as the last known stable state is
+    where no resources were allocated and the configuration undefined.
     """
 
 
@@ -246,13 +265,13 @@ class ControlMode(enum.IntEnum):
 
     LOCAL = 1
     """
-    TANGO Device accepts only from a ‘local’ client and ignores commands and queries received 
-    from TM or any other ‘remote’ clients. This is typically activated by a switch, 
+    TANGO Device accepts only from a ‘local’ client and ignores commands and queries received
+    from TM or any other ‘remote’ clients. This is typically activated by a switch,
     or a connection on the local control interface. The intention is to support early
     integration of DISHes and stations. The equipment has to be put back in ``REMOTE``
     before clients can take  control again. ``controlMode`` may be removed from the
     SCM if unused/not needed.
-    
+
     **Note:** Setting `controlMode` to `LOCAL` **is not a safety feature**, but rather a
     usability feature.  Safety has to be implemented separately to the control paths.
     """
@@ -289,8 +308,8 @@ class TestMode(enum.IntEnum):
 
     TEST = 1
     """
-    Element (entity) behaviour and/or set of commands differ for the normal operating mode. To 
-    be implemented only by devices that implement one or more test modes. The Element 
+    Element (entity) behaviour and/or set of commands differ for the normal operating mode. To
+    be implemented only by devices that implement one or more test modes. The Element
     documentation shall provide detailed description.
     """
 
@@ -309,3 +328,93 @@ class LoggingLevel(enum.IntEnum):
     WARNING = 3
     INFO = 4
     DEBUG = 5
+
+
+class DeviceStateModel:
+    """
+    Base class for the state model used by SKA devices.
+    """
+
+    def __init__(self, transitions, initial_state):
+        """
+        Create a new device state model.
+
+        :param transitions: a dictionary for which each key is a (state,
+            event) tuple, and each value is a (state, side-effect)
+            tuple. When the device is in state `IN-STATE`, and action
+            `ACTION` is attempted, the transitions table will be checked
+            for an entry under key `(IN-STATE, EVENT)`. If no such key
+            exists, the action will be denied and a model will raise a
+            `StateModelError`. If the key does exist, then its value
+            `(OUT-STATE, SIDE-EFFECT)` will result in the model
+            transitioning to state `OUT-STATE`, and executing
+            `SIDE-EFFECT`, which must be a function or lambda that
+            takes a single parameter - a model instance.
+        :type transitions: dict
+        :param initial_state: the starting state of the model
+        :type initial_state: a state with an entry in the transitions
+            table
+        """
+        self._transitions = transitions
+        self._state = initial_state
+
+    @property
+    def state(self):
+        """Return current state as a string."""
+        return self._state
+
+    def update_transitions(self, transitions):
+        """
+        Update the transitions table with new transitions.
+
+        :param transitions: new transitions to be included in the
+            transitions table. Transitions with pre-existing keys will
+            replace the transitions for that key. Transitions with novel
+            keys will be added. There is no provision for removing
+            transitions
+        :type transitions: dict
+        """
+        self._transitions.update(transitions)
+
+    def is_action_allowed(self, action):
+        """
+        Whether a given action is allowed in the current state.
+
+        :param action: an action, as given in the transitions table
+        :type action: ANY
+        """
+        return (self._state, action) in self._transitions
+
+    def try_action(self, action):
+        """
+        Checks whether a given action is allowed in the current state,
+        and raises a StateModelError if it is not.
+
+        :param action: an action, as given in the transitions table
+        :type action: ANY
+        :raises StateModelError: if the action is not allowed in the
+            current state
+        :returns: True if the action is allowed
+        :rtype: boolean
+        """
+        if not self.is_action_allowed(action):
+            raise StateModelError(
+                f"Action '{action}' not allowed in current state ({self._state})."
+            )
+        return True
+
+    def perform_action(self, action):
+        """
+        Performs an action on the state model
+
+        :param action: an action, as given in the transitions table
+        :type action: ANY
+        :raises StateModelError: if the action is not allowed in the
+            current state
+
+        """
+        self.try_action(action)
+
+        (self._state, side_effect) = self._transitions[(self._state, action)]
+        if side_effect is not None:
+            side_effect(self)
diff --git a/src/ska/base/faults.py b/src/ska/base/faults.py
index 0fb8cf06f5b2aa557fa09e0f00f77251a2b66b62..b25218b6ad9a3b7d2b76ad024c22cfd8236b1730 100644
--- a/src/ska/base/faults.py
+++ b/src/ska/base/faults.py
@@ -15,3 +15,19 @@ class LoggingLevelError(SKABaseError):
 
 class LoggingTargetError(SKABaseError):
     """Error parsing logging target string."""
+
+
+class ResultCodeError(ValueError):
+    """A method has returned an invalid return code."""
+
+
+class StateModelError(ValueError):
+    """Error in state machine model related to transitions or state."""
+
+
+class CommandError(RuntimeError):
+    """Error executing a BaseCommand or similar."""
+
+
+class CapabilityValidationError(ValueError):
+    """Error in validating capability input against capability types."""
diff --git a/src/ska/base/logger_device.py b/src/ska/base/logger_device.py
index 6f1113f88f2295e75c53770fc97016097c399495..72f03d2cf291bd62b2135e94257cbbe432d5a793 100644
--- a/src/ska/base/logger_device.py
+++ b/src/ska/base/logger_device.py
@@ -4,23 +4,21 @@
 #
 #
 #
-""" SKALogger
-
-A generic base device for Logging for SKA. It enables to view on-line logs through the TANGO Logging Services
-and to store logs using Python logging. It configures the log levels of remote logging for selected devices.
+"""
+This module implements SKALogger device, a generic base device for
+logging for SKA. It enables to view on-line logs through the TANGO
+Logging Services and to store logs using Python logging. It configures
+the log levels of remote logging for selected devices.
 """
 # PROTECTED REGION ID(SKALogger.additionnal_import) ENABLED START #
-# Standard imports
-import os
-import sys
-
 # Tango imports
 from tango import DebugIt, DeviceProxy, DevFailed
 from tango.server import run, command
 
 # SKA specific imports
-from . import SKABaseDevice, release
-from .control_model import LoggingLevel
+from ska.base import SKABaseDevice
+from ska.base.commands import ResponseCommand, ResultCode
+from ska.base.control_model import LoggingLevel
 # PROTECTED REGION END #    //  SKALogger.additionnal_import
 
 __all__ = ["SKALogger", "main"]
@@ -44,14 +42,15 @@ class SKALogger(SKABaseDevice):
     # ---------------
     # General methods
     # ---------------
-
-    def init_device(self):
-        SKABaseDevice.init_device(self)
-        # PROTECTED REGION ID(SKALogger.init_device) ENABLED START #
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # PROTECTED REGION END #    //  SKALogger.init_device
+    def init_command_objects(self):
+        """
+        Sets up the command objects
+        """
+        super().init_command_objects()
+        self.register_command_object(
+            "SetLoggingLevel",
+            self.SetLoggingLevelCommand(self, self.state_model, self.logger)
+        )
 
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKALogger.always_executed_hook) ENABLED START #
@@ -70,36 +69,84 @@ class SKALogger(SKABaseDevice):
     # --------
     # Commands
     # --------
-
-    @command(dtype_in='DevVarLongStringArray',
-             doc_in="Logging level for selected devices:"
-                    "(0=OFF, 1=FATAL, 2=ERROR, 3=WARNING, 4=INFO, 5=DEBUG)."
-                    "Example: [[4, 5], ['my/dev/1', 'my/dev/2']].")
+    class SetLoggingLevelCommand(ResponseCommand):
+        """
+        A class for the SKALoggerDevice's SetLoggingLevel() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for SetLoggingLevelCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, logger=logger)
+
+        def do(self, argin):
+            """
+            Stateless hook for SetLoggingLevel() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            logging_levels = argin[0][:]
+            logging_devices = argin[1][:]
+            for level, device in zip(logging_levels, logging_devices):
+                try:
+                    new_level = LoggingLevel(level)
+                    self.logger.info("Setting logging level %s for %s", new_level, device)
+                    dev_proxy = DeviceProxy(device)
+                    dev_proxy.loggingLevel = new_level
+                except DevFailed:
+                    self.logger.exception("Failed to set logging level %s for %s", level, device)
+
+            message = "SetLoggingLevel command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    @command(
+        dtype_in='DevVarLongStringArray',
+        doc_in="Logging level for selected devices:"
+               "(0=OFF, 1=FATAL, 2=ERROR, 3=WARNING, 4=INFO, 5=DEBUG)."
+               "Example: [[4, 5], ['my/dev/1', 'my/dev/2']].",
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
     def SetLoggingLevel(self, argin):
         # PROTECTED REGION ID(SKALogger.SetLoggingLevel) ENABLED START #
         """
         Sets logging level of the specified devices.
 
-        :parameter: argin: DevVarLongStringArray
-            Array consisting of
+        To modify behaviour for this command, modify the do() method of
+        the command class.
 
-            argin[0]: list of DevLong. Desired logging level.
+        :param argin: Array consisting of
 
-            argin[1]: list of DevString. Desired tango device.
+            * argin[0]: list of DevLong. Desired logging level.
+            * argin[1]: list of DevString. Desired tango device.
+
+        :type argin: DevVarLongStringArray
 
         :returns: None.
         """
-        logging_levels = argin[0][:]
-        logging_devices = argin[1][:]
-        for level, device in zip(logging_levels, logging_devices):
-            try:
-                new_level = LoggingLevel(level)
-                self.logger.info("Setting logging level %s for %s", new_level, device)
-                dev_proxy = DeviceProxy(device)
-                dev_proxy.loggingLevel = new_level
-            except DevFailed:
-                self.logger.exception("Failed to set logging level %s for %s", level, device)
+        command = self.get_command_object("SetLoggingLevel")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
+
         # PROTECTED REGION END #    //  SKALogger.SetLoggingLevel
 
 # ----------
@@ -115,5 +162,6 @@ def main(args=None, **kwargs):
     return run((SKALogger,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKALogger.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/master_device.py b/src/ska/base/master_device.py
index 1b227c8a8026cd4cc7651c34944060cffeacd324..bc5038198b293844d4717febb1fbd06811c91be2 100644
--- a/src/ska/base/master_device.py
+++ b/src/ska/base/master_device.py
@@ -7,20 +7,17 @@
 
 """ SKAMaster
 
-A master test
+Master device
 """
 # PROTECTED REGION ID(SKAMaster.additionnal_import) ENABLED START #
-# Standard imports
-import os
-import sys
-
 # Tango imports
 from tango import DebugIt
 from tango.server import run, attribute, command, device_property
 
 # SKA specific imports
-from . import SKABaseDevice, release
-from .utils import validate_capability_types, validate_input_sizes, convert_dict_to_list
+from ska.base import SKABaseDevice
+from ska.base.commands import BaseCommand, ResultCode
+from ska.base.utils import validate_capability_types, validate_input_sizes, convert_dict_to_list
 
 
 # PROTECTED REGION END #    //  SKAMaster.additionnal_imports
@@ -30,8 +27,54 @@ __all__ = ["SKAMaster", "main"]
 
 class SKAMaster(SKABaseDevice):
     """
-    A master test
+    Master device
     """
+    def init_command_objects(self):
+        """
+        Sets up the command objects
+        """
+        super().init_command_objects()
+        self.register_command_object(
+            "IsCapabilityAchievable",
+            self.IsCapabilityAchievableCommand(
+                self, self.state_model, self.logger
+            )
+        )
+
+    class InitCommand(SKABaseDevice.InitCommand):
+        """
+        A class for the SKAMaster's init_device() "command".
+        """
+        def do(self):
+            """
+            Stateless hook for device initialisation.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            super().do()
+
+            device = self.target
+            device._element_logger_address = ""
+            device._element_alarm_address = ""
+            device._element_tel_state_address = ""
+            device._element_database_address = ""
+            device._element_alarm_device = ""
+            device._element_tel_state_device = ""
+            device._element_database_device = ""
+            device._max_capabilities = {}
+            if device.MaxCapabilities:
+                for max_capability in device.MaxCapabilities:
+                    capability_type, max_capability_instances = max_capability.split(":")
+                    device._max_capabilities[capability_type] = int(max_capability_instances)
+            device._available_capabilities = device._max_capabilities.copy()
+
+            message = "SKAMaster Init command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
     # PROTECTED REGION ID(SKAMaster.class_variable) ENABLED START #
     # PROTECTED REGION END #    //  SKAMaster.class_variable
 
@@ -73,7 +116,8 @@ class SKAMaster(SKABaseDevice):
     maxCapabilities = attribute(
         dtype=('str',),
         max_dim_x=20,
-        doc="Maximum number of instances of each capability type, e.g. 'CORRELATOR:512', 'PSS-BEAMS:4'.",
+        doc=("Maximum number of instances of each capability type,"
+             " e.g. 'CORRELATOR:512', 'PSS-BEAMS:4'."),
     )
 
     availableCapabilities = attribute(
@@ -87,28 +131,6 @@ class SKAMaster(SKABaseDevice):
     # General methods
     # ---------------
 
-    def init_device(self):
-        SKABaseDevice.init_device(self)
-        # PROTECTED REGION ID(SKAMaster.init_device) ENABLED START #
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # Initialize attribute values.
-        self._element_logger_address = ""
-        self._element_alarm_address = ""
-        self._element_tel_state_address = ""
-        self._element_database_address = ""
-        self._element_alarm_device = ""
-        self._element_tel_state_device = ""
-        self._element_database_device = ""
-        self._max_capabilities = {}
-        if self.MaxCapabilities:
-            for max_capability in self.MaxCapabilities:
-                capability_type, max_capability_instances = max_capability.split(":")
-                self._max_capabilities[capability_type] = int(max_capability_instances)
-        self._available_capabilities = self._max_capabilities.copy()
-        # PROTECTED REGION END #    //  SKAMaster.init_device
-
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKAMaster.always_executed_hook) ENABLED START #
         pass
@@ -163,49 +185,70 @@ class SKAMaster(SKABaseDevice):
     # Commands
     # --------
 
-    @command(dtype_in='DevVarLongStringArray', doc_in="[nrInstances][Capability types]", dtype_out='bool',)
+    class IsCapabilityAchievableCommand(BaseCommand):
+        """
+        A class for the SKAMaster's IsCapabilityAchievable() command.
+        """
+        def do(self, argin):
+            """
+            Stateless hook for device IsCapabilityAchievable() command.
+
+            :return: Whether the capability is achievable
+            :rtype: bool
+            """
+            device = self.target
+            command_name = 'isCapabilityAchievable'
+            capabilities_instances, capability_types = argin
+            validate_input_sizes(command_name, argin)
+            validate_capability_types(command_name, capability_types,
+                                      list(device._max_capabilities.keys()))
+
+            for capability_type, capability_instances in zip(
+                capability_types, capabilities_instances
+            ):
+                if not device._available_capabilities[
+                    capability_type
+                ] >= capability_instances:
+                    return False
+            return True
+
+    @command(
+        dtype_in='DevVarLongStringArray',
+        doc_in="[nrInstances][Capability types]",
+        dtype_out='bool',
+    )
     @DebugIt()
     def isCapabilityAchievable(self, argin):
         # PROTECTED REGION ID(SKAMaster.isCapabilityAchievable) ENABLED START #
         """
         Checks of provided capabilities can be achieved by the resource(s).
 
-        :param argin: DevVarLongStringArray.
+        To modify behaviour for this command, modify the do() method of
+        the command class.
 
-            An array consisting pair of
-                    [nrInstances]: DevLong. Number of instances of the capability.
+        :param argin: An array consisting pair of
 
-                    [Capability types]: DevString. Type of capability.
+            * [nrInstances]: DevLong. Number of instances of the capability.
+            * [Capability types]: DevString. Type of capability.
 
-        :return: DevBoolean
+        :type argin: DevVarLongStringArray.
 
-            True if capability can be achieved.
-
-            False if cannot.
+        :return: True if capability can be achieved, False if cannot
+        :rtype: DevBoolean
         """
-        command_name = 'isCapabilityAchievable'
-        capabilities_instances, capability_types = argin
-        validate_input_sizes(command_name, argin)
-        validate_capability_types(command_name, capability_types,
-                                  list(self._max_capabilities.keys()))
-
-        for capability_type, capability_instances in zip(
-                capability_types, capabilities_instances):
-            if not self._available_capabilities[capability_type] >= capability_instances:
-                return False
-
-        return True
+        command = self.get_command_object("IsCapabilityAchievable")
+        return command(argin)
         # PROTECTED REGION END #    //  SKAMaster.isCapabilityAchievable
 
+
 # ----------
 # Run server
 # ----------
-
-
 def main(args=None, **kwargs):
     # PROTECTED REGION ID(SKAMaster.main) ENABLED START #
     return run((SKAMaster,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKAMaster.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/obs_device.py b/src/ska/base/obs_device.py
index 18f63d8997737156af3993a3696d22b6c2b7a57a..90e5d9834ad8e9dde56dfdb5ee899992b3b1fd6b 100644
--- a/src/ska/base/obs_device.py
+++ b/src/ska/base/obs_device.py
@@ -6,32 +6,96 @@
 #
 """ SKAObsDevice
 
-A generic base device for Observations for SKA. It inherits SKABaseDevice class. Any device implementing
-an obsMode will inherit from SKAObsDevice instead of just SKABaseDevice.
+A generic base device for Observations for SKA. It inherits SKABaseDevice
+class. Any device implementing an obsMode will inherit from SKAObsDevice
+instead of just SKABaseDevice.
 """
 
 # Additional import
 # PROTECTED REGION ID(SKAObsDevice.additionnal_import) ENABLED START #
-# Standard imports
-import os
-import sys
-
 # Tango imports
+from tango import DevState
 from tango.server import run, attribute
 
 # SKA specific imports
-from . import SKABaseDevice, release
-from .control_model import ObsMode, ObsState
+from ska.base import SKABaseDevice, SKABaseDeviceStateModel
+from ska.base.commands import ResultCode
+from ska.base.control_model import AdminMode, ObsMode, ObsState
 # PROTECTED REGION END #    //  SKAObsDevice.additionnal_imports
 
-__all__ = ["SKAObsDevice", "main"]
+__all__ = ["SKAObsDevice", "SKAObsDeviceStateModel", "main"]
+
+
+class SKAObsDeviceStateModel(SKABaseDeviceStateModel):
+    """
+    Implements the state model for the SKABaseDevice
+    """
+    def __init__(self, dev_state_callback=None):
+        """
+        Initialises the model. Note that this does not imply moving to
+        INIT state. The INIT state is managed by the model itself.
+        """
+        super().__init__(
+            dev_state_callback=dev_state_callback
+        )
+        self.update_transitions(
+            {
+                ('UNINITIALISED', 'init_started'): (
+                    "INIT (ENABLED)",
+                    lambda self: (
+                        self._set_admin_mode(AdminMode.MAINTENANCE),
+                        self._set_dev_state(DevState.INIT),
+                        self._set_obs_state(ObsState.EMPTY)
+                    )
+                )
+            }
+        )
+        self._obs_state = None
+
+    def _set_obs_state(self, obs_state):
+        """
+        Helper method: set the value of obs_state value
+
+        :param obs_state: the new obs_state value
+        :type obs_state: ObsState
+        """
+        self._obs_state = obs_state
+
+    @property
+    def obs_state(self):
+        return self._obs_state
 
 
 class SKAObsDevice(SKABaseDevice):
     """
     A generic base device for Observations for SKA.
     """
+    class InitCommand(SKABaseDevice.InitCommand):
+        """
+        A class for the SKAObsDevice's init_device() "command".
+        """
+        def do(self):
+            """
+            Stateless hook for device initialisation.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            super().do()
+
+            device = self.target
+            device._obs_mode = ObsMode.IDLE
+            device._config_progress = 0
+            device._config_delay_expected = 0
+
+            message = "SKAObsDevice Init command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
     # PROTECTED REGION ID(SKAObsDevice.class_variable) ENABLED START #
+
     # PROTECTED REGION END #    //  SKAObsDevice.class_variable
 
     # -----------------
@@ -71,19 +135,13 @@ class SKAObsDevice(SKABaseDevice):
     # ---------------
     # General methods
     # ---------------
-
-    def init_device(self):
-        SKABaseDevice.init_device(self)
-        # PROTECTED REGION ID(SKAObsDevice.init_device) ENABLED START #
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # Initialize attribute values.
-        self._obs_state = ObsState.IDLE
-        self._obs_mode = ObsMode.IDLE
-        self._config_progress = 0
-        self._config_delay_expected = 0
-        # PROTECTED REGION END #    //  SKAObsDevice.init_device
+    def _init_state_model(self):
+        """
+        Sets up the state model for the device
+        """
+        self.state_model = SKAObsDeviceStateModel(
+            dev_state_callback=self._update_state,
+        )
 
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKAObsDevice.always_executed_hook) ENABLED START #
@@ -102,7 +160,7 @@ class SKAObsDevice(SKABaseDevice):
     def read_obsState(self):
         # PROTECTED REGION ID(SKAObsDevice.obsState_read) ENABLED START #
         """Reads Observation State of the device"""
-        return self._obs_state
+        return self.state_model.obs_state
         # PROTECTED REGION END #    //  SKAObsDevice.obsState_read
 
     def read_obsMode(self):
@@ -123,7 +181,6 @@ class SKAObsDevice(SKABaseDevice):
         return self._config_delay_expected
         # PROTECTED REGION END #    //  SKAObsDevice.configurationDelayExpected_read
 
-
     # --------
     # Commands
     # --------
@@ -132,10 +189,18 @@ class SKAObsDevice(SKABaseDevice):
 # Run server
 # ----------
 
+
 def main(args=None, **kwargs):
     # PROTECTED REGION ID(SKAObsDevice.main) ENABLED START #
+    """
+    Main function of the SKAObsDevice module.
+
+    :param args: None
+    :param kwargs:
+    """
     return run((SKAObsDevice,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKAObsDevice.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/release.py b/src/ska/base/release.py
index 7ec0d525f8fca6ebf0d17f91571b76091239fc6e..f6b57c23af647733b1970b8af313b9070f0f7456 100644
--- a/src/ska/base/release.py
+++ b/src/ska/base/release.py
@@ -7,7 +7,7 @@
 """Release information for lmc-base-classes Python Package"""
 
 name = """lmcbaseclasses"""
-version = "0.5.4"
+version = "0.6.0"
 version_info = version.split(".")
 description = """A set of generic base devices for SKA Telescope."""
 author = "SKA India and SARAO"
diff --git a/src/ska/base/subarray_device.py b/src/ska/base/subarray_device.py
index 1b785c5833126318b97ac125fa986a6dc0ca0ac5..db105c86e02d60aa1beb4821a6c93e45564fed30 100644
--- a/src/ska/base/subarray_device.py
+++ b/src/ska/base/subarray_device.py
@@ -6,158 +6,964 @@
 #
 """ SKASubarray
 
-A SubArray handling device. It allows the assigning/releasing of resources into/from Subarray, configuring
-capabilities, and exposes the related information like assigned resources, configured capabilities, etc.
+A SubArray handling device. It allows the assigning/releasing of resources
+into/from Subarray, configuring capabilities, and exposes the related
+information like assigned resources, configured capabilities, etc.
 """
 # PROTECTED REGION ID(SKASubarray.additionnal_import) ENABLED START #
-# Standard imports
-import os
-import sys
+import json
 
-# Tango imports
 from tango import DebugIt
+from tango import DevState
 from tango.server import run, attribute, command
 from tango.server import device_property
-from tango import Except, ErrSeverity, DevState
 
 # SKA specific imports
-from . import SKAObsDevice, release
-from .control_model import AdminMode, ObsState
+from ska.base import SKAObsDevice, SKAObsDeviceStateModel
+from ska.base.commands import ActionCommand, ResultCode
+from ska.base.control_model import ObsState
+from ska.base.faults import CapabilityValidationError
 # PROTECTED REGION END #    //  SKASubarray.additionnal_imports
 
-__all__ = ["SKASubarray", "main"]
+__all__ = ["SKASubarray", "SKASubarrayResourceManager", "SKASubarrayStateModel", "main"]
 
 
-class SKASubarray(SKAObsDevice):
+class SKASubarrayStateModel(SKAObsDeviceStateModel):
     """
-    SubArray handling device
+    Implements the state model for the SKASubarray
     """
-    # PROTECTED REGION ID(SKASubarray.class_variable) ENABLED START #
-    def _is_command_allowed(self, command_name):
-        """Determine whether the command specified by the command_name parameter should
-        be allowed to execute or not.
-
-        Parameters
-        ----------
-        command_name: str
-            The name of the command which is to be executed.
-
-        Returns
-        -------
-        True or False: boolean
-            A True is returned when the device is in the allowed states and modes to
-            execute the command. Returns False if the command name is not in the list of
-            commands with rules specified for them.
-
-        Raises
-        ------
-        tango.DevFailed: If the device is not in the allowed states and/modes to
-            execute the command.
-        """
-        admin_mode = self.read_adminMode()
-        obs_state = self.read_obsState()
-        if command_name in ["ReleaseResources", "AssignResources"]:
-            if admin_mode in [AdminMode.OFFLINE, AdminMode.NOT_FITTED]:
-                Except.throw_exception("Command failed!", "Subarray adminMode is"
-                                       " 'OFFLINE' or 'NOT_FITTED'.",
-                                       command_name, ErrSeverity.ERR)
-
-            if obs_state == ObsState.IDLE:
-                if admin_mode in [AdminMode.ONLINE, AdminMode.MAINTENANCE]:
-                    return True
-                else:
-                    Except.throw_exception("Command failed!", "Subarray adminMode not"
-                                           "'ONLINE' or not in 'MAINTENANCE'.",
-                                           command_name, ErrSeverity.ERR)
-            else:
-                Except.throw_exception("Command failed!", "Subarray obsState not 'IDLE'.",
-                                       command_name, ErrSeverity.ERR)
-
-        elif command_name in ['ConfigureCapability', 'DeconfigureCapability',
-                              'DeconfigureAllCapabilities']:
-            if self.get_state() == DevState.ON and admin_mode == AdminMode.ONLINE:
-                if obs_state in [ObsState.IDLE, ObsState.READY]:
-                    return True
-                else:
-                    Except.throw_exception(
-                        "Command failed!", "Subarray obsState not 'IDLE' or 'READY'.",
-                        command_name, ErrSeverity.ERR)
-            else:
-                Except.throw_exception(
-                    "Command failed!", "Subarray State not 'ON' and/or adminMode not"
-                    " 'ONLINE'.", command_name, ErrSeverity.ERR)
+    __transitions = {
+        ('OFF', 'on_succeeded'): (
+            "EMPTY",
+            lambda self: self._set_dev_state(DevState.ON)
+        ),
+        ('OFF', 'on_failed'): (
+            "FAULT",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('EMPTY', 'off_succeeded'): (
+            "OFF",
+            lambda self: self._set_dev_state(DevState.OFF)
+        ),
+        ('EMPTY', 'off_failed'): (
+            "FAULT",
+            lambda self: self._set_dev_state(DevState.FAULT)
+        ),
+        ('EMPTY', 'assign_started'): (
+            "RESOURCING",
+            lambda self: self._set_obs_state(ObsState.RESOURCING)
+        ),
+        ('EMPTY', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESOURCING', 'resourcing_succeeded_some_resources'): (
+            "IDLE",
+            lambda self: self._set_obs_state(ObsState.IDLE)
+        ),
+        ('RESOURCING', 'resourcing_succeeded_no_resources'): (
+            "EMPTY",
+            lambda self: self._set_obs_state(ObsState.EMPTY)
+        ),
+        ('RESOURCING', 'resourcing_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESOURCING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('IDLE', 'assign_started'): (
+            "RESOURCING",
+            lambda self: self._set_obs_state(ObsState.RESOURCING)
+        ),
+        ('IDLE', 'release_started'): (
+            "RESOURCING",
+            lambda self: self._set_obs_state(ObsState.RESOURCING)
+        ),
+        ('IDLE', 'configure_started'): (
+            "CONFIGURING",
+            lambda self: self._set_obs_state(ObsState.CONFIGURING)
+        ),
+        ('IDLE', 'abort_started'): (
+            "ABORTING",
+            lambda self: self._set_obs_state(ObsState.ABORTING)
+        ),
+        ('IDLE', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('CONFIGURING', 'configure_succeeded'): (
+            "READY",
+            lambda self: self._set_obs_state(ObsState.READY)
+        ),
+        ('CONFIGURING', 'configure_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('CONFIGURING', 'abort_started'): (
+            "ABORTING",
+            lambda self: self._set_obs_state(ObsState.ABORTING)
+        ),
+        ('CONFIGURING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('READY', 'end_succeeded'): (
+            "IDLE",
+            lambda self: self._set_obs_state(ObsState.IDLE)
+        ),
+        ('READY', 'end_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('READY', 'configure_started'): (
+            "CONFIGURING",
+            lambda self: self._set_obs_state(ObsState.CONFIGURING)
+        ),
+        ('READY', 'abort_started'): (
+            "ABORTING",
+            lambda self: self._set_obs_state(ObsState.ABORTING)
+        ),
+        ('READY', 'scan_started'): (
+            "SCANNING",
+            lambda self: self._set_obs_state(ObsState.SCANNING)
+        ),
+        ('READY', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('SCANNING', 'scan_succeeded'): (
+            "READY",
+            lambda self: self._set_obs_state(ObsState.READY)
+        ),
+        ('SCANNING', 'scan_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('SCANNING', 'end_scan_succeeded'): (
+            "READY",
+            lambda self: self._set_obs_state(ObsState.READY)
+        ),
+        ('SCANNING', 'end_scan_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('SCANNING', 'abort_started'): (
+            "ABORTING",
+            lambda self: self._set_obs_state(ObsState.ABORTING)
+        ),
+        ('SCANNING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('ABORTING', 'abort_succeeded'): (
+            "ABORTED",
+            lambda self: self._set_obs_state(ObsState.ABORTED)
+        ),
+        ('ABORTING', 'abort_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('ABORTING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('ABORTED', 'obs_reset_started'): (
+            "RESETTING",
+            lambda self: self._set_obs_state(ObsState.RESETTING)
+        ),
+        ('ABORTED', 'restart_started'): (
+            "RESTARTING",
+            lambda self: self._set_obs_state(ObsState.RESTARTING)
+        ),
+        ('ABORTED', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('OBSFAULT', 'obs_reset_started'): (
+            "RESETTING",
+            lambda self: self._set_obs_state(ObsState.RESETTING)
+        ),
+        ('OBSFAULT', 'restart_started'): (
+            "RESTARTING",
+            lambda self: self._set_obs_state(ObsState.RESTARTING)
+        ),
+        ('OBSFAULT', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESETTING', 'obs_reset_succeeded'): (
+            "IDLE",
+            lambda self: self._set_obs_state(ObsState.IDLE)
+        ),
+        ('RESETTING', 'obs_reset_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESETTING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESTARTING', 'restart_succeeded'): (
+            "EMPTY",
+            lambda self: self._set_obs_state(ObsState.EMPTY)
+        ),
+        ('RESTARTING', 'restart_failed'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+        ('RESTARTING', 'fatal_error'): (
+            "OBSFAULT",
+            lambda self: self._set_obs_state(ObsState.FAULT)
+        ),
+    }
+
+    def __init__(self, dev_state_callback=None):
+        """
+        Initialises the model. Note that this does not imply moving to
+        INIT state. The INIT state is managed by the model itself.
+        """
+        super().__init__(
+            dev_state_callback=dev_state_callback,
+        )
+        self.update_transitions(self.__transitions)
 
-        return False
 
+class SKASubarrayResourceManager:
+    """
+    A simple class for managing subarray resources
+    """
+    def __init__(self):
+        """
+        Constructor for SKASubarrayResourceManager
+        """
+        self._resources = set()
 
-    def _validate_capability_types(self, command_name, capability_types):
-        """Check the validity of the input parameter passed on to the command specified
-        by the command_name parameter.
+    def __len__(self):
+        """
+        Returns the number of resources currently assigned. Note that
+        this also functions as a boolean method for whether there are
+        any assigned resources: ``if len()``.
 
-        Parameters
-        ----------
-        command_name: str
-            The name of the command which is to be executed.
-        capability_types: list
-            A list strings representing capability types.
+        :return: number of resources assigned
+        :rtype: int
+        """
+        return len(self._resources)
 
-        Raises
-        ------
-        tango.DevFailed: If any of the capabilities requested are not valid.
+    def assign(self, resources):
         """
-        invalid_capabilities = list(
-            set(capability_types) - set(self._configured_capabilities))
+        Assign some resources
+
+        :todo: Currently implemented for testing purposes to take a JSON
+            string encoding a dictionary with key 'example'. In future this
+            will take a collection of resources.
+        :param resources: JSON-encoding of a dictionary, with resources to
+            assign under key 'example'
+        :type resources: JSON string
+        """
+        resources_dict = json.loads(resources)
+        add_resources = resources_dict['example']
+        self._resources |= set(add_resources)
 
-        if invalid_capabilities:
-            Except.throw_exception(
-                "Command failed!", "Invalid capability types requested {}".format(
-                    invalid_capabilities), command_name, ErrSeverity.ERR)
+    def release(self, resources):
+        """
+        Release some resources
+
+        :todo: Currently implemented for testing purposes to take a JSON
+            string encoding a dictionary with key 'example'. In future this
+            will take a collection of resources.
+        :param resources: JSON-encoding of a dictionary, with resources to
+            assign under key 'example'
+        :type resources: JSON string
+        """
+        resources_dict = json.loads(resources)
+        drop_resources = resources_dict['example']
+        self._resources -= set(drop_resources)
 
+    def release_all(self):
+        """
+        Release all resources
+        """
+        self._resources.clear()
 
-    def _validate_input_sizes(self, command_name, argin):
-        """Check the validity of the input parameters passed on to the command specified
-        by the command_name parameter.
+    def get(self):
+        """
+        Get current resources
+
+        :return: a set of current resources.
+        :rtype: set of string
+        """
+        return set(self._resources)
 
-        Parameters
-        ----------
-        command_name: str
-            The name of the command which is to be executed.
-        argin: tango.DevVarLongStringArray
-            A tuple of two lists representing [number of instances][capability types]
 
-        Raises
-        ------
-        tango.DevFailed: If the two lists are not equal in length.
+class SKASubarray(SKAObsDevice):
+    """
+    Implements the SKA SubArray device
+    """
+    class InitCommand(SKAObsDevice.InitCommand):
         """
-        capabilities_instances, capability_types = argin
-        if len(capabilities_instances) != len(capability_types):
-            Except.throw_exception("Command failed!", "Argin value lists size mismatch.",
-                                   command_name, ErrSeverity.ERR)
+        A class for the SKASubarray's init_device() "command".
+        """
+        def do(self):
+            """
+            Stateless hook for device initialisation.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            super().do()
+
+            device = self.target
+            device.resource_manager = SKASubarrayResourceManager()
+            device._activation_time = 0.0
+
+            # device._configured_capabilities is kept as a
+            # dictionary internally. The keys and values will represent
+            # the capability type name and the number of instances,
+            # respectively.
+            try:
+                device._configured_capabilities = dict.fromkeys(
+                    device.CapabilityTypes,
+                    0
+                )
+            except TypeError:
+                # Might need to have the device property be mandatory in the database.
+                device._configured_capabilities = {}
+
+            message = "SKASubarray Init command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class OnCommand(ActionCommand):
+        """
+        A class for the SKASubarray's On() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for OnCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "on", logger=logger)
+
+        def do(self):
+            """
+            Stateless hook for On() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            message = "On command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class OffCommand(ActionCommand):
+        """
+        A class for the SKASubarray's Off() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for OffCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "off", logger=logger)
+
+        def do(self):
+            """
+            Stateless hook for Off() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            message = "Off command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class _ResourcingCommand(ActionCommand):
+        """
+        An abstract base class for SKASubarray's resourcing commands.
+        """
+        def __init__(self, target, state_model, action_hook, logger=None):
+            """
+            Constructor for _ResourcingCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param action_hook: a hook for the command, used to build
+                actions that will be sent to the state model; for example,
+                if the hook is "scan", then success of the command will
+                result in action "scan_succeeded" being sent to the state
+                model.
+            :type action_hook: string
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, action_hook, start_action=True, logger=logger
+            )
+
+        def succeeded(self):
+            """
+            Action to take on successful completion of a resourcing
+            command.
+            """
+            if len(self.target):
+                action = "resourcing_succeeded_some_resources"
+            else:
+                action = "resourcing_succeeded_no_resources"
+            self.state_model.perform_action(action)
 
+        def failed(self):
+            """
+            Action to take on failed completion of a resourcing command.
+            """
+            self.state_model.perform_action("resourcing_failed")
 
-    def is_AssignResources_allowed(self):
-        return self._is_command_allowed("AssignResources")
+    class AssignResourcesCommand(_ResourcingCommand):
+        """
+        A class for SKASubarray's AssignResources() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for AssignResourcesCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "assign", logger=logger)
+
+        def do(self, argin):
+            """
+            Stateless hook for AssignResources() command functionality.
+
+            :param argin: The resources to be assigned
+            :type argin: list of str
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            resource_manager = self.target
+            resource_manager.assign(argin)
+
+            message = "AssignResources command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class ReleaseResourcesCommand(_ResourcingCommand):
+        """
+        A class for SKASubarray's ReleaseResources() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for OnCommand()
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "release", logger=logger)
+
+        def do(self, argin):
+            """
+            Stateless hook for ReleaseResources() command functionality.
+
+            :param argin: The resources to be released
+            :type argin: list of str
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            resource_manager = self.target
+            resource_manager.release(argin)
+
+            message = "ReleaseResources command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class ReleaseAllResourcesCommand(ReleaseResourcesCommand):
+        """
+        A class for SKASubarray's ReleaseAllResources() command.
+        """
+        def do(self):
+            """
+            Stateless hook for ReleaseAllResources() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            resource_manager = self.target
+            resource_manager.release_all()
+
+            if len(resource_manager):
+                message = "ReleaseAllResources command failed to release all."
+                self.logger.info(message)
+                return (ResultCode.FAILED, message)
+            else:
+                message = "ReleaseAllResources command completed OK"
+                self.logger.info(message)
+                return (ResultCode.OK, message)
 
-    def is_ReleaseResources_allowed(self):
-        return self._is_command_allowed("ReleaseResources")
+    class ConfigureCommand(ActionCommand):
+        """
+        A class for SKASubarray's Configure() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for ConfigureCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "configure", start_action=True, logger=logger
+            )
+
+        def do(self, argin):
+            """
+            Stateless hook for Configure() command functionality.
+
+            :param argin: The configuration as JSON
+            :type argin: str
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+
+            # In this example implementation, the keys of the dict
+            # are the capability types, and the values are the
+            # integer number of instances required.
+            # E.g., config = {"BAND1": 5, "BAND2": 3}
+            config = json.loads(argin)
+            capability_types = list(config.keys())
+            device._validate_capability_types(capability_types)
+
+            # Perform the configuration.
+            for capability_type, capability_instances in config.items():
+                device._configured_capabilities[capability_type] += capability_instances
+
+            message = "Configure command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class ScanCommand(ActionCommand):
+        """
+        A class for SKASubarray's Scan() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for ScanCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "scan", start_action=True, logger=logger
+            )
+
+        def do(self, argin):
+            """
+            Stateless hook for Scan() command functionality.
+
+            :param argin: Scan info
+            :type argin: str
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            # we do a json.loads just for basic string validation
+            message = f"Scan command STARTED - config {json.loads(argin)}"
+            self.logger.info(message)
+            return (ResultCode.STARTED, message)
+
+    class EndScanCommand(ActionCommand):
+        """
+        A class for SKASubarray's EndScan() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for EndScanCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "end_scan", logger=logger)
+
+        def do(self):
+            """
+            Stateless hook for EndScan() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            message = "EndScan command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class EndCommand(ActionCommand):
+        """
+        A class for SKASubarray's End() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for EndCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(target, state_model, "end", logger=logger)
+
+        def do(self):
+            """
+            Stateless hook for End() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+            device._deconfigure()
+
+            message = "End command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class AbortCommand(ActionCommand):
+        """
+        A class for SKASubarray's Abort() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for AbortCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "abort", start_action=True, logger=logger
+            )
+
+        def do(self):
+            """
+            Stateless hook for Abort() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            message = "Abort command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class ObsResetCommand(ActionCommand):
+        """
+        A class for SKASubarray's ObsReset() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for ObsResetCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "obs_reset", start_action=True, logger=logger
+            )
+
+        def do(self):
+            """
+            Stateless hook for ObsReset() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+
+            # We might have interrupted a long-running command such as a Configure
+            # or a Scan, so we need to clean up from that.
+
+            # Now totally deconfigure
+            device._deconfigure()
+
+            message = "ObsReset command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
+
+    class RestartCommand(ActionCommand):
+        """
+        A class for SKASubarray's Restart() command.
+        """
+        def __init__(self, target, state_model, logger=None):
+            """
+            Constructor for RestartCommand
+
+            :param target: the object that this command acts upon; for
+                example, the SKASubarray device for which this class
+                implements the command
+            :type target: object
+            :param state_model: the state model that this command uses
+                 to check that it is allowed to run, and that it drives
+                 with actions.
+            :type state_model: SKABaseClassStateModel or a subclass of
+                same
+            :param logger: the logger to be used by this Command. If not
+                provided, then a default module logger will be used.
+            :type logger: a logger that implements the standard library
+                logger interface
+            """
+            super().__init__(
+                target, state_model, "restart", start_action=True, logger=logger
+            )
+
+        def do(self):
+            """
+            Stateless hook for Restart() command functionality.
+
+            :return: A tuple containing a return code and a string
+                message indicating status. The message is for
+                information purpose only.
+            :rtype: (ResultCode, str)
+            """
+            device = self.target
+
+            # We might have interrupted a long-running command such as a Configure
+            # or a Scan, so we need to clean up from that.
+
+            # Now totally deconfigure
+            device._deconfigure()
+
+            # and release all resources
+            device.resource_manager.release_all()
+
+            message = "Restart command completed OK"
+            self.logger.info(message)
+            return (ResultCode.OK, message)
 
-    def is_ReleaseAllResources_allowed(self):
-        return self._is_command_allowed("ReleaseResources")
+    # PROTECTED REGION ID(SKASubarray.class_variable) ENABLED START #
+    def _init_state_model(self):
+        """
+        Sets up the state model for the device
+        """
+        self.state_model = SKASubarrayStateModel(
+            dev_state_callback=self._update_state,
+        )
 
-    def is_ConfigureCapability_allowed(self):
-        return self._is_command_allowed('ConfigureCapability')
+    def init_command_objects(self):
+        """
+        Sets up the command objects
+        """
+        super().init_command_objects()
+
+        device_args = (self, self.state_model, self.logger)
+        resource_args = (self.resource_manager, self.state_model, self.logger)
+
+        self.register_command_object("On", self.OnCommand(*device_args))
+        self.register_command_object("Off", self.OffCommand(*device_args))
+        self.register_command_object(
+            "AssignResources",
+            self.AssignResourcesCommand(*resource_args)
+        )
+        self.register_command_object(
+            "ReleaseResources",
+            self.ReleaseResourcesCommand(*resource_args)
+        )
+        self.register_command_object(
+            "ReleaseAllResources",
+            self.ReleaseAllResourcesCommand(*resource_args)
+        )
+        self.register_command_object(
+            "Configure",
+            self.ConfigureCommand(*device_args)
+        )
+        self.register_command_object("Scan", self.ScanCommand(*device_args))
+        self.register_command_object(
+            "EndScan",
+            self.EndScanCommand(*device_args)
+        )
+        self.register_command_object("End", self.EndCommand(*device_args))
+        self.register_command_object("Abort", self.AbortCommand(*device_args))
+        self.register_command_object(
+            "ObsReset",
+            self.ObsResetCommand(*device_args)
+        )
+        self.register_command_object(
+            "Restart",
+            self.RestartCommand(*device_args)
+        )
+
+    def _validate_capability_types(self, capability_types):
+        """
+        Check the validity of the input parameter passed to the
+        Configure command.
+
+        :param device: the device for which this class implements
+            the configure command
+        :type device: SKASubarray
+        :param capability_types: a list strings representing
+            capability types.
+        :type capability_types: list
+        :raises ValueError: If any of the capabilities requested are
+            not valid.
+        """
+        invalid_capabilities = list(
+            set(capability_types) - set(self._configured_capabilities))
 
-    def is_DeconfigureCapability_allowed(self):
-        return self._is_command_allowed('DeconfigureCapability')
+        if invalid_capabilities:
+            raise CapabilityValidationError(
+                "Invalid capability types requested {}".format(
+                    invalid_capabilities
+                )
+            )
 
-    def is_DeconfigureAllCapabilities_allowed(self):
-        return self._is_command_allowed('DeconfigureAllCapabilities')
-    # PROTECTED REGION END #    //  SKASubarray.class_variable
+    def _deconfigure(self):
+        """
+        Completely deconfigure the subarray
+        """
+        self._configured_capabilities = {k: 0 for k in self._configured_capabilities}
 
     # -----------------
     # Device Properties
     # -----------------
-
     CapabilityTypes = device_property(
         dtype=('str',),
     )
@@ -169,7 +975,6 @@ class SKASubarray(SKAObsDevice):
     # ----------
     # Attributes
     # ----------
-
     activationTime = attribute(
         dtype='double',
         unit="s",
@@ -196,32 +1001,6 @@ class SKASubarray(SKAObsDevice):
     # ---------------
     # General methods
     # ---------------
-
-    def init_device(self):
-        SKAObsDevice.init_device(self)
-        # PROTECTED REGION ID(SKASubarray.init_device) ENABLED START #
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-
-        # Initialize attribute values.
-        self._activation_time = 0.0
-        self._assigned_resources = [""]
-        self._assigned_resources.clear()
-        # self._configured_capabilities is gonna be kept as a dictionary internally. The
-        # keys and value will represent the capability type name and the number of
-        # instances, respectively.
-        try:
-            self._configured_capabilities = dict.fromkeys(self.CapabilityTypes, 0)
-        except TypeError:
-            # Might need to have the device property be mandatory in the database.
-            self._configured_capabilities = {}
-
-        # When Subarray in not in use it reports:
-        self.set_state(DevState.DISABLE)
-
-        # PROTECTED REGION END #    //  SKASubarray.init_device
-
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKASubarray.always_executed_hook) ENABLED START #
         pass
@@ -235,11 +1014,11 @@ class SKASubarray(SKAObsDevice):
     # ------------------
     # Attributes methods
     # ------------------
-
     def read_activationTime(self):
         # PROTECTED REGION ID(SKASubarray.activationTime_read) ENABLED START #
         """
         Reads the time since device is activated.
+
         :return: Time of activation in seconds since Unix epoch.
         """
         return self._activation_time
@@ -249,17 +1028,19 @@ class SKASubarray(SKAObsDevice):
         # PROTECTED REGION ID(SKASubarray.assignedResources_read) ENABLED START #
         """
         Reads the resources assigned to the device.
+
         :return: Resources assigned to the device.
         """
-        return self._assigned_resources
+        return sorted(self.resource_manager.get())
         # PROTECTED REGION END #    //  SKASubarray.assignedResources_read
 
     def read_configuredCapabilities(self):
         # PROTECTED REGION ID(SKASubarray.configuredCapabilities_read) ENABLED START #
         """
         Reads capabilities configured in the Subarray.
-        :return: A list of capability types with no. of instances
-        used in the Subarray
+
+        :return: A list of capability types with no. of instances used
+            in the Subarray
         """
         configured_capabilities = []
         for capability_type, capability_instances in (
@@ -272,164 +1053,366 @@ class SKASubarray(SKAObsDevice):
     # --------
     # Commands
     # --------
+    def is_On_allowed(self):
+        """
+        Check if command `On` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("On")
+        return command.check_allowed()
 
     @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
-    def Abort(self):
-        # PROTECTED REGION ID(SKASubarray.Abort) ENABLED START #
-        """Change obsState to ABORTED."""
-        # PROTECTED REGION END #    //  SKASubarray.Abort
+    def On(self):
+        """
+        Turn subarray on
 
-    @command(dtype_in='DevVarLongStringArray', doc_in="[Number of instances to add][Capability types]",)
-    @DebugIt()
-    def ConfigureCapability(self, argin):
-        # PROTECTED REGION ID(SKASubarray.ConfigureCapability) ENABLED START #
-        """Configures number of instances for each capability. If the capability exists,
-        it increments the configured instances by the number of instances requested,
-        otherwise an exception will be raised.
-        Note: The two lists arguments must be of equal length or an exception will be raised."""
-        command_name = 'ConfigureCapability'
-
-        capabilities_instances, capability_types = argin
-        self._validate_capability_types(command_name, capability_types)
-        self._validate_input_sizes(command_name, argin)
-
-        # Set obsState to 'CONFIGURING'.
-        self._obs_state = ObsState.CONFIGURING
-
-        # Perform the configuration.
-        for capability_instances, capability_type in zip(
-                capabilities_instances, capability_types):
-            self._configured_capabilities[capability_type] += capability_instances
-
-        # Change the obsState to 'READY'.
-        self._obs_state = ObsState.READY
-        # PROTECTED REGION END #    //  SKASubarray.ConfigureCapability
-
-    @command(dtype_in='str', doc_in="Capability type",)
-    @DebugIt()
-    def DeconfigureAllCapabilities(self, argin):
-        # PROTECTED REGION ID(SKASubarray.DeconfigureAllCapabilities) ENABLED START #i
-        """Deconfigure all instances of the given Capability type. If the capability
-        type does not exist an exception will be raised, otherwise it sets the
-        configured instances for that capability type to zero."""
-        self._validate_capability_types('DeconfigureAllCapabilities', [argin])
-        self._configured_capabilities[argin] = 0
-        # PROTECTED REGION END #    //  SKASubarray.DeconfigureAllCapabilities
-
-    @command(dtype_in='DevVarLongStringArray', doc_in="[Number of instances to remove][Capability types]",)
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("On")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_Off_allowed(self):
+        """
+        Check if command `Off` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("Off")
+        return command.check_allowed()
+
+    @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
-    def DeconfigureCapability(self, argin):
-        # PROTECTED REGION ID(SKASubarray.DeconfigureCapability) ENABLED START #
-        """Deconfigures a given number of instances for each capability.
-        If the capability exists, it decrements the configured instances by the
-        number of instances requested, otherwise an exceptioin will be raised.
-        Note: The two lists arguments must be of equal length or an exception
-        will be raised"""
-        command_name = 'DeconfigureCapability'
-        capabilities_instances, capability_types = argin
-
-        self._validate_capability_types(command_name, capability_types)
-        self._validate_input_sizes(command_name, argin)
-
-
-        # Perform the deconfiguration
-        for capability_instances, capability_type in zip(
-                capabilities_instances, capability_types):
-            if self._configured_capabilities[capability_type] < int(capability_instances):
-                self._configured_capabilities[capability_type] = 0
-            else:
-                self._configured_capabilities[capability_type] -= (
-                    int(capability_instances))
-        # PROTECTED REGION END #    //  SKASubarray.DeconfigureCapability
+    def Off(self):
+        """
+        Turn the subarray off
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("Off")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_AssignResources_allowed(self):
+        """
+        Check if command `AssignResources` is allowed in the current
+        device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("AssignResources")
+        return command.check_allowed()
 
-    @command(dtype_in=('str',), doc_in="List of Resources to add to subarray.", dtype_out=('str',),
-             doc_out="A list of Resources added to the subarray.",)
+    @command(
+        dtype_in="DevString",
+        doc_in="JSON-encoded string with the resources to add to subarray",
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
     def AssignResources(self, argin):
-        # PROTECTED REGION ID(SKASubarray.AssignResources) ENABLED START #
-        """Assign resources to a Subarray"""
-        argout = []
-        resources = self._assigned_resources[:]
-        for resource in argin:
-            if resource not in resources:
-                self._assigned_resources.append(resource)
-            argout.append(resource)
-
-        self.set_state(DevState.ON)
-        return argout
-
-    @command(dtype_in=('str',), doc_in="List of resources to remove from the subarray.", dtype_out=('str',),
-             doc_out="List of resources removed from the subarray.",)
+        """
+        Assign resources to this subarray
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
+        :param argin: the resources to be assigned
+        :type argin: list of str
+        """
+        command = self.get_command_object("AssignResources")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
+
+    def is_ReleaseResources_allowed(self):
+        """
+        Check if command `ReleaseResources` is allowed in the current
+        device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("ReleaseResources")
+        return command.check_allowed()
+
+    @command(
+        dtype_in="DevString",
+        doc_in="JSON-encoded string with the resources to remove from the subarray",
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
     def ReleaseResources(self, argin):
-        # PROTECTED REGION ID(SKASubarray.ReleaseResources) ENABLED START #
-        """Delta removal of assigned resources."""
-        argout = []
-        # Release resources...
-        resources = self._assigned_resources[:]
-        for resource in argin:
-            if resource in resources:
-                self._assigned_resources.remove(resource)
-            argout.append(resource)
-        return argout
-        # PROTECTED REGION END #    //  SKASubarray.ReleaseResources
+        """
+        Delta removal of assigned resources.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
+        :param argin: the resources to be released
+        :type argin: list of str
+        """
+        command = self.get_command_object("ReleaseResources")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
+
+    def is_ReleaseAllResources_allowed(self):
+        """
+        Check if command `ReleaseAllResources` is allowed in the current
+        device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("ReleaseAllResources")
+        return command.check_allowed()
 
     @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
-    def EndSB(self):
-        # PROTECTED REGION ID(SKASubarray.EndSB) ENABLED START #
-        """Change obsState to IDLE."""
-        # PROTECTED REGION END #    //  SKASubarray.EndSB
+    def ReleaseAllResources(self):
+        """
+        Remove all resources to tear down to an empty subarray.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
+        :return: list of resources removed
+        :rtype: list of string
+        """
+        command = self.get_command_object("ReleaseAllResources")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_Configure_allowed(self):
+        """
+        Check if command `Configure` is allowed in the current
+        device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("Configure")
+        return command.check_allowed()
 
     @command(
+        dtype_in="DevString",
+        doc_in="JSON-encoded string with the scan configuration",
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
+    @DebugIt()
+    def Configure(self, argin):
+        """
+        Configures the capabilities of this subarray
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
+        :param argin: configuration specification
+        :type argin: string
+        """
+        command = self.get_command_object("Configure")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
+
+    def is_Scan_allowed(self):
+        """
+        Check if command `Scan` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("Scan")
+        return command.check_allowed()
+
+    @command(
+        dtype_in="DevString",
+        doc_in="JSON-encoded string with the per-scan configuration",
+        dtype_out="DevVarLongStringArray",
+        doc_out="(ReturnType, 'informational message')",
+    )
+    @DebugIt()
+    def Scan(self, argin):
+        """
+        Start scanning
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+
+        :param argin: Information about the scan
+        :type argin: Array of str
+        """
+        command = self.get_command_object("Scan")
+        (return_code, message) = command(argin)
+        return [[return_code], [message]]
+
+    def is_EndScan_allowed(self):
+        """
+        Check if command `EndScan` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("EndScan")
+        return command.check_allowed()
+
+    @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
     def EndScan(self):
-        # PROTECTED REGION ID(SKASubarray.EndScan) ENABLED START #
-        """Ends the scan"""
-        # PROTECTED REGION END #    //  SKASubarray.EndScan
+        """
+        End the scan
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("EndScan")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_End_allowed(self):
+        """
+        Check if command `End` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("End")
+        return command.check_allowed()
 
     @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
-    def Pause(self):
-        # PROTECTED REGION ID(SKASubarray.Pause) ENABLED START #
-        """Pauses the scan"""
-        # PROTECTED REGION END #    //  SKASubarray.Pause
+    def End(self):
+        # PROTECTED REGION ID(SKASubarray.EndSB) ENABLED START #
+        """
+        End the scan block.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("End")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_Abort_allowed(self):
+        """
+        Check if command `Abort` is allowed in the current device state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("Abort")
+        return command.check_allowed()
 
-    @command(dtype_out=('str',), doc_out="List of resources removed from the subarray.",)
+    @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
-    def ReleaseAllResources(self):
-        # PROTECTED REGION ID(SKASubarray.ReleaseAllResources) ENABLED START #
-        """Remove all resources to tear down to an empty subarray."""
-        resources = self._assigned_resources[:]
-        released_resources = self.ReleaseResources(resources)
-        return released_resources
-        # PROTECTED REGION END #    //  SKASubarray.ReleaseAllResources
+    def Abort(self):
+        """
+        Abort any long-running command such as ``Configure()`` or
+        ``Scan()``.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("Abort")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_ObsReset_allowed(self):
+        """
+        Check if command `ObsReset` is allowed in the current device
+        state.
+
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("ObsReset")
+        return command.check_allowed()
 
     @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
     )
     @DebugIt()
-    def Resume(self):
-        # PROTECTED REGION ID(SKASubarray.Resume) ENABLED START #
-        """Resumes the scan"""
-        # PROTECTED REGION END #    //  SKASubarray.Resume
+    def ObsReset(self):
+        """
+        Reset the current observation process.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("ObsReset")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
+    def is_Restart_allowed(self):
+        """
+        Check if command `Restart` is allowed in the current device
+        state.
 
-    @command(dtype_in=('str',),)
+        :raises ``tango.DevFailed``: if the command is not allowed
+        :return: ``True`` if the command is allowed
+        :rtype: boolean
+        """
+        command = self.get_command_object("Restart")
+        return command.check_allowed()
+
+    @command(
+        dtype_out='DevVarLongStringArray',
+        doc_out="(ReturnType, 'informational message')",
+    )
     @DebugIt()
-    def Scan(self, argin):
-        # PROTECTED REGION ID(SKASubarray.Scan) ENABLED START #
-        """Starts the scan"""
-        # PROTECTED REGION END #    //  SKASubarray.Scan
+    def Restart(self):
+        """
+        Restart the subarray. That is, deconfigure and release
+        all resources.
+
+        To modify behaviour for this command, modify the do() method of
+        the command class.
+        """
+        command = self.get_command_object("Restart")
+        (return_code, message) = command()
+        return [[return_code], [message]]
+
 
 # ----------
 # Run server
 # ----------
-
-
 def main(args=None, **kwargs):
     # PROTECTED REGION ID(SKASubarray.main) ENABLED START #
     """
@@ -438,5 +1421,6 @@ def main(args=None, **kwargs):
     return run((SKASubarray,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKASubarray.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/tel_state_device.py b/src/ska/base/tel_state_device.py
index d9b3b0fd2cb2a60407d95b6d1870c08a23905771..c040e37e1173d62620665c0f2cad51377418d3f5 100644
--- a/src/ska/base/tel_state_device.py
+++ b/src/ska/base/tel_state_device.py
@@ -9,15 +9,12 @@
 A generic base device for Telescope State for SKA.
 """
 # PROTECTED REGION ID(SKATelState.additionnal_import) ENABLED START #
-# Standard import
-import os
-import sys
-
 # Tango imports
+# from tango import DebugIt
 from tango.server import run, device_property
 
 # SKA specific imports
-from . import SKABaseDevice, release
+from ska.base import SKABaseDevice
 # PROTECTED REGION END #    //  SKATelState.additionnal_imports
 
 __all__ = ["SKATelState", "main"]
@@ -38,7 +35,6 @@ class SKATelState(SKABaseDevice):
         dtype='str',
     )
 
-
     # ----------
     # Attributes
     # ----------
@@ -46,18 +42,6 @@ class SKATelState(SKABaseDevice):
     # ---------------
     # General methods
     # ---------------
-
-    def init_device(self):
-        """init_device
-        Init device method of SKATelStateDevice
-        """
-        SKABaseDevice.init_device(self)
-        self._build_state = '{}, {}, {}'.format(release.name, release.version,
-                                                release.description)
-        self._version_id = release.version
-        # PROTECTED REGION ID(SKATelState.init_device) ENABLED START #
-        # PROTECTED REGION END #    //  SKATelState.init_device
-
     def always_executed_hook(self):
         # PROTECTED REGION ID(SKATelState.always_executed_hook) ENABLED START #
         pass
@@ -72,7 +56,6 @@ class SKATelState(SKABaseDevice):
     # Attributes methods
     # ------------------
 
-
     # --------
     # Commands
     # --------
@@ -96,5 +79,6 @@ def main(args=None, **kwargs):
     return run((SKATelState,), args=args, **kwargs)
     # PROTECTED REGION END #    //  SKATelState.main
 
+
 if __name__ == '__main__':
     main()
diff --git a/src/ska/base/utils.py b/src/ska/base/utils.py
index 7a5b93d1e002a8c92976ac4c343aceffc1ee8727..a1fe8ca0abce482b70152fd1ff21df306e28c44a 100644
--- a/src/ska/base/utils.py
+++ b/src/ska/base/utils.py
@@ -14,7 +14,7 @@ from tango import (DeviceProxy, DbDatum, DbDevInfo, AttrQuality,
                    AttrWriteType, Except, ErrSeverity)
 from tango import DevState
 from contextlib import contextmanager
-from .faults import GroupDefinitionsError, SKABaseError
+from ska.base.faults import GroupDefinitionsError, SKABaseError
 
 int_types = {tango._tango.CmdArgType.DevUShort,
              tango._tango.CmdArgType.DevLong,
@@ -222,7 +222,7 @@ def get_dp_attribute(device_proxy, attribute, with_value=False, with_context=Fal
             ts = datetime.fromtimestamp(attr_value.time.tv_sec)
             ts.replace(microsecond=attr_value.time.tv_usec)
             attr_dict['timestamp'] = ts.isoformat()
-        except:
+        except Exception:
             # TBD - decide what to do - add log?
             pass
 
@@ -358,7 +358,6 @@ def get_groups_from_json(json_definitions):
         # the exc_info is included for detailed traceback
         ska_error = SKABaseError(exc)
         raise GroupDefinitionsError(ska_error).with_traceback(sys.exc_info()[2])
-        #raise GroupDefinitionsError(exc), None, sys.exc_info()[2]
 
 
 def _validate_group(definition):
diff --git a/tests/conftest.py b/tests/conftest.py
index ce97603d5261dec15e1c2444afc6b1661b88c958..da3a018dbc6ea1b04b50b49e2b5a649fa8d45a85 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,6 +6,8 @@ import pytest
 
 from tango.test_context import DeviceTestContext
 
+from ska.base import SKASubarrayStateModel
+
 
 @pytest.fixture(scope="class")
 def tango_context(request):
@@ -27,7 +29,7 @@ def tango_context(request):
             },
 
         'SKASubarray': {
-            'CapabilityTypes': 'BAND1',
+            "CapabilityTypes": ["BAND1", "BAND2"],
             'LoggingTargetsDefault': '',
             'GroupDefinitions': '',
             'SkaLevel': '4',
@@ -61,3 +63,11 @@ def initialize_device(tango_context):
         Context to run a device without a database.
     """
     yield tango_context.device.Init()
+
+
+@pytest.fixture(scope="function")
+def state_model():
+    """
+    Yields an SKASubarrayStateModel.
+    """
+    yield SKASubarrayStateModel()
diff --git a/tests/test_alarm_handler_device.py b/tests/test_alarm_handler_device.py
index e82e28c94bea73a4ec669696848032107d9af129..b97224d71337a9300def21f93fadc8a30f3c1737 100644
--- a/tests/test_alarm_handler_device.py
+++ b/tests/test_alarm_handler_device.py
@@ -8,14 +8,11 @@
 #########################################################################################
 """Contain the tests for the SKAAlarmHandler."""
 
-# Standard imports
-import sys
-import os
-
 # Imports
 import re
 import pytest
 
+
 # PROTECTED REGION ID(SKAAlarmHandler.test_additional_imports) ENABLED START #
 # PROTECTED REGION END #    //  SKAAlarmHandler.test_additional_imports
 # Device test case
@@ -47,7 +44,6 @@ class TestSKAAlarmHandler(object):
         # PROTECTED REGION ID(SKAAlarmHandler.test_properties) ENABLED START #
         # PROTECTED REGION END #    //  SKAAlarmHandler.test_properties
 
-
     # PROTECTED REGION ID(SKAAlarmHandler.test_GetAlarmRule_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKAAlarmHandler.test_GetAlarmRule_decorators
     def test_GetAlarmRule(self, tango_context):
diff --git a/tests/test_base_device.py b/tests/test_base_device.py
index d220092b03409b966a7c9483b888fb67d36bbf71..37d37633ea1971c2a3570d036a5cc2791404a417 100644
--- a/tests/test_base_device.py
+++ b/tests/test_base_device.py
@@ -8,9 +8,6 @@
 #########################################################################################
 """Contain the tests for the SKABASE."""
 
-# Standard imports
-import sys
-import os
 import re
 import pytest
 
@@ -323,7 +320,9 @@ class TestLoggingUtils:
 @pytest.mark.usefixtures("tango_context", "initialize_device")
 # PROTECTED REGION END #    //  SKABaseDevice.test_SKABaseDevice_decorators
 class TestSKABaseDevice(object):
-    """Test case for packet generation."""
+    """
+    Test cases for SKABaseDevice.
+    """
 
     properties = {
         'SkaLevel': '4',
@@ -358,7 +357,7 @@ class TestSKABaseDevice(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKABaseDevice.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.UNKNOWN
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKABaseDevice.test_State
 
     # PROTECTED REGION ID(SKABaseDevice.test_Status_decorators) ENABLED START #
@@ -366,7 +365,7 @@ class TestSKABaseDevice(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKABaseDevice.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in UNKNOWN state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKABaseDevice.test_Status
 
     # PROTECTED REGION ID(SKABaseDevice.test_GetVersionInfo_decorators) ENABLED START #
@@ -386,7 +385,11 @@ class TestSKABaseDevice(object):
     def test_Reset(self, tango_context):
         """Test for Reset"""
         # PROTECTED REGION ID(SKABaseDevice.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
+        # This is a pretty weak test, but Reset() is only allowed from
+        # device state FAULT, and we have no way of putting into FAULT
+        # state through its interface.
+        with pytest.raises(DevFailed):
+            tango_context.device.Reset()
         # PROTECTED REGION END #    //  SKABaseDevice.test_Reset
 
     # PROTECTED REGION ID(SKABaseDevice.test_buildState_decorators) ENABLED START #
@@ -433,7 +436,9 @@ class TestSKABaseDevice(object):
         # tango logging target must be enabled by default
         assert tango_context.device.loggingTargets == ("tango::logger", )
 
-        with mock.patch("ska.base.base_device.LoggingUtils.create_logging_handler") as mocked_creator:
+        with mock.patch(
+            "ska.base.base_device.LoggingUtils.create_logging_handler"
+        ) as mocked_creator:
 
             def null_creator(target, tango_logger):
                 handler = logging.NullHandler()
@@ -490,7 +495,7 @@ class TestSKABaseDevice(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKABaseDevice.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
         # PROTECTED REGION END #    //  SKABaseDevice.test_adminMode
 
     # PROTECTED REGION ID(SKABaseDevice.test_controlMode_decorators) ENABLED START #
diff --git a/tests/test_capability_device.py b/tests/test_capability_device.py
index 1f75b9beab08dfe04be1ddabe2b8581cd2adac78..a837f5532fa0edd8997c3b4fbd2c0a1658609ab3 100644
--- a/tests/test_capability_device.py
+++ b/tests/test_capability_device.py
@@ -7,15 +7,10 @@
 #
 #########################################################################################
 """Contain the tests for the SKACapability."""
-
-# Standard imports
-import sys
-import os
-
-# Imports
 import re
 import pytest
 
+
 # PROTECTED REGION ID(SKACapability.test_additional_imports) ENABLED START #
 # PROTECTED REGION END #    //  SKACapability.test_additional_imports
 # Device test case
diff --git a/tests/test_logger_device.py b/tests/test_logger_device.py
index cf794703480598836fed7a1a5708fdd3607fd8c6..d60e5038851971a4dcb15fe717f70a7615284cf8 100644
--- a/tests/test_logger_device.py
+++ b/tests/test_logger_device.py
@@ -8,15 +8,9 @@
 #########################################################################################
 """Contain the tests for the SKALogger."""
 
-# Standard imports
-import sys
-import os
-
-# Imports
 import re
 import pytest
 from tango import DevState, DeviceProxy
-
 import tango
 
 # PROTECTED REGION ID(SKALogger.test_additional_imports) ENABLED START #
@@ -24,7 +18,8 @@ from ska.base.control_model import (
     AdminMode, ControlMode, HealthState, LoggingLevel, SimulationMode, TestMode
 )
 # PROTECTED REGION END #    //  SKALogger.test_additional_imports
-# Device test case
+
+
 # PROTECTED REGION ID(SKALogger.test_SKALogger_decorators) ENABLED START #
 @pytest.mark.usefixtures("tango_context", "initialize_device")
 # PROTECTED REGION END #    //  SKALogger.test_SKALogger_decorators
@@ -53,7 +48,7 @@ class TestSKALogger(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKALogger.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.UNKNOWN
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKALogger.test_State
 
     # PROTECTED REGION ID(SKALogger.test_Status_decorators) ENABLED START #
@@ -61,7 +56,7 @@ class TestSKALogger(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKALogger.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in UNKNOWN state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKALogger.test_Status
 
     # PROTECTED REGION ID(SKALogger.test_SetLoggingLevel_decorators) ENABLED START #
@@ -100,14 +95,6 @@ class TestSKALogger(object):
         assert (re.match(versionPattern, versionInfo[0])) is not None
         # PROTECTED REGION END #    //  SKALogger.test_GetVersionInfo
 
-    # PROTECTED REGION ID(SKALogger.test_Reset_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKALogger.test_Reset_decorators
-    def test_Reset(self, tango_context):
-        """Test for Reset"""
-        # PROTECTED REGION ID(SKALogger.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
-        # PROTECTED REGION END #    //  SKALogger.test_Reset
-
     # PROTECTED REGION ID(SKALogger.test_buildState_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKALogger.test_buildState_decorators
     def test_buildState(self, tango_context):
@@ -149,7 +136,7 @@ class TestSKALogger(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKALogger.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
         # PROTECTED REGION END #    //  SKALogger.test_adminMode
 
     # PROTECTED REGION ID(SKALogger.test_controlMode_decorators) ENABLED START #
diff --git a/tests/test_master_device.py b/tests/test_master_device.py
index a3c4584f291539d661e8fac7ad40a86878588ace..c775e4b99c61e463b131dc8ee99dc0ea16e234f2 100644
--- a/tests/test_master_device.py
+++ b/tests/test_master_device.py
@@ -8,11 +8,6 @@
 #########################################################################################
 """Contain the tests for the SKAMaster."""
 
-# Standard imports
-import sys
-import os
-
-# Imports
 import re
 import pytest
 from tango import DevState
@@ -20,7 +15,8 @@ from tango import DevState
 # PROTECTED REGION ID(SKAMaster.test_additional_imports) ENABLED START #
 from ska.base.control_model import AdminMode, ControlMode, HealthState, SimulationMode, TestMode
 # PROTECTED REGION END #    //  SKAMaster.test_additional_imports
-# Device test case
+
+
 # PROTECTED REGION ID(SKAMaster.test_SKAMaster_decorators) ENABLED START #
 @pytest.mark.usefixtures("tango_context")
 # PROTECTED REGION END #    //  SKAMaster.test_SKAMaster_decorators
@@ -56,7 +52,7 @@ class TestSKAMaster(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKAMaster.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.UNKNOWN
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKAMaster.test_State
 
     # PROTECTED REGION ID(SKAMaster.test_Status_decorators) ENABLED START #
@@ -64,7 +60,7 @@ class TestSKAMaster(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKAMaster.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in UNKNOWN state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKAMaster.test_Status
 
     # PROTECTED REGION ID(SKAMaster.test_GetVersionInfo_decorators) ENABLED START #
@@ -76,7 +72,7 @@ class TestSKAMaster(object):
             r'SKAMaster, lmcbaseclasses, [0-9].[0-9].[0-9], '
             r'A set of generic base devices for SKA Telescope.')
         versionInfo = tango_context.device.GetVersionInfo()
-        assert (re.match(versionPattern, versionInfo[0])) != None
+        assert (re.match(versionPattern, versionInfo[0])) is not None
         # PROTECTED REGION END #    //  SKAMaster.test_GetVersionInfo
 
     # PROTECTED REGION ID(SKAMaster.test_isCapabilityAchievable_failure_decorators) ENABLED START #
@@ -95,15 +91,6 @@ class TestSKAMaster(object):
         assert tango_context.device.isCapabilityAchievable([[1], ['BAND1']]) is True
         # PROTECTED REGION END #    //  SKAMaster.test_isCapabilityAchievable_success
 
-    # PROTECTED REGION ID(SKAMaster.test_Reset_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKAMaster.test_Reset_decorators
-    def test_Reset(self, tango_context):
-        """Test for Reset"""
-        # PROTECTED REGION ID(SKAMaster.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
-        # PROTECTED REGION END #    //  SKAMaster.test_Reset
-
-
     # PROTECTED REGION ID(SKAMaster.test_elementLoggerAddress_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKAMaster.test_elementLoggerAddress_decorators
     def test_elementLoggerAddress(self, tango_context):
@@ -144,7 +131,9 @@ class TestSKAMaster(object):
         buildPattern = re.compile(
             r'lmcbaseclasses, [0-9].[0-9].[0-9], '
             r'A set of generic base devices for SKA Telescope')
-        assert (re.match(buildPattern, tango_context.device.buildState)) != None
+        assert (
+            re.match(buildPattern, tango_context.device.buildState)
+        ) is not None
         # PROTECTED REGION END #    //  SKAMaster.test_buildState
 
     # PROTECTED REGION ID(SKAMaster.test_versionId_decorators) ENABLED START #
@@ -153,7 +142,9 @@ class TestSKAMaster(object):
         """Test for versionId"""
         # PROTECTED REGION ID(SKAMaster.test_versionId) ENABLED START #
         versionIdPattern = re.compile(r'[0-9].[0-9].[0-9]')
-        assert (re.match(versionIdPattern, tango_context.device.versionId)) != None
+        assert (
+            re.match(versionIdPattern, tango_context.device.versionId)
+        ) is not None
         # PROTECTED REGION END #    //  SKAMaster.test_versionId
 
     # PROTECTED REGION ID(SKAMaster.test_healthState_decorators) ENABLED START #
@@ -169,7 +160,7 @@ class TestSKAMaster(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKAMaster.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
         # PROTECTED REGION END #    //  SKAMaster.test_adminMode
 
     # PROTECTED REGION ID(SKAMaster.test_controlMode_decorators) ENABLED START #
diff --git a/tests/test_obs_device.py b/tests/test_obs_device.py
index 2b36c09e45bd9fb6ad0f52c900703cea5e5c0f36..7d9d1d187e9bf7ff330b9315b95e8f7a7a2be7be 100644
--- a/tests/test_obs_device.py
+++ b/tests/test_obs_device.py
@@ -8,10 +8,6 @@
 #########################################################################################
 """Contain the tests for the SKAObsDevice."""
 
-# Standard imports
-import sys
-import os
-
 # Imports
 import re
 import pytest
@@ -23,6 +19,7 @@ from ska.base.control_model import (
 )
 # PROTECTED REGION END #    //  SKAObsDevice.test_additional_imports
 
+
 # Device test case
 # PROTECTED REGION ID(SKAObsDevice.test_SKAObsDevice_decorators) ENABLED START #
 @pytest.mark.usefixtures("tango_context", "initialize_device")
@@ -55,7 +52,7 @@ class TestSKAObsDevice(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKAObsDevice.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.UNKNOWN
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKAObsDevice.test_State
 
     # PROTECTED REGION ID(SKAObsDevice.test_Status_decorators) ENABLED START #
@@ -63,7 +60,7 @@ class TestSKAObsDevice(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKAObsDevice.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in UNKNOWN state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKAObsDevice.test_Status
 
     # PROTECTED REGION ID(SKAObsDevice.test_GetVersionInfo_decorators) ENABLED START #
@@ -78,20 +75,12 @@ class TestSKAObsDevice(object):
         assert (re.match(versionPattern, versionInfo[0])) is not None
         # PROTECTED REGION END #    //  SKAObsDevice.test_GetVersionInfo
 
-    # PROTECTED REGION ID(SKAObsDevice.test_Reset_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKAObsDevice.test_Reset_decorators
-    def test_Reset(self, tango_context):
-        """Test for Reset"""
-        # PROTECTED REGION ID(SKAObsDevice.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
-        # PROTECTED REGION END #    //  SKAObsDevice.test_Reset
-
     # PROTECTED REGION ID(SKAObsDevice.test_obsState_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKAObsDevice.test_obsState_decorators
     def test_obsState(self, tango_context):
         """Test for obsState"""
         # PROTECTED REGION ID(SKAObsDevice.test_obsState) ENABLED START #
-        assert tango_context.device.obsState == ObsState.IDLE
+        assert tango_context.device.obsState == ObsState.EMPTY
         # PROTECTED REGION END #    //  SKAObsDevice.test_obsState
 
     # PROTECTED REGION ID(SKAObsDevice.test_obsMode_decorators) ENABLED START #
@@ -126,7 +115,7 @@ class TestSKAObsDevice(object):
         buildPattern = re.compile(
             r'lmcbaseclasses, [0-9].[0-9].[0-9], '
             r'A set of generic base devices for SKA Telescope')
-        assert (re.match(buildPattern, tango_context.device.buildState)) != None
+        assert (re.match(buildPattern, tango_context.device.buildState)) is not None
         # PROTECTED REGION END #    //  SKAObsDevice.test_buildState
 
     # PROTECTED REGION ID(SKAObsDevice.test_versionId_decorators) ENABLED START #
@@ -135,7 +124,7 @@ class TestSKAObsDevice(object):
         """Test for versionId"""
         # PROTECTED REGION ID(SKAObsDevice.test_versionId) ENABLED START #
         versionIdPattern = re.compile(r'[0-9].[0-9].[0-9]')
-        assert (re.match(versionIdPattern, tango_context.device.versionId)) != None
+        assert (re.match(versionIdPattern, tango_context.device.versionId)) is not None
         # PROTECTED REGION END #    //  SKAObsDevice.test_versionId
 
     # PROTECTED REGION ID(SKAObsDevice.test_healthState_decorators) ENABLED START #
@@ -151,7 +140,7 @@ class TestSKAObsDevice(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKAObsDevice.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
         # PROTECTED REGION END #    //  SKAObsDevice.test_adminMode
 
     # PROTECTED REGION ID(SKAObsDevice.test_controlMode_decorators) ENABLED START #
diff --git a/tests/test_subarray_device.py b/tests/test_subarray_device.py
index 9376dabd29ee1e5af86917ef69cf41e16eacd05b..9058e73db2f2acd75e9fd918207522e231e7b296 100644
--- a/tests/test_subarray_device.py
+++ b/tests/test_subarray_device.py
@@ -8,24 +8,23 @@
 #########################################################################################
 """Contain the tests for the SKASubarray."""
 
-# Standard imports
-import sys
-import os
-
-# Imports
+import itertools
 import re
 import pytest
-from tango import DevState, DevSource
+
+from tango import DevState, DevSource, DevFailed
 
 # PROTECTED REGION ID(SKASubarray.test_additional_imports) ENABLED START #
+from ska.base import SKASubarray, SKASubarrayResourceManager
+from ska.base.commands import ResultCode
 from ska.base.control_model import (
     AdminMode, ControlMode, HealthState, ObsMode, ObsState, SimulationMode, TestMode
 )
+from ska.base.faults import CommandError
 # PROTECTED REGION END #    //  SKASubarray.test_additional_imports
-# Device test case
-# PROTECTED REGION ID(SKASubarray.test_SKASubarray_decorators) ENABLED START #
+
+
 @pytest.mark.usefixtures("tango_context", "initialize_device")
-# PROTECTED REGION END #    //  SKASubarray.test_SKASubarray_decorators
 class TestSKASubarray(object):
     """Test case for packet generation."""
 
@@ -56,49 +55,29 @@ class TestSKASubarray(object):
     def test_Abort(self, tango_context):
         """Test for Abort"""
         # PROTECTED REGION ID(SKASubarray.test_Abort) ENABLED START #
-        assert tango_context.device.Abort() is None
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        assert tango_context.device.Abort() == [
+            [ResultCode.OK], ["Abort command completed OK"]
+        ]
         # PROTECTED REGION END #    //  SKASubarray.test_Abort
 
-    # PROTECTED REGION ID(SKASubarray.test_ConfigureCapability_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKASubarray.test_ConfigureCapability_decorators
-    def test_ConfigureCapability(self, tango_context):
-        """Test for ConfigureCapability"""
-        # PROTECTED REGION ID(SKASubarray.test_ConfigureCapability) ENABLED START #
-        tango_context.device.adminMode = AdminMode.ONLINE
-        tango_context.device.AssignResources(["BAND1"])
-        tango_context.device.ConfigureCapability([[2], ["BAND1"]])
-        # The obsState attribute is changed by ConfigureCapability, but
+    # PROTECTED REGION ID(SKASubarray.test_Configure_decorators) ENABLED START #
+    # PROTECTED REGION END #    //  SKASubarray.test_Configure_decorators
+    def test_Configure(self, tango_context):
+        """Test for Configure"""
+        # PROTECTED REGION ID(SKASubarray.test_Configure) ENABLED START #
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        # The obsState attribute is changed by Configure, but
         # as it is a polled attribute the value in the cache may be stale,
         # so change source to ensure we read directly from the device
         tango_context.device.set_source(DevSource.DEV)
         assert tango_context.device.obsState == ObsState.READY
-        assert tango_context.device.configuredCapabilities == ("BAND1:2", )
-        # PROTECTED REGION END #    //  SKASubarray.test_ConfigureCapability
-
-    # PROTECTED REGION ID(SKASubarray.test_DeconfigureAllCapabilities_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKASubarray.test_DeconfigureAllCapabilities_decorators
-    def test_DeconfigureAllCapabilities(self, tango_context):
-        """Test for DeconfigureAllCapabilities"""
-        # PROTECTED REGION ID(SKASubarray.test_DeconfigureAllCapabilities) ENABLED START #
-        tango_context.device.adminMode = AdminMode.ONLINE
-        tango_context.device.AssignResources(["BAND1"])
-        tango_context.device.ConfigureCapability([[3], ["BAND1"]])
-        tango_context.device.DeconfigureAllCapabilities("BAND1")
-        assert tango_context.device.configuredCapabilities == ("BAND1:0", )
-        # PROTECTED REGION END #    //  SKASubarray.test_DeconfigureAllCapabilities
-
-    # TODO: Fix the test case.
-    # PROTECTED REGION ID(SKASubarray.test_DeconfigureCapability_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKASubarray.test_DeconfigureCapability_decorators
-    def test_DeconfigureCapability(self, tango_context):
-        """Test for DeconfigureCapability"""
-        # PROTECTED REGION ID(SKASubarray.test_DeconfigureCapability) ENABLED START #
-        tango_context.device.adminMode = AdminMode.ONLINE
-        tango_context.device.AssignResources(["BAND1"])
-        tango_context.device.ConfigureCapability([[1], ["BAND1"]])
-        tango_context.device.DeconfigureCapability([[1], ["BAND1"]])
-        assert tango_context.device.configuredCapabilities == ("BAND1:0", )
-        # PROTECTED REGION END #    //  SKASubarray.test_DeconfigureCapability
+        assert tango_context.device.configuredCapabilities == ("BAND1:2", "BAND2:0")
+        # PROTECTED REGION END #    //  SKASubarray.test_Configure
 
     # PROTECTED REGION ID(SKASubarray.test_GetVersionInfo_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKASubarray.test_GetVersionInfo_decorators
@@ -117,7 +96,7 @@ class TestSKASubarray(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKASubarray.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in DISABLE state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKASubarray.test_Status
 
     # PROTECTED REGION ID(SKASubarray.test_State_decorators) ENABLED START #
@@ -125,7 +104,7 @@ class TestSKASubarray(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKASubarray.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.DISABLE
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKASubarray.test_State
 
     # PROTECTED REGION ID(SKASubarray.test_AssignResources_decorators) ENABLED START #
@@ -133,18 +112,26 @@ class TestSKASubarray(object):
     def test_AssignResources(self, tango_context):
         """Test for AssignResources"""
         # PROTECTED REGION ID(SKASubarray.test_AssignResources) ENABLED START #
-        tango_context.device.AssignResources(['BAND1', 'BAND2'])
-        assert tango_context.device.State() == DevState.ON and \
-               tango_context.device.assignedResources == ('BAND1', 'BAND2')
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1", "BAND2"]}')
+        assert tango_context.device.State() == DevState.ON
+        assert tango_context.device.assignedResources == ('BAND1', 'BAND2')
         tango_context.device.ReleaseAllResources()
+        with pytest.raises(DevFailed):
+            tango_context.device.AssignResources('Invalid JSON')
         # PROTECTED REGION END #    //  SKASubarray.test_AssignResources
 
     # PROTECTED REGION ID(SKASubarray.test_EndSB_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKASubarray.test_EndSB_decorators
-    def test_EndSB(self, tango_context):
+    def test_End(self, tango_context):
         """Test for EndSB"""
         # PROTECTED REGION ID(SKASubarray.test_EndSB) ENABLED START #
-        assert tango_context.device.EndSB() is None
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        assert tango_context.device.End() == [
+            [ResultCode.OK], ["End command completed OK"]
+        ]
         # PROTECTED REGION END #    //  SKASubarray.test_EndSB
 
     # PROTECTED REGION ID(SKASubarray.test_EndScan_decorators) ENABLED START #
@@ -152,24 +139,23 @@ class TestSKASubarray(object):
     def test_EndScan(self, tango_context):
         """Test for EndScan"""
         # PROTECTED REGION ID(SKASubarray.test_EndScan) ENABLED START #
-        assert tango_context.device.EndScan() is None
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        tango_context.device.Scan('{"id": 123}')
+        assert tango_context.device.EndScan() == [
+            [ResultCode.OK], ["EndScan command completed OK"]
+        ]
         # PROTECTED REGION END #    //  SKASubarray.test_EndScan
 
-    # PROTECTED REGION ID(SKASubarray.test_Pause_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKASubarray.test_Pause_decorators
-    def test_Pause(self, tango_context):
-        """Test for Pause"""
-        # PROTECTED REGION ID(SKASubarray.test_Pause) ENABLED START #
-        assert tango_context.device.Pause() is None
-        # PROTECTED REGION END #    //  SKASubarray.test_Pause
-
     # PROTECTED REGION ID(SKASubarray.test_ReleaseAllResources_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKASubarray.test_ReleaseAllResources_decorators
     def test_ReleaseAllResources(self, tango_context):
         """Test for ReleaseAllResources"""
         # PROTECTED REGION ID(SKASubarray.test_ReleaseAllResources) ENABLED START #
         # assert tango_context.device.ReleaseAllResources() == [""]
-        tango_context.device.AssignResources(['BAND1', 'BAND2'])
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1", "BAND2"]}')
         tango_context.device.ReleaseAllResources()
         assert tango_context.device.assignedResources is None
         # PROTECTED REGION END #    //  SKASubarray.test_ReleaseAllResources
@@ -179,9 +165,9 @@ class TestSKASubarray(object):
     def test_ReleaseResources(self, tango_context):
         """Test for ReleaseResources"""
         # PROTECTED REGION ID(SKASubarray.test_ReleaseResources) ENABLED START #
-        # assert tango_context.device.ReleaseResources([""]) == [""]
-        tango_context.device.AssignResources(['BAND1', 'BAND2'])
-        tango_context.device.ReleaseResources(['BAND1'])
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1", "BAND2"]}')
+        tango_context.device.ReleaseResources('{"example": ["BAND1"]}')
         assert tango_context.device.State() == DevState.ON and\
                tango_context.device.assignedResources == ('BAND2',)
         tango_context.device.ReleaseAllResources()
@@ -189,26 +175,32 @@ class TestSKASubarray(object):
 
     # PROTECTED REGION ID(SKASubarray.test_Reset_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKASubarray.test_Reset_decorators
-    def test_Reset(self, tango_context):
+    def test_ObsReset(self, tango_context):
         """Test for Reset"""
         # PROTECTED REGION ID(SKASubarray.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        tango_context.device.Abort()
+        assert tango_context.device.ObsReset() == [
+            [ResultCode.OK], ["ObsReset command completed OK"]
+        ]
         # PROTECTED REGION END #    //  SKASubarray.test_Reset
 
-    # PROTECTED REGION ID(SKASubarray.test_Resume_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKASubarray.test_Resume_decorators
-    def test_Resume(self, tango_context):
-        """Test for Resume"""
-        # PROTECTED REGION ID(SKASubarray.test_Resume) ENABLED START #
-        assert tango_context.device.Resume() is None
-        # PROTECTED REGION END #    //  SKASubarray.test_Resume
-
     # PROTECTED REGION ID(SKASubarray.test_Scan_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKASubarray.test_Scan_decorators
     def test_Scan(self, tango_context):
         """Test for Scan"""
         # PROTECTED REGION ID(SKASubarray.test_Scan) ENABLED START #
-        assert tango_context.device.Scan([""]) is None
+        tango_context.device.On()
+        tango_context.device.AssignResources('{"example": ["BAND1"]}')
+        tango_context.device.Configure('{"BAND1": 2}')
+        assert tango_context.device.Scan('{"id": 123}') == [
+            [ResultCode.STARTED], ["Scan command STARTED - config {'id': 123}"]
+        ]
+        tango_context.device.EndScan()
+        with pytest.raises(DevFailed):
+            tango_context.device.Scan('Invalid JSON')
         # PROTECTED REGION END #    //  SKASubarray.test_Scan
 
     # PROTECTED REGION ID(SKASubarray.test_activationTime_decorators) ENABLED START #
@@ -224,7 +216,11 @@ class TestSKASubarray(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKASubarray.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
+        assert tango_context.device.state() == DevState.OFF
+        tango_context.device.adminMode = AdminMode.OFFLINE
+        assert tango_context.device.adminMode == AdminMode.OFFLINE
+        assert tango_context.device.state() == DevState.DISABLE
         # PROTECTED REGION END #    //  SKASubarray.test_adminMode
 
     # PROTECTED REGION ID(SKASubarray.test_buildState_decorators) ENABLED START #
@@ -283,7 +279,7 @@ class TestSKASubarray(object):
     def test_obsState(self, tango_context):
         """Test for obsState"""
         # PROTECTED REGION ID(SKASubarray.test_obsState) ENABLED START #
-        assert tango_context.device.obsState == ObsState.IDLE
+        assert tango_context.device.obsState == ObsState.EMPTY
         # PROTECTED REGION END #    //  SKASubarray.test_obsState
 
     # PROTECTED REGION ID(SKASubarray.test_simulationMode_decorators) ENABLED START #
@@ -316,7 +312,7 @@ class TestSKASubarray(object):
     def test_assignedResources(self, tango_context):
         """Test for assignedResources"""
         # PROTECTED REGION ID(SKASubarray.test_assignedResources) ENABLED START #
-        assert tango_context.device.assignedResources == None
+        assert tango_context.device.assignedResources is None
         # PROTECTED REGION END #    //  SKASubarray.test_assignedResources
 
     # PROTECTED REGION ID(SKASubarray.test_configuredCapabilities_decorators) ENABLED START #
@@ -324,5 +320,281 @@ class TestSKASubarray(object):
     def test_configuredCapabilities(self, tango_context):
         """Test for configuredCapabilities"""
         # PROTECTED REGION ID(SKASubarray.test_configuredCapabilities) ENABLED START #
-        assert tango_context.device.configuredCapabilities == ("BAND1:0", )
+        assert tango_context.device.configuredCapabilities == ("BAND1:0", "BAND2:0")
         # PROTECTED REGION END #    //  SKASubarray.test_configuredCapabilities
+
+    @pytest.mark.parametrize(
+        'state_under_test, action_under_test',
+        itertools.product(
+            [
+                # not testing FAULT or OBSFAULT states because in the current
+                # implementation the interface cannot be used to get the device
+                # into these states
+                "DISABLED", "OFF", "EMPTY", "IDLE", "READY", "SCANNING",
+                "ABORTED",
+            ],
+            [
+                # not testing 'reset' action because in the current
+                # implementation the interface cannot be used to get the device
+                # into a state from which 'reset' is a valid action
+                "notfitted", "offline", "online", "maintenance", "on", "off",
+                "assign", "release", "release (all)", "releaseall",
+                "configure", "scan", "endscan", "end", "abort", "obsreset",
+                "restart"]
+        )
+    )
+    def test_state_machine(self, tango_context,
+                           state_under_test, action_under_test):
+        """
+        Test the subarray state machine: for a given initial state and
+        an action, does execution of that action, from that initial
+        state, yield the expected results? If the action was not allowed
+        from that initial state, does the device raise a DevFailed
+        exception? If the action was allowed, does it result in the
+        correct state transition?
+        """
+
+        states = {
+            "DISABLED":
+                ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.DISABLE, ObsState.EMPTY),
+            "FAULT":  # not tested
+                ([AdminMode.NOT_FITTED, AdminMode.OFFLINE, AdminMode.ONLINE, AdminMode.MAINTENANCE],
+                 DevState.FAULT, ObsState.EMPTY),
+            "OFF":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.OFF, ObsState.EMPTY),
+            "EMPTY":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.EMPTY),
+            "IDLE":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.IDLE),
+            "READY":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.READY),
+            "SCANNING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.SCANNING),
+            "ABORTED":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.ABORTED),
+            "OBSFAULT":  # not tested
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.FAULT),
+        }
+
+        def assert_state(state):
+            (admin_modes, dev_state, obs_state) = states[state]
+            assert tango_context.device.adminMode in admin_modes
+            assert tango_context.device.state() == dev_state
+            assert tango_context.device.obsState == obs_state
+
+        actions = {
+            "notfitted":
+                lambda d: d.write_attribute("adminMode", AdminMode.NOT_FITTED),
+            "offline":
+                lambda d: d.write_attribute("adminMode", AdminMode.OFFLINE),
+            "online":
+                lambda d: d.write_attribute("adminMode", AdminMode.ONLINE),
+            "maintenance":
+                lambda d: d.write_attribute("adminMode", AdminMode.MAINTENANCE),
+            "on":
+                lambda d: d.On(),
+            "off":
+                lambda d: d.Off(),
+            "reset":
+                lambda d: d.Reset(),  # not tested
+            "assign":
+                lambda d: d.AssignResources('{"example": ["BAND1", "BAND2"]}'),
+            "release":
+                lambda d: d.ReleaseResources('{"example": ["BAND1"]}'),
+            "release (all)":
+                lambda d: d.ReleaseResources('{"example": ["BAND1", "BAND2"]}'),
+            "releaseall":
+                lambda d: d.ReleaseAllResources(),
+            "configure":
+                lambda d: d.Configure('{"BAND1": 2, "BAND2": 2}'),
+            "scan":
+                lambda d: d.Scan('{"id": 123}'),
+            "endscan":
+                lambda d: d.EndScan(),
+            "end":
+                lambda d: d.End(),
+            "abort":
+                lambda d: d.Abort(),
+            "obsreset":
+                lambda d: d.ObsReset(),
+            "restart":
+                lambda d: d.Restart(),
+        }
+
+        def perform_action(action):
+            actions[action](tango_context.device)
+
+        transitions = {
+            ("DISABLED", "notfitted"): "DISABLED",
+            ("DISABLED", "offline"): "DISABLED",
+            ("DISABLED", "online"): "OFF",
+            ("DISABLED", "maintenance"): "OFF",
+            ("OFF", "notfitted"): "DISABLED",
+            ("OFF", "offline"): "DISABLED",
+            ("OFF", "online"): "OFF",
+            ("OFF", "maintenance"): "OFF",
+            ("OFF", "on"): "EMPTY",
+            ("EMPTY", "off"): "OFF",
+            ("EMPTY", "assign"): "IDLE",
+            ("IDLE", "assign"): "IDLE",
+            ("IDLE", "release"): "IDLE",
+            ("IDLE", "release (all)"): "EMPTY",
+            ("IDLE", "releaseall"): "EMPTY",
+            ("IDLE", "configure"): "READY",
+            ("IDLE", "abort"): "ABORTED",
+            ("READY", "configure"): "READY",
+            ("READY", "end"): "IDLE",
+            ("READY", "abort"): "ABORTED",
+            ("READY", "scan"): "SCANNING",
+            ("SCANNING", "endscan"): "READY",
+            ("SCANNING", "abort"): "ABORTED",
+            ("ABORTED", "obsreset"): "IDLE",
+            ("ABORTED", "restart"): "EMPTY",
+        }
+
+        setups = {
+            "DISABLED":
+                ['offline'],
+            "OFF":
+                [],
+            "EMPTY":
+                ['on'],
+            "IDLE":
+                ['on', 'assign'],
+            "READY":
+                ['on', 'assign', 'configure'],
+            "SCANNING":
+                ['on', 'assign', 'configure', 'scan'],
+            "ABORTED":
+                ['on', 'assign', 'abort'],
+        }
+
+        # bypass cache for this test because we are testing for a change
+        # in the polled attribute obsState
+        tango_context.device.set_source(DevSource.DEV)
+
+        state = "OFF"  # debugging only
+        assert_state(state)  # debugging only
+
+        # Put the device into the state under test
+        for action in setups[state_under_test]:
+            perform_action(action)
+            state = transitions[state, action]  # debugging only
+            assert_state(state)  # debugging only
+
+        # Check that we are in the state under test
+        assert_state(state_under_test)
+
+        # Test that the action under test does what we expect it to
+        if (state_under_test, action_under_test) in transitions:
+            # Action should succeed
+            perform_action(action_under_test)
+            assert_state(transitions[(state_under_test, action_under_test)])
+        else:
+            # Action should fail and the state should not change
+            with pytest.raises(DevFailed):
+                perform_action(action_under_test)
+            assert_state(state_under_test)
+
+
+@pytest.fixture
+def resource_manager():
+    yield SKASubarrayResourceManager()
+
+
+class TestSKASubarrayResourceManager:
+    def test_ResourceManager_assign(self, resource_manager):
+        # create a resource manager and check that it is empty
+        assert not len(resource_manager)
+        assert resource_manager.get() == set()
+
+        resource_manager.assign('{"example": ["A"]}')
+        assert len(resource_manager) == 1
+        assert resource_manager.get() == set(["A"])
+
+        resource_manager.assign('{"example": ["A"]}')
+        assert len(resource_manager) == 1
+        assert resource_manager.get() == set(["A"])
+
+        resource_manager.assign('{"example": ["A", "B"]}')
+        assert len(resource_manager) == 2
+        assert resource_manager.get() == set(["A", "B"])
+
+        resource_manager.assign('{"example": ["A"]}')
+        assert len(resource_manager) == 2
+        assert resource_manager.get() == set(["A", "B"])
+
+        resource_manager.assign('{"example": ["A", "C"]}')
+        assert len(resource_manager) == 3
+        assert resource_manager.get() == set(["A", "B", "C"])
+
+        resource_manager.assign('{"example": ["D"]}')
+        assert len(resource_manager) == 4
+        assert resource_manager.get() == set(["A", "B", "C", "D"])
+
+    def test_ResourceManager_release(self, resource_manager):
+        resource_manager = SKASubarrayResourceManager()
+        resource_manager.assign('{"example": ["A", "B", "C", "D"]}')
+
+        # okay to release resources not assigned; does nothing
+        resource_manager.release('{"example": ["E"]}')
+        assert len(resource_manager) == 4
+        assert resource_manager.get() == set(["A", "B", "C", "D"])
+
+        # check release does what it should
+        resource_manager.release('{"example": ["D"]}')
+        assert len(resource_manager) == 3
+        assert resource_manager.get() == set(["A", "B", "C"])
+
+        # okay to release resources both assigned and not assigned
+        resource_manager.release('{"example": ["C", "D"]}')
+        assert len(resource_manager) == 2
+        assert resource_manager.get() == set(["A", "B"])
+
+        # check release all does what it should
+        resource_manager.release_all()
+        assert len(resource_manager) == 0
+        assert resource_manager.get() == set()
+
+        # okay to call release_all when already empty
+        resource_manager.release_all()
+        assert len(resource_manager) == 0
+        assert resource_manager.get() == set()
+
+
+class TestSKASubarray_commands:
+    """
+    This class contains tests of SKASubarray commands
+    """
+
+    def test_AssignCommand(self, resource_manager, state_model):
+        """
+        Test for SKASubarray.AssignResourcesCommand
+        """
+        assign_resources = SKASubarray.AssignResourcesCommand(
+            resource_manager,
+            state_model
+        )
+
+        # until the state_model is in the right state for it, the
+        # command's is_allowed() method will return False, and an
+        # attempt to call the command will raise a CommandError, and
+        # there will be no side-effect on the resource manager
+        for action in ["init_started", "init_succeeded", "on_succeeded"]:
+            assert not assign_resources.is_allowed()
+            with pytest.raises(CommandError):
+                assign_resources('{"example": ["foo"]}')
+            assert not len(resource_manager)
+            assert resource_manager.get() == set()
+
+            state_model.perform_action(action)
+
+        # now that the state_model is in the right state, is_allowed()
+        # should return True, and the command should succeed, and we
+        # should see the result in the resource manager
+        assert assign_resources.is_allowed()
+        assert assign_resources('{"example": ["foo"]}') == (
+            ResultCode.OK, "AssignResources command completed OK"
+        )
+        assert len(resource_manager) == 1
+        assert resource_manager.get() == set(["foo"])
diff --git a/tests/test_subarray_state_model.py b/tests/test_subarray_state_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c8d2f16ccc4e0fd7833ec380510968afbc0a44b
--- /dev/null
+++ b/tests/test_subarray_state_model.py
@@ -0,0 +1,273 @@
+#########################################################################################
+# -*- coding: utf-8 -*-
+#
+# This file is part of the SKASubarray project
+#
+#
+#
+#########################################################################################
+"""Contain the tests for the SKASubarray."""
+
+import itertools
+import pytest
+
+from tango import DevState
+
+from ska.base.control_model import AdminMode, ObsState
+from ska.base.faults import StateModelError
+
+
+class TestSKASubarrayStateModel():
+    """
+    Test cases for SKASubarrayStateModel.
+    """
+
+    @pytest.mark.parametrize(
+        'state_under_test, action_under_test',
+        itertools.product(
+            ["UNINITIALISED", "INIT_ENABLED", "INIT_DISABLED", "FAULT_ENABLED",
+             "FAULT_DISABLED", "DISABLED", "OFF", "EMPTY",
+             "RESOURCING", "IDLE", "CONFIGURING", "READY", "SCANNING",
+             "ABORTING", "ABORTED", "OBSFAULT"],
+            ["init_started", "init_succeeded", "init_failed", "fatal_error",
+             "reset_succeeded", "reset_failed", "to_notfitted",
+             "to_offline", "to_online", "to_maintenance", "on_succeeded",
+             "on_failed", "off_succeeded", "off_failed", "assign_started",
+             "resourcing_succeeded_no_resources", "resourcing_succeeded_some_resources",
+             "resourcing_failed", "release_started", "configure_started",
+             "configure_succeeded", "configure_failed", "scan_started",
+             "scan_succeeded", "scan_failed", "end_scan_succeeded",
+             "end_scan_failed", "abort_started", "abort_succeeded",
+             "abort_failed", "obs_reset_started", "obs_reset_succeeded",
+             "obs_reset_failed", "restart_started", "restart_succeeded",
+             "restart_failed"]
+        )
+    )
+    def test_state_machine(self, state_model,
+                           state_under_test, action_under_test):
+        """
+        Test the subarray state machine: for a given initial state and
+        an action, does execution of that action, from that initial
+        state, yield the expected results? If the action was not allowed
+        from that initial state, does the device raise a DevFailed
+        exception? If the action was allowed, does it result in the
+        correct state transition?
+
+        :todo: support starting in different memorised adminModes
+        """
+
+        states = {
+            "UNINITIALISED":
+                (None, None, None),
+            "FAULT_ENABLED":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.FAULT, None),
+            "FAULT_DISABLED":
+                ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.FAULT, None),
+            "INIT_ENABLED":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.INIT, ObsState.EMPTY),
+            "INIT_DISABLED":
+                ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.INIT, ObsState.EMPTY),
+            "DISABLED":
+                ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.DISABLE, ObsState.EMPTY),
+            "OFF":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.OFF, ObsState.EMPTY),
+            "EMPTY":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.EMPTY),
+            "RESOURCING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESOURCING),
+            "IDLE":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.IDLE),
+            "CONFIGURING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.CONFIGURING),
+            "READY":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.READY),
+            "SCANNING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.SCANNING),
+            "ABORTING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.ABORTING),
+            "ABORTED":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.ABORTED),
+            "RESETTING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESETTING),
+            "RESTARTING":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESTARTING),
+            "OBSFAULT":
+                ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.FAULT),
+        }
+
+        def assert_state(state):
+            (admin_modes, state, obs_state) = states[state]
+            if admin_modes is not None:
+                assert state_model.admin_mode in admin_modes
+            if state is not None:
+                assert state_model.dev_state == state
+            if obs_state is not None:
+                assert state_model.obs_state == obs_state
+
+        transitions = {
+            ('UNINITIALISED', 'init_started'): "INIT_ENABLED",
+            ('INIT_ENABLED', 'to_notfitted'): "INIT_DISABLED",
+            ('INIT_ENABLED', 'to_offline'): "INIT_DISABLED",
+            ('INIT_ENABLED', 'to_online'): "INIT_ENABLED",
+            ('INIT_ENABLED', 'to_maintenance'): "INIT_ENABLED",
+            ('INIT_ENABLED', 'init_succeeded'): 'OFF',
+            ('INIT_ENABLED', 'init_failed'): 'FAULT_ENABLED',
+            ('INIT_ENABLED', 'fatal_error'): "FAULT_ENABLED",
+            ('INIT_DISABLED', 'to_notfitted'): "INIT_DISABLED",
+            ('INIT_DISABLED', 'to_offline'): "INIT_DISABLED",
+            ('INIT_DISABLED', 'to_online'): "INIT_ENABLED",
+            ('INIT_DISABLED', 'to_maintenance'): "INIT_ENABLED",
+            ('INIT_DISABLED', 'init_succeeded'): 'DISABLED',
+            ('INIT_DISABLED', 'init_failed'): 'FAULT_DISABLED',
+            ('INIT_DISABLED', 'fatal_error'): "FAULT_DISABLED",
+            ('FAULT_DISABLED', 'to_notfitted'): "FAULT_DISABLED",
+            ('FAULT_DISABLED', 'to_offline'): "FAULT_DISABLED",
+            ('FAULT_DISABLED', 'to_online'): "FAULT_ENABLED",
+            ('FAULT_DISABLED', 'to_maintenance'): "FAULT_ENABLED",
+            ('FAULT_DISABLED', 'reset_succeeded'): "DISABLED",
+            ('FAULT_DISABLED', 'reset_failed'): "FAULT_DISABLED",
+            ('FAULT_DISABLED', 'fatal_error'): "FAULT_DISABLED",
+            ('FAULT_ENABLED', 'to_notfitted'): "FAULT_DISABLED",
+            ('FAULT_ENABLED', 'to_offline'): "FAULT_DISABLED",
+            ('FAULT_ENABLED', 'to_online'): "FAULT_ENABLED",
+            ('FAULT_ENABLED', 'to_maintenance'): "FAULT_ENABLED",
+            ('FAULT_ENABLED', 'reset_succeeded'): "OFF",
+            ('FAULT_ENABLED', 'reset_failed'): "FAULT_ENABLED",
+            ('FAULT_ENABLED', 'fatal_error'): "FAULT_ENABLED",
+            ('DISABLED', 'to_notfitted'): "DISABLED",
+            ('DISABLED', 'to_offline'): "DISABLED",
+            ('DISABLED', 'to_online'): "OFF",
+            ('DISABLED', 'to_maintenance'): "OFF",
+            ('DISABLED', 'fatal_error'): "FAULT_DISABLED",
+            ('OFF', 'to_notfitted'): "DISABLED",
+            ('OFF', 'to_offline'): "DISABLED",
+            ('OFF', 'to_online'): "OFF",
+            ('OFF', 'to_maintenance'): "OFF",
+            ('OFF', 'on_succeeded'): "EMPTY",
+            ('OFF', 'on_failed'): "FAULT_ENABLED",
+            ('OFF', 'fatal_error'): "FAULT_ENABLED",
+            ('EMPTY', 'off_succeeded'): "OFF",
+            ('EMPTY', 'off_failed'): "FAULT_ENABLED",
+            ('EMPTY', 'assign_started'): "RESOURCING",
+            ('EMPTY', 'fatal_error'): "OBSFAULT",
+            ('RESOURCING', 'resourcing_succeeded_some_resources'): "IDLE",
+            ('RESOURCING', 'resourcing_succeeded_no_resources'): "EMPTY",
+            ('RESOURCING', 'resourcing_failed'): "OBSFAULT",
+            ('RESOURCING', 'fatal_error'): "OBSFAULT",
+            ('IDLE', 'assign_started'): "RESOURCING",
+            ('IDLE', 'release_started'): "RESOURCING",
+            ('IDLE', 'configure_started'): "CONFIGURING",
+            ('IDLE', 'abort_started'): "ABORTING",
+            ('IDLE', 'fatal_error'): "OBSFAULT",
+            ('CONFIGURING', 'configure_succeeded'): "READY",
+            ('CONFIGURING', 'configure_failed'): "OBSFAULT",
+            ('CONFIGURING', 'abort_started'): "ABORTING",
+            ('CONFIGURING', 'fatal_error'): "OBSFAULT",
+            ('READY', 'end_succeeded'): "IDLE",
+            ('READY', 'end_failed'): "OBSFAULT",
+            ('READY', 'configure_started'): "CONFIGURING",
+            ('READY', 'abort_started'): "ABORTING",
+            ('READY', 'scan_started'): "SCANNING",
+            ('READY', 'fatal_error'): "OBSFAULT",
+            ('SCANNING', 'scan_succeeded'): "READY",
+            ('SCANNING', 'scan_failed'): "OBSFAULT",
+            ('SCANNING', 'end_scan_succeeded'): "READY",
+            ('SCANNING', 'end_scan_failed'): "OBSFAULT",
+            ('SCANNING', 'abort_started'): "ABORTING",
+            ('SCANNING', 'fatal_error'): "OBSFAULT",
+            ('ABORTING', 'abort_succeeded'): "ABORTED",
+            ('ABORTING', 'abort_failed'): "OBSFAULT",
+            ('ABORTING', 'fatal_error'): "OBSFAULT",
+            ('ABORTED', 'obs_reset_started'): "RESETTING",
+            ('ABORTED', 'restart_started'): "RESTARTING",
+            ('ABORTED', 'fatal_error'): "OBSFAULT",
+            ('OBSFAULT', 'obs_reset_started'): "RESETTING",
+            ('OBSFAULT', 'restart_started'): "RESTARTING",
+            ('OBSFAULT', 'fatal_error'): "OBSFAULT",
+            ('RESETTING', 'obs_reset_succeeded'): "IDLE",
+            ('RESETTING', 'obs_reset_failed'): "OBSFAULT",
+            ('RESETTING', 'fatal_error'): "OBSFAULT",
+            ('RESTARTING', 'restart_succeeded'): "EMPTY",
+            ('RESTARTING', 'restart_failed'): "OBSFAULT",
+            ('RESTARTING', 'fatal_error'): "OBSFAULT",
+        }
+
+        setups = {
+            "UNINITIALISED": [],
+            "INIT_ENABLED": ['init_started'],
+            "INIT_DISABLED": ['init_started', 'to_offline'],
+            "FAULT_ENABLED": ['init_started', 'init_failed'],
+            "FAULT_DISABLED": ['init_started', 'to_offline', 'init_failed'],
+            "OFF": ['init_started', 'init_succeeded'],
+            "DISABLED": ['init_started', 'init_succeeded', 'to_offline'],
+            "EMPTY": ['init_started', 'init_succeeded', 'on_succeeded'],
+            "RESOURCING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started'
+            ],
+            "IDLE": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources'],
+            "CONFIGURING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'configure_started'
+            ],
+            "READY": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'configure_started', 'configure_succeeded'
+            ],
+            "SCANNING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'configure_started', 'configure_succeeded', 'scan_started'
+            ],
+            "ABORTING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'abort_started'
+            ],
+            "ABORTED": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'abort_started', 'abort_succeeded'
+            ],
+            "OBSFAULT": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'fatal_error'
+            ],
+            "RESETTING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'abort_started', 'abort_succeeded', 'obs_reset_started'
+            ],
+            "RESTARTING": [
+                'init_started', 'init_succeeded', 'on_succeeded',
+                'assign_started', 'resourcing_succeeded_some_resources',
+                'abort_started', 'abort_succeeded', 'restart_started'
+            ],
+        }
+
+        # state = "UNINITIALISED"  # for test debugging only
+        # assert_state(state)  # for test debugging only
+
+        # Put the device into the state under test
+        for action in setups[state_under_test]:
+            state_model.perform_action(action)
+            # state = transitions[state, action]  # for test debugging only
+            # assert_state(state)  # for test debugging only
+
+        # Check that we are in the state under test
+        assert_state(state_under_test)
+
+        # Test that the action under test does what we expect it to
+        if (state_under_test, action_under_test) in transitions:
+            # Action should succeed
+            state_model.perform_action(action_under_test)
+            assert_state(transitions[(state_under_test, action_under_test)])
+        else:
+            # Action should fail and the state should not change
+            with pytest.raises(StateModelError):
+                state_model.perform_action(action_under_test)
+            assert_state(state_under_test)
diff --git a/tests/test_tel_state_device.py b/tests/test_tel_state_device.py
index 5b696b0b0664aa55f99ec18478f5010870dfefd1..a5c5361ffde0399206e9cff61c7892d7542b9b98 100644
--- a/tests/test_tel_state_device.py
+++ b/tests/test_tel_state_device.py
@@ -8,11 +8,6 @@
 #########################################################################################
 """Contain the tests for the SKATelState."""
 
-# Standard imports
-import sys
-import os
-
-# Imports
 import re
 import pytest
 from tango import DevState
@@ -20,7 +15,8 @@ from tango import DevState
 # PROTECTED REGION ID(SKATelState.test_additional_imports) ENABLED START #
 from ska.base.control_model import AdminMode, ControlMode, HealthState, SimulationMode, TestMode
 # PROTECTED REGION END #    //  SKATelState.test_additional_imports
-# Device test case
+
+
 # PROTECTED REGION ID(SKATelState.test_SKATelState_decorators) ENABLED START #
 @pytest.mark.usefixtures("tango_context", "initialize_device")
 # PROTECTED REGION END #    //  SKATelState.test_SKATelState_decorators
@@ -53,7 +49,7 @@ class TestSKATelState(object):
     def test_State(self, tango_context):
         """Test for State"""
         # PROTECTED REGION ID(SKATelState.test_State) ENABLED START #
-        assert tango_context.device.State() == DevState.UNKNOWN
+        assert tango_context.device.State() == DevState.OFF
         # PROTECTED REGION END #    //  SKATelState.test_State
 
     # PROTECTED REGION ID(SKATelState.test_Status_decorators) ENABLED START #
@@ -61,7 +57,7 @@ class TestSKATelState(object):
     def test_Status(self, tango_context):
         """Test for Status"""
         # PROTECTED REGION ID(SKATelState.test_Status) ENABLED START #
-        assert tango_context.device.Status() == "The device is in UNKNOWN state."
+        assert tango_context.device.Status() == "The device is in OFF state."
         # PROTECTED REGION END #    //  SKATelState.test_Status
 
     # PROTECTED REGION ID(SKATelState.test_GetVersionInfo_decorators) ENABLED START #
@@ -76,15 +72,6 @@ class TestSKATelState(object):
         assert (re.match(versionPattern, versionInfo[0])) is not None
         # PROTECTED REGION END #    //  SKATelState.test_GetVersionInfo
 
-    # PROTECTED REGION ID(SKATelState.test_Reset_decorators) ENABLED START #
-    # PROTECTED REGION END #    //  SKATelState.test_Reset_decorators
-    def test_Reset(self, tango_context):
-        """Test for Reset"""
-        # PROTECTED REGION ID(SKATelState.test_Reset) ENABLED START #
-        assert tango_context.device.Reset() is None
-        # PROTECTED REGION END #    //  SKATelState.test_Reset
-
-
     # PROTECTED REGION ID(SKATelState.test_buildState_decorators) ENABLED START #
     # PROTECTED REGION END #    //  SKATelState.test_buildState_decorators
     def test_buildState(self, tango_context):
@@ -118,7 +105,7 @@ class TestSKATelState(object):
     def test_adminMode(self, tango_context):
         """Test for adminMode"""
         # PROTECTED REGION ID(SKATelState.test_adminMode) ENABLED START #
-        assert tango_context.device.adminMode == AdminMode.ONLINE
+        assert tango_context.device.adminMode == AdminMode.MAINTENANCE
         # PROTECTED REGION END #    //  SKATelState.test_adminMode
 
     # PROTECTED REGION ID(SKATelState.test_controlMode_decorators) ENABLED START #
diff --git a/tests/test_utils.py b/tests/test_utils.py
index c12e38fa537d54b8124b3fa0d74b6971e2b4cc71..fb749b7f630cec318d74020ff6e5e5d236e87ccb 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -122,6 +122,7 @@ BAD_GROUP_KEYS = [
     ('basic_no_subgroups', 'bk1_bad_keys', ),
 ]
 
+
 def _jsonify_group_configs(group_configs):
     """Returns list of JSON definitions for groups."""
     definitions = []