Using domain-specific modeling languages for medical device development
Domain-Specific Languages have become a common tool in the toolbox of software developers. There is a natural reason for this: they are more expressive and therefore tackle complexity better, making software development easier and more convenient. They can also raise the level of abstraction from raw implementation code to the actual problem domain. Designs and specifications expressed with the higher-level problem domain concepts make specifications easier to create, check and communicate with. Most importantly, together with domain-specific generators, they can automate the creation of production code.
When companies start using domain-specific languages, they often want to utilize existing languages and specifications along with related components and legacy code. Through a concrete example from a medical domain, we describe how existing languages for Programmable Logic Controllers (PLCs), like IEC 61131-3 structured text or function block diagrams, can be extended with domain-specific constructs.
We also demonstrate native code building for the run-time system (RTS), targeting both Intel and ARM processors, and managing HMI components for monitoring measurement and control processes. The high-level languages are not only used for design and code generation, but also when debugging and profiling, or even incremental specification and execution “on the fly”. We conclude by describing our experiences of creating the domain-specific language and of using it in production.
Example domain: blood separation machines
PLCs are widely used in different industries and machines for automation. We focus here on a medical domain and describe how control logic for blood separation machines can be specified with models in domain-specific languages. These models are then used to generate the functional code – integrated with the libraries and target operating systems.
A blood separator is a medical device separating various kinds of fluids from blood. The main task of this device is to separate erythrocytes (RC), lymphocytes (BC) and plasma (PL) after centrifugation. In practice, the separation process is performed by mechanical presses and devices called “blood separation machines” operating with high speed and precision. Such a device is expected to:
- prevent the mixing of fluids using a precise control logic with a prompt response;
- enable measurements of fluid weight; and
- recognize different blood variants and adapt the separation process to blood specifics.
In addition to these medical operations there are also technical and organizational requirements on machine operation, e.g. that a device can concurrently execute several processes of separation, make labels for marking bags, or update a database of blood types and blood supplies using a web service.
Traditionally applications for these devices are developed using general-purpose languages (GPL), such as C++, Java or IEC 61131, describing both the structure and logic of the application. Figure 1 shows an example of one of the languages: an IEC 61131 function block diagram. This language specifies the individual functions of the system, and their connections along with input and output variables. In our example, the model specifies the three variable links between functions for motor speed adjustment and a step motor. Other IEC languages then allow the specification of control and logic within the functions and the whole system.
Since IEC languages are general-purpose, there is nothing in the above language, or in other GPLs, that relates them to blood separation or even to the medical domain. These languages thus don’t enable reasoning, checking, optimization for code generation, or integration with the dedicated libraries or hardware/platform in use. Nor do they allow checking any of the rules or constraints of blood separation, but leave these to be manually checked at the code level. To improve the quality and productivity of the development, we defined a domain-specific language and a code generator targeting blood separation.
Extending the language with domain concepts
To raise the level of abstraction, the language concepts were taken directly from the problem domain, in our case blood separation. This does not mean that we completely abandon all currently used languages: parts that are considered to work well can be incorporated. This also allows us to use existing specifications along with domain-specific languages.
A natural source for language constructs is the blood separation machine itself. The various elements of the hardware are easy to detect: a press, motor, switch, balance, clamp, fluid sensor and position detector. Each of these have also specific characteristics, e.g. a press must describe its limits, a detector measures the position and limits of movements, a motor has speed and direction, and an activity has state. Among all the potential characteristics those relevant for machine specification can be used directly as constructs in the language. To formalize these language constructs they are specified as a metamodel.
A metamodel typically also captures relationships among the language constructs and related constraints. Figure 2 shows a partial metamodel describing some of the key language concepts, their relationships and constraints. This metamodel was defined in the MetaEdit+ tool, enabling immediate language testing as well as production use with already available modeling editors. ()
Reading the metamodel from top left, a TwoStateController is an object of the language with two properties: a string identifier and a Boolean property to indicate if the controller is on or off. It can be connected via a SwitchSensor relationship to a Press object. The Press has more complex properties than just an entry field: several IEC_Inputs each having its own characteristics. A Press object can be connected to a PositionDetector to provide its position as a value. The PositionDetector again has its own properties, including the current position of a press. Other domain concepts are defined similarly.
If already available specifications need to be applied along with the domain concepts, their modeling constructs can be added to the metamodel too. For example, the concept of Function comes directly from IEC 61131, and the ways how it is connected have been added to the metamodel too: A function has a set of properties and it can be connected via Signal distribution to other functions and also to some other domain concepts. This enables evolutionary refinement of models which use general constructs, like Functions, into models which use domain-specific language constructs.
By directly using the concepts of the blood separation domain as language constructs, the modeling language helps prevent errors early in the development phase, minimizes specification work, and at the same time makes the language more suitable for code generation. Describing things in problem domain terms instead of implementation concepts is also good future-proofing. We can for example use the same models for other target languages or libraries.
While the metamodel defines what is possible to specify with the language, perhaps more important is what it left out: all those concepts that are not relevant for specifying blood separation machines are not part of the metamodel. This not only helps in using the language, but makes it easier to build the generators as they access the same metamodel knowing the meaning of each model element.
In addition to the metamodel, the language definition has a concrete syntax: the notation. For each language construct we created a symbol that is used to visualize it in a model. The notation was again taken from the problem domain. A sample of the notation is shown in Figure 3.
Incremental refinement of the language while using it
A good practice is to test the language early on with concrete application examples: in our case, specifying the structure and functionality of various blood separation machines. Figure 3 illustrates a sample model describing fluid squeezing. This model uses the concepts defined in the metamodel shown in Figure 2. Elements M1T to M6T on the left are TwoStateControllers (optical sensors), TopBloodBag is a Press and TopStepMotor is a StepMotor. In addition to the names of the elements shown in the screenshot, the tool also allows to specify the related properties and their default values, like direction and speed for step motors.
Industrial automation, and the medical domain in particular, demands high quality from the software and related systems. The domain-specific language follows the constraints and rules of the domain, guiding engineers to create correct, consistent and complete specifications. For example, the language does not allow connecting a motor to other objects other than through its ports (Figure 3, ports Enabled, Direction and Speed of the TopStepMotor).
The same language is also used to specify control logic, like calibration of the balance (Figure 4). As before, this model directly applies the domain concepts, like Switch and Balance. Here the whole model is specified with plain domain concepts, without using functions or other general-purpose constructs.
Independently of the hardware employed to realize the device, the operating system, and any libraries used in implementation, a typical balance calibration procedure consists of the following steps:
- Pressing a switch to start and stop calibration (TopBalCalib)
- Reading previous reference values, for possible comparison with new ones (TopBalRefVal)
- Selection of the switch that shows if a calibration weight is placed on the scale (TopBalMax)
- Reading of numerical values, with and without the presence of the weight, using an A/D converter (TopBal and TopBalCtrl)
- Updating appropriate values of TopBalRefVal, depending on whether TopBalMax is on or off
- Repeating the calibration routine while the switch (TopBalCalib) is on
The specification of balance calibration in Figure 4 is used for generating control logic code for different programming languages and for different target platforms. For embedded systems, a particularly useful platform is a Run-Time System (RTS) which interprets or executes IEC 61131 and IEC 61499 specifications. Such a RTS provides execution of code generated based on function block diagrams. Also, it enables synchronization of distributed components, and reporting on the state of the system using events. From the code generation point of view, such a target system significantly simplifies the definition of the code generator, which needs to synchronize control logic with drivers and task scheduler.
Generators for code and configuration
Building a generator is about defining how model concepts are mapped to program code or other output like library calls. The access of the model elements is dictated by the language definition (metamodel). Consider the generator script in Figure 5 as an example: it accesses AnalogControllers (like TopBalCtrl on Figure 4), and then connections to two state switches and controllers. The green text refers directly to concepts of the language. The generator is defined in the same MetaEdit+ tool ( )we used for language definition. In this way changes in the metamodel can be immediately seen and tested while making the generators, significantly speeding up language and generator development.
This generator is then executed during code generation to produce the code needed for calibration of the machine. Other generators produce other parts like code for press, position detector, etc. Listing 1 shows the piece of code generated from the model shown in Figure 4. The generator script described above produces the code for the if-then-else structure, and another generator script produces the variables and initializations given in the first half of Listing 1.