Programming Guide¶
Device Manager Extensions¶
Having type safe interfaces as defined by CII ICD XML enables to improve runtime robustness of our applications. However it is also makes the extendability harder to achieve. To simplify adding instrument specific extensions to the Device Manager, we have provided a template that can be used as starting point to implement special devices along with some companion modules.
Template¶
You should create your software based on the provided project template by following the procedure in the Getting Started guide here
Top Directory Structure¶
<root> # Generated project directory
├── resource # directory containing the resources
└── <prefix>-ics # WAF project
├── <component> # Generated FCS
├── seq
├── <prefix>stoo
└── wscript
Component Directory Structure¶
Here it will be reported the specific parts about FCS in the generated project. The generated structure resembles the structure of the FCF component.
<component>
├── devices # Custom Device
├── devsim # Simulator for custom device
├── fcsclib # Custom Python client library
├── gui # Custom GUI
├── LCS # Example PLC Project (VS)
├── server # Custom server
└── wscript
Note
This FCS structure has been simplified in version 3. There is no need of having a custom CII interface for the setup command. The state machine is the same as the default server and the only difference is the handling of a custom device library.
Note
All custom devices will use the CUSTOM device type of the existing FCF XML interface.
Custom Device¶
The generated code includes the implementation of a dummy custom device that can be used as starting point to develop instrument specific ones. This device contains the intelligence to deserialize the custom setup command payload in method Setup. Users will have to modify this method and method IsSetupActive to adapt to their specific needs.
Warning
The serialization of the setup command for custom devices is done in JSON format.
void Mirror::Setup(const std::any& payload) {
RAD_LOG_TRACE();
RAD_LOG_INFO() << "Processing Setup command ...";
if (!m_config->GetIgnored()) {
auto fcf_vector = std::any_cast<std::vector<std::shared_ptr<fcfif::SetupElem>>>(&payload);
for (auto it = fcf_vector->begin(); it != fcf_vector->end(); it++) {
auto setup_elem = *it;
RAD_LOG_INFO() << "ID: " << setup_elem->getId();
// ignore other shutters
if (IsMsgForMe(setup_elem->getId()) != true) {
continue;
}
auto fcs_union = setup_elem->getDevice();
if (fcs_union->getDiscriminator() != ::fcfif::DeviceType::CUSTOM) {
RAD_LOG_ERROR() << "[" << m_config->GetName() << "] "
<< "Setup is for me but device type is not correct?";
continue;
}
auto custom = fcs_union->getCustom();
RAD_LOG_DEBUG() << "[" << m_config->GetName() << "] "
<< "Setup ID: " << setup_elem->getId();
std::string params = boost::replace_all_copy(custom->getParameters(), "'", "\"");
RAD_LOG_INFO() << "Setup buffer: " << params;
//@TODO: Add handling of setup parameters
EncDec data = nlohmann::json::parse(params);
// Optional parameter
RAD_LOG_INFO() << "Action: " << data.get_action();
if (data.has_piston()) {
RAD_LOG_INFO() << "Piston: " << data.get_piston();
}
RAD_LOG_INFO() << "Tip: " << data.get_tip();
RAD_LOG_INFO() << "Tilt: " << data.get_tilt();
if (data.get_action() == "MOVE") {
RAD_LOG_INFO() << "[" << m_config->GetName() << "] "
<< "Executing RPC_PING ...";
m_lcs_if->Ping();
RAD_LOG_INFO() << "[" << m_config->GetName() << "] "
<< "Successfull call of Mirror ping: ";
}
}
}
}
Testing¶
The generated device module contains a set unit test that can be extended accordingly. After the generation, all unit test shall pass based on the actual implementation.
$ cd <component>devices
$ waf test --alltests
[==========] Running 26 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 9 tests from TestMirror
[ RUN ] TestMirror.Ctor
...
[----------] Global test environment tear-down
[==========] 26 tests from 3 test cases ran. (2545 ms total)
[ PASSED ] 26 tests.
Custom Simulation¶
In oder to allow the testing of the dummy device, a device simulator is generated and can be used for testing purposes. The simulator implements the RPC_Ping dummy method used by the custom device as an action of the Setup command.
Extending JSON schema¶
In order to validate the setup payload before sending it to the server, applications shall extend the FCF schema for custom devices. We provide an example for this in the template for the Mirror device.
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["MOVE" ],
"description": "Mirror action."
},
"tip": {
"type": "number",
"description": "tip."
},
"tilt": {
"type": "number",
"description": "tilt."
},
"piston": {
"type": "number",
"description": "piston."
}
},
"required": ["action","tip","tilt"]
}
Applications required to define their own python library where to compose the FCS schema adding the new one. There are probably different ways to achieve this, we provide an example below where the composition in done in python.
""" Load basic schema for all standard devices """
self._schema = json.loads(json_obj.SETUP_SCHEMA)
""" Change the schema to add new device """
""" add the new definition """
self._schema['definitions']['mirror'] = json_obj.load_json_string(SetupBuffer.MIRROR_SCHEMA)
""" add new element the array """
self._schema['definitions']['param']['oneOf'].append(json_obj.load_json_string('{"required": [ "mirror"]}'))
self._schema['definitions']['param']['properties']['mirror'] = json_obj.load_json_string('{"$ref": "#/definitions/mirror"}')