Mid.CBF FPGA Host Server (FHS) Device Simulation Design

How to simulate an FHS device

FHS Tango devices can be simulated using the ska_mid_cbf_fhs_common.testing.simulation module.

This module provides several key classes, including;

  • FhsSimMode: simulation mix-in for FHS device classes - supplies the simOverrides attribute to the device interface

  • SimModeCMBase: base for simulating FHS component manager (CM) classes

  • FhsObsSimMode and SimModeObsCMBase: extensions of the above classes for simulation of observing devices

Any FHS device needing simulation can be turned into a standalone simulator with the following changes:

  • Create a sim CM for the device that replicates the actual CM’s interface. The FHS sim CM inherits from either SimModeCMBase or SimModeObsCMBase. Three basic things must be implemented:

    • Default attribute return values, stored in attribute_overrides.

    • Attribute getters so that attribute_overrides can be read by the device class.

    • Attribute setters for any write attributes, so the device class can set attribute_overrides.

    • Default command return values, stored in command_overrides.

  • Create an FHS simulator device class, that inherits from both the actual FHS device class and one of either FhsSimMode or FhsObsSimMode.

    • This will need a main() method to run the simulator, which will furthermore require an entrypoint script in pyproject.toml.

    • This process startup command will lastly need to be added to the chart values such that it is called instead of the genuine device startup when the value standaloneSimulator is set to true.

For a fleshed-out example see the simulation of VCCAllBandsController: ska-mid-cbf-fhs-vcc

Example test code: overriding On command result code

# Get a proxy to the simulated device.
simulator = tango.DeviceProxy("<simulator FQDN>")

# Commands are set by default to return OK.
(result_code, message) = simulator.On()
assert (result_code, message) == (ResultCode.OK, "On completed OK")

# Override the result code value by writing the simOverrides attribute.
# Only behaviours included in the JSON configuration will override the default,
# so here the returned message will stay the same.
simulator.simOverrides = json.dumps(
    {
        "commands": {
            "On": {
                "result_code": "FAILED"
            }
        }
    }
)
(result_code, message) = simulator.On()
assert (result_code, message) == (ResultCode.FAILED, "On completed OK")

Class breakdown

  • FHS simulator device base mix-in (FhsSimMode) - inherits from ska-tango-base tango.Device and implements patterns from the ska-tango-base class TestModeOverrideMixin

    • Adds the simOverrides attribute: write a JSON-formatted string containing overrides under 2 keys, “attributes” and “commands”, which update the attribute_overrides and command_overrides dicts respectively

      • “attributes”: keys are attribute names, values are override values for those attributes

        • Initialized with default values

        • Updated with values from simOverrides.write; any change in attribute values will push a new change event for those attributes

        • Attribute write methods also update the override values; clients should be able to write new values to the simulator and expect them to be stored if allowed

      • “commands”: keys are command names, values are dicts containing the following:

        • For LRCs: command callback update values, including result code, message, component state updates

        • For FastCommands: return value tuple

        • start/stop_communicating: callback update values to edit adminMode pseudo-command behaviour

  • FHS simulator component manager class (SimModeCMBase) - inherits from the ska-tango-base class TaskExecutorComponentManager

    • Provides 3 methods for simulating LRC behaviour

      • sim_command: behaves as typical LRC target method, simply submits private _sim_command method to the task executor queue; the only difference is that it takes in the command name as sole argument, passing it to the private method as well, for logging and task_callback use

      • is_sim_command_allowed: returns True and thus executes the _sim_command private method if “allowed” is set to True or None in command overrides; if set to False, simulate command rejection. Used by both LRCs and FastCommandSim.

      • _sim_command: private LRC execution method (following the pattern established for all other Mid.CBF devices), follows command_overrides to indicate behaviour of command

    • start/stop_communicating: adminMode.write is a pseudo-command, so we can alter the behaviour of these component manager methods as well

Example sequence: using simOverrides

@startuml
'https://plantuml.com/sequence-diagram

skinparam backgroundColor #EEEBDC
skinparam sequence {
  ParticipantBorderColor DodgerBlue
  ParticipantBackgroundColor DeepSkyBlue
  ActorBorderColor DarkGreen
  ActorBackgroundColor Green
  BoxBorderColor LightBlue
  BoxBackgroundColor #F0FFFF
}
skinparam collections {
  BackGroundColor LightBlue
  BorderColor DodgerBlue
}
skinparam database {
  BackgroundColor LightGreen
  BorderColor DarkGreen
}

title Example Simulator Test Failure Sequence\n\nSimulate VCC belonging to another subarray,\nthen being released and available\n

participant "pytest" as pyt #Thistle
box "\nMCS\n"
    participant "Mid.CBF\nSubarray" as subarray
    collections "Mid.CBF VCC\nSimulator" as vcc
end box

note over subarray           : DeviceID: 1\nObsState.EMPTY\nSimulationMode.TRUE
note over vcc                : DeviceID: 1\nDishID: "SKA001"\nadminMode.OFFLINE\nsubarrayMembership: 0\nObsState.IDLE

group Test Subarray AddReceptors failure

  group #SeaShell Update Mid.CBF VCC Simulator Behaviour

    pyt         ->  vcc        : simOverrides: {\n\t"attributes": {\n\t\t"subarrayMembership": 2\n\t}\t\n}
    note over vcc              : subarrayMembership: 2

  end group

  group #SeaShell AddReceptors

    pyt         ->  subarray   : AddReceptors(["SKA001"]])

    group #LightCyan AddReceptors:

      note over subarray       : ObsState.RESOURCING
      subarray    -> subarray  : Validate DISH ID
      subarray    -x vcc       : Check VCC subarrayMembership\nFAIL
      note over subarray       : ObsState.EMPTY

    end group

    pyt        <--  subarray   : ResultCode.FAILED, "Failed to assign SKA001"

  end group

end group

group Test Subarray AddReceptors success

  group #SeaShell Update Mid.CBF VCC Simulator Behaviour

    pyt         ->  vcc        : simOverrides: {\n\t"attributes": {\n\t\t"subarrayMembership": 0\n\t}\t\n}
    note over vcc              : subarrayMembership: 0

  end group

  group #SeaShell AddReceptors

    pyt         ->  subarray    : AddReceptors(["SKA001"])

    group #LightCyan AddReceptors:

      note over subarray       : ObsState.RESOURCING
      subarray    -> subarray  : Validate DISH ID
      subarray    -> vcc       : Check VCC subarrayMembership\nSUCCESS
      subarray    -> vcc       : adminMode = ONLINE
      note over vcc            : adminMode: ONLINE\nOpState: ON
      subarray    ->  vcc      : subarrayMembership = 1
      note over vcc            : subarrayMembership: 1
      note over subarray       : ObsState.IDLE

    end group

    pyt        <--  subarray   : ResultCode.OK, "AddReceptors completed OK"

  end group

end group

group Test Subarray ConfigureScan failure

  group #SeaShell Update Mid.CBF VCC Simulator Behaviour

    pyt         ->  vcc        : simOverrides: {\n\t"commands": {\n\t\t"ConfigureScan": {\n\t\t\t"result_code": "FAILED",\n\t\t\t"message": "ConfigureScan command failed.",\n\t\t\t"component_state": {\n\t\t\t\t"configured": False\n\t\t\t}\n\t\t}\n\t}\n}
    note over vcc              : command_overrides updated

  end group

  group #SeaShell Test Subarray ConfigureScan

    pyt         ->  subarray    : ConfigureScan("{...}")

    group #LightCyan ConfigureScan:

      note over subarray       : ObsState.CONFIGURING
      note over subarray       : (...)
      subarray  ->  vcc        : ConfigureScan("{...}")
      note over vcc            : ObsState.CONFIGURING\ncommand_overrides induces failure\nObsState.IDLE
      subarray  <-- vcc        : ResultCode.FAILED\n"ConfigureScan command failed"
      note over subarray       : ObsState.IDLE

    end group

    pyt        <--  subarray   : ResultCode.FAILURE: "Failed to configure VCC 1"

  end group

end group

group Test Subarray ConfigureScan success

  group #SeaShell Update Mid.CBF VCC Simulator Behaviour

    pyt         ->  vcc        : simOverrides: {\n\t"commands": {\n\t\t"ConfigureScan": {\n\t\t\t"result_code": "OK",\n\t\t\t"message": "ConfigureScan completed OK",\n\t\t\t"component_state": {\n\t\t\t\t"configured": True\n\t\t\t}\n\t\t}\n\t}\n}
    note over vcc              : command_overrides updated

  end group

  group #SeaShell Test Subarray ConfigureScan

    pyt         ->  subarray    : ConfigureScan("{...}")

    group #LightCyan ConfigureScan:

      note over subarray       : ObsState.CONFIGURING
      note over subarray       : (...)
      subarray  ->  vcc        : ConfigureScan("{...}")
      note over vcc            : ObsState.CONFIGURING\ncommand_overrides induces success\nObsState.READY
      subarray  <-- vcc        : ResultCode.OK\n"ConfigureScan completed OK"
      note over subarray       : (...)
      note over subarray       : ObsState.READY

    end group

    pyt        <--  subarray   : ResultCode.OK: "ConfigureScan completed OK"

  end group

end group

@enduml