Bruce Douglass wraps up his series on the Unified Modeling Language with a focus on architectural, mechanistic, and detailed design.
This article is the third in a series of three on how the Unified Modeling Lan-guage (UML) can be used to develop real-time and embedded systems. The first article identified the major notational and semantic features of the UML. The second developed the notions of analysis of object-oriented (OO) systems and used an anesthesia patient ventilator-the Gasp-o-matic-as an illustrative example. In this last installment, we’ll complete our look at the use of the UML in the development of real-time systems by focusing on design.
As I discussed last time, analysis is the process of identifying what the system is-its essential properties and characteristics-rather than on how the system is put together. I decompose analysis into two primary parts: requirements analysis and object analysis. Requirements analysis is fundamentally a black-box view of the system. The process of requirements analysis is a process of defining the externally visible features and functions of the system to be developed. The UML provides conceptual tools, such as use cases and scenarios, for exploring and capturing this information. Object analysis "opens the box" and identifies the key concepts of the system and their relationships. This is a process of discovery, and a number of strategies are available to achieve it.1,2 Analysis identifies only the required characteristics of the system that must be present in all acceptable solutions. Some methodologists call this the "essential model" for just that reason. A detailed description of how this analysis model will be realized is omitted-that is the job of design, the topic of this month’s installment.
I divide design into three phases. The first phase, architectural design, defines large-scale strategic decisions about the system implementation. This level of design concerns itself with the number and type of processors, how they are interconnected, the concurrency model used, and so on. Architectural design decisions affect most or all software in the system.
The next level of design is concerned with the insertion of "glue" objects-used to bind together the objects identified in analysis-into small collaborating groups, and optimizing their interaction. These small groups are called mechanisms, and this phase of design is called mechanistic design.
The last level of design is concerned with the details within individual objects, such as how associations will be implemented, primitive structuring of attributes, visibility of object members, and so on. This level is called detailed design.
Now, let’s continue developing the Gasp-o-matic patient ventilator as an example of how to apply the techniques, views, and semantics of the UML to the design of embedded real-time systems.
Gasp-o-matic Design Issues
Just as in a real system, we’ll consider a number of issues related to the design of the Gasp-o-matic. These issues will include the:
- Physical topology of processors
- Safety-criticality and reliability
- Concurrency model
- Management of collections of objects
- Resolution of logical "messages" into object operations
- State machine implementation policy
- Memory management, especially control of object creation and destruction, and avoiding memory fragmentation
Some of these issues have broad strategic applicability affecting many or most components. These issues will be discussed in the section on architectural design. Others will deal with the interaction of small groups of objects that are isolated from other system aspects. These issues will be discussed in the section on mechanistic design. The remainder have to do with the internal structure of individual objects and will be discussed in the section on detailed design.
In this article, we will take a pattern-oriented approach to the design of this real-time system. As mentioned in the first installment of this series, a design pattern is a kind of design template for a solution to a common type of problem. A design pattern consists of three elements:
- A common problem
- The problem context
- A general solution
Design patterns are a means of capturing the best design practices in the form of a design approach to a generic problem. They’re often customized and tailored to meet specific needs of the application, just as arches in architectural systems are customized to meet the specific requirements of the building under construction.
Most of the active research in this area is in the area of mechanistic design patterns. However, in this article we’ll use design patterns in all phases of design. Many of these patterns are taken from my books and from the recent Embedded Systems Conference in Chicago.3 Other references include Design Patterns: Elements of Reusable Object-Oriented Software, which is often referred to as the GoF ("Gang of Four") book.4
Architectural Design of the Gasp-o-matic
The primary architectural design questions for the Gasp-o-matic are:
- How many processors should be used?
- How should these processors be linked together?
- What software should run on each of these processors?
- What kind of architectural support should we have to ensure patient safety?
- What kind of policies should we use to manage memory to avoid memory fragmentation?
- How can we ensure all deadlines are met now and in the future?
Naturally, these architectural decisions aren’t independent. In fact, I like to think of them as different dimensions in an n-dimensional vector space. The process of constructing a good design is akin to finding a local minima in that vector space. Put another way, it’s the set of decisions that finds the best overall fit in the global sense. Because I have only two hands and a limited IQ,5 I find it difficult to grasp all aspects of a complex system simultaneously. So my approach is to grab a hold of one or two of these issues that seem to be of primary importance and shake them really hard to see where the others end up.6
Safety Architecture
The issues we’ll start with here are the safety and the processor architecture. The primary hazards present in the Gasp-o-matic are reviewed in Table 1.
Clearly, a software-only solution cannot provide adequate safety.8,9 For example, software alone cannot detect a timebase fault, such as the doubling of the CPU’s clock crystal’s rate (a common failure mode), or a CPU halt. Safety must be provided by the combination of mechanical, electronic, and software components.
Two patterns are relevant here.10,11 A simple solution for the timebase problem is to use the watchdog pattern, as shown in Figure 1. The watchdog pattern uses a hardware assistant called a watchdog that must be stroked within a certain timeframe or it will cause some failure recovery activity to take place, such as a system reset. A watchdog can detect a timebase failure (too fast and/or too slow, depending on its implementation), and a CPU halt, but not much else. In addition, the scope of the corrective actions that it can take is usually limited to a system halt or reset.
Another pattern involves the use of redundant processors. The homogeneous redundancy pattern replicates channels.12 This pattern is useful for detecting and handling random failures but not systematic errors (such as software defects). This is because, by definition, systematic errors must occur in all identical channels. The heterogeneous redundancy pattern (also known as diverse redundancy pattern) uses different but equivalent channels to provide redundancy. Heterogeneous redundancy is stronger than homogeneous redundancy because the former can also identify systematic errors.
In this case, let’s use a specialized form of heterogeneous redundancy called the Monitor-Actuator Pattern to achieve our safety architectural goals (see Figure 2). This pattern achieves safety in the following way:
- If a fault occurs in the actuation channel, the independent monitoring channel detects it, allowing corrective action to be taken
- If a fault occurs in the monitoring channel, the independent actuation channel continues to operate correctly
This assumes independence of the two channels-specifically, that the two channels have no common mode faults. That is, any single fault that occurs within one channel will not affect the other. Also note that even though the system continues to be correct in the presence of a monitor failure, we need to identify any persistent failure of the monitor circuit. Otherwise we have what is known as a latent fault-a condition that isn’t now dangerous, but when combined with another single fault can lead to an accident. It is common to at least check for latent faults at system startup or daily, whichever is more frequent.
Our application safety architecture now looks something like that shown in Figure 3.13 Remember, the key requirement for this architecture to be safe in the event of a single failure is that the monitor channel components must be unaffected by any faults in the channel responsible for the delivery of ventilation. For this reason, we’ll select the processor architecture shown in Figure 4. This diagram is in the form of a UML deployment diagram. The graphical syntax of these diagrams was described in the first part of this series.
The deployment diagram illustrated in Figure 4 uses the standard node icon with various stereotypes. Note that it uses three processors. Two of them, the ventilator controller and the ventilator monitor, are separated to ensure safety. The third is for efficiency, so that managing user input and drawing graphics won’t affect the timing of the other components.14
Mapping Objects to Processors
The object model we constructed in the previous article is shown in Figure 5. We can map these classes to the deployment model as shown in Table 2.
The Concurrency Model
Some people think that the identification of tasks should take place in analysis, but I strongly disagree. In analysis, the subsumed conceptual model dictates that all objects operate independently-more or less in their own thread of control. Of course, actually implementing the system in this manner is inefficient, so we introduce a smaller set of threads, in each of which a number of objects execute. The set of threads is only selected for optimization reasons, not because the set of threads is in any way part of the essential logical model of the system.
For the concurrency model of the Gasp-o-matic, we’ll use the preemptive scheduling pattern.15 This pattern has a number of advantages, including that we can mathematically prove schedulability given certain assumptions.16 But that still leaves a big question unanswered-namely, how do we identify an appropriate set of threads and map objects into those threads?
In one of my books, I identified nine different strategies for the identification of task threads.17 Let’s use a couple of those strategies and group objects into threads based on:
- Handling of information to/from different devices
- Special purpose components, such as alarm handling
We can map the objects on each of the processors to a thread using these policies, as shown in Tables 3, 4, and 5. As design progresses, a need for additional threads-or even a reorganization of threads-might appear for a variety or reasons, such as newly uncovered objects, inadequate performance, and so on. Naturally, other sets of threads are equally possible, using a different combination of thread identification strategies.
Showing threads via a task diagram
The UML uses the ýactiveý object stereotype to represent an object that is the root of a thread. As we discussed in the first installment, a stereotype is a subclass of an entity within the UML metamodel itself. The ýactiveý stereotyped class is typically a composite class that tightly aggregates its components. The ýactiveý object creates and destroys those components, and the components execute within the thread of their composite ýactiveý object.
We can graphically depict these objects on a task diagram, which is an idiomatic usage of a UML object diagram on which only ýactiveý objects are shown. Such a diagram is shown for the ventilator display processor in Figure 6.
Global Memory Management
The remaining architectural design issues with which we’ll deal are memory management and processor communication. Real-time systems have a number of important concerns with respect to memory management:
- Garbage collection isn’t practical because it’s non-deterministic
- Preallocation of all objects in a system of this complexity isn’t usually practical either; objects will need to be created and destroyed on-the-fly
- In a system that must run for long periods of time, dynamic memory allocation is problematic, as it can lead to memory fragmentation, a potentially system-halting problem
- Dynamic memory allocation can be a nondeterministic process by itself when the heap manager must search for a block large enough to satisfy a request
The solution we’ll use is the Fixed Size Block Pattern, shown in Figure 7. This pattern helps us because it doesn’t require garbage collection or preallocation. Rather, the pattern uses multiple heaps, each of which manages memory blocks of a specific size. This bounds the length of time it takes to find a block of the correct size (it becomes an O(c) process, where c is a constant). Even more importantly, it eliminates memory fragmentation altogether. And it’s a feature provided by most RTOSes.
Since we have multiple processors, we’ll have to provide some means for the objects on each to send messages to the others. For example, note that on the class diagram Alarm is associated with an AlarmView, but they are on different processors. What global policy should we have to allow these objects to send messages to each other?
We could use any of a number of possible approaches here, including the Broker Pattern to manage communications. The Broker Pattern puts an "object repository" in the system, called a broker, with which objects register when they’re created. When one object sends a message to another, the broker intercepts the message and routes it to the actual location of the desired object. This relieves the sender from knowing the address of the receiver. Some brokers allow work by letting the sender get the object address from the broker, but otherwise aren’t involved in forwarding the messages.
Using a broker makes a system much more flexible and maintainable once it gets above a certain level of complexity. In this case, however, to keep the scope and complexity of the system down for the purpose of this article, we’ll make it easy. Each class that must communicate to another remote object will know the processor on which the latter is located, as well as the object ID of the object. We’ll do this by subclassing from a class that knows how to construct, send, and receive messages. Each processor will have a communication subsystem that routes the messages to the specified processor. On the receiving end, the communication subsystem routes the message to the appropriate object. Because this is the general means for communicating across processors, it’s an architectural design decision. However, we’ll leave the definition of the communicating object superclass until the next section.
Each processor has its own communication subsystem. One of the jobs of this subsystem is to map incoming messages to active objects residing on the processor. Let’s ignore the routing of messages to the appropriate processor (there’s lots of simple solutions for that) and concentrate on how the communication subsystem can send on a message that it receives from a remote object to the correct local object.
We’ll give a name to objects in the Gasp-o-matic that can send and receive messages to and from other processors-communicating objects (COB). In the next section, we’ll create a superclass that encapsulates the behavior common to such objects and give it that name.
Each communicating object will, as part of its constructor, register itself with the communication subsystem. The object’s destructor will de-register itself. The communication subsystem will then maintain a repository of currently active communicating objects. In order to receive remote messages, each COB has a unique attribute, a COB-ID. To make life easy, COB-IDs will be unique system-wide and assigned at compile time. The COB-ID may be a 24- or 32-bit number broken into fields, such as processor ID (PID), relative object ID (ROID), or sub-object ID (SID)-or a unique system-wide number can be used.
This ID will be enforced by not providing a default constructor, and forcing each COB subclass to have at least one argument-its COB-ID. When the object is created, it will register with the communication subsystem by providing both the COB and a pointer or reference of some kind. The COB will also provide a virtual accept() method that will be overridden in the derived class. This will allow the communication subsystem to pass any message it gets to any COB within the processor by (1) looking up the COB-ID, (2) dereferencing the pointer, and (3) calling the accept() method of the relevant object, passing the incoming message to it.
The preceding scheme bears the advantages of simplicity, good scalability, and reasonable flexibility. More flexibility and scalability would be provided through the use of an object broker, as in the Broker Pattern.18 However, this solution is more complex, requires much more overhead during run time, and is generally non-deterministic.
Mechanistic Design of the Gasp-o-matic
Mechanistic design involves the refinement of the interactions of clusters of objects that collaborate together. This refinement normally involves the addition of new "glue" objects to facilitate or optimize the execution of the mechanism. This level is the most common in which design patterns are applied.
The mechanistic design concerns on which we’ll focus here are:
- How should we manage the different 1-* object collections?
- How can we help prevent dangling pointers and memory leaks as objects are created and destroyed?
- How should the objects that have associations with remote objects communicate?
Collection management is commonly handled through the use of the container pattern, as shown in Figure 8.19 This pattern works by abstracting away the concepts involved in the management of collections of objects and encapsulating them within a "glue" object called a container. The container knows how to insert, remove, search, sort, and so on, depending on the type of container. The user of the container (the client in the pattern) uses another object, an iterator, to access objects held by the container. The use of iterators allows multiple clients to access the container without stepping on each other’s toes, as well as to provide a single client with multiple interfaces to the server, if necessary.
In the Gasp-o-matic problem, note that the analysis model in Figure 5 has two associations with nonunary multiplicities. The alarm manager must manage multiple alarms and the AlarmListView must manage multiple alarm views. More opportunities for this pattern will no doubt arise as we progress in the design. For example, the communications subsystem to which I alluded earlier will no doubt have to manage multiple messages.
The choice of the kind of container depends on the kind of access you’re trying to optimize. If the structure will be written infrequently, but read accesses must be very fast, then a balanced binary tree (such as an AVL tree) is a good choice. Due to the overhead of balancing the tree, adding or deleting tree nodes is relatively costly in such structures, but searching is fast: O(log(n)). On the other hand, if the objects will always be accessed as a FIFO, then a queue is an appropriate container. If the access will usually be random rather than systematic, then the searching should be fast; but if the accesses will usually be sequential, then a simple list might be best.
Let’s look at how the alarm manager will access alarms. First of all, alarms constantly come and go during use of the system. Alarms will be added when an object detects a dangerous condition, an out-of-range value, and so on. When conditions improve or are fixed, then alarms will go away. The alarms will not be removed in sequence, but when the alarm condition itself clears.
The alarm manager might also have to apply filtering rules to alarms so that only a single alarm is raised for a particular condition. For example, if a hose begins to leak in the inspiratory limb of the breathing circuit, the airway pressure (Paw) parameter might raise a low-pressure alarm and the CO2 parameter might raise a low-CO2 alarm in the expiratory limb, because of inadequate pressure to ventilate the patient. Annunciating both alarms would be counter-productive. The alarm manager might therefore apply a set of filtering rules to reduce the number of nuisance alarms. This allows the user to focus on the most valid alarm and more quickly identify and fix the problem.
Another reason for the alarm manager to search the alarm list will be to remove old alarms.20 Therefore, the alarm manager must search the alarms frequently, but because it must search the entire list each time, the list needn’t be maintained in an ordered fashion. In this case, an array could be used, but because alarm removal will leave gaps in the middle of the array (requiring a search to find the "next open spot" for insertion), a linked list is probably the most appropriate container class.
Now let’s consider the nonunary association between the AlarmListView and the AlarmView class. AlarmViews are displayed in sorted order. They are sorted first by criticality (critical, then caution, then informational), and secondarily by time of occurrence. The alarms will continue to be added and removed dynamically, but the collection must be maintained in order. Although a linked list could work in these circumstances, a balanced binary tree would probably have better overall performance. These design additions are shown in Figure 9.
Is the arrangement in Figure 9 a reasonable one? Let’s explore it by using a scenario. We’ll add an informational alarm, followed by a critical alarm and see if the objects involved can all meet their responsibilities. This scenario is shown in Figure 10.
This scenario shows the addition of two alarms followed by the user pressing the silence button (also known as "Alarm Acknowledge"). The scenario includes the use of the containers Alarm_List and AlarmView_Tree. Notice that the construction of this scenario forced us to examine what happens when the alarm silence button is pushed. How should the alarms be silenced? The specification given last month said that all viewed informational alarms would be removed-critical alarms would stop being annunciated for a two-minute period of time, even though the condition persists.
The alarm class itself is reactive, meaning that it has state behavior. Last month we examined the state behavior of the alarm class and its subclasses InfoAlarm and CriticalAlarm. The state machines for these classes are shown in the statecharts in Figures 11, 12, and 13.
Clearly, these objects with these state machines must interact with other objects. The alarm statechart, for example, acts on the acknowledge event. This action can occur with the design as it exists already because we have an acknowledge method. This method can act as an event acceptor, causing the state machine to accept and process this particular event. The alarm class also responds to the WasSeen event, which occurs when an alarm view is displayed by the AlarmListView class. How should this event acceptor work?
The most natural way is to add a WasSeen method to act as an event acceptor for this event. The AlarmView object associated with a particular alarm knows that it’s being viewed (a reasonable thing), and must be able to communicate this with the alarm object. In fact, an association already exists between these classes in the analysis model for precisely that reason. If these objects were executing within the same thread and on the same processor, this would be most likely implemented as a simple method call. However, life is not so easy. We’ll talk about sending messages a little later in this section.
Note that an infoAlarm object must also act on a timeout message. Specifically, if it is unacknowledged for InfoCycleTime seconds, it should be removed (see Figure 12). How should this work?21 As always, there are many ways to solve this design problem. One way is to allocate a timer for every InfoAlarm and CriticalAlarm (they, too, accept timeout events). This naýve approach has the virtue of simplicity, but it is probably an inefficient use of RTOS resources.
Another approach is to have a single-event timer for each processor and build some machinery to check each currently operating state machine to see if it is ready for a timeout event. Or each object could compute when it would time out, and each object would be notified on a periodic basis what the current time is. Yet another approach is to use the RTOS pend mechanism to wait for a particular time to occur.
In the approach we’ll take, the Alarm Manager will accept time ticks from the RTOS timer. The Alarm Manager will then see whether any alarms are ready to accept a timeout event.
Having the alarms managed on one processor and their views on another isn’t unreasonable, but it does raise the issue of how these objects will communicate. In the section on architectural design, we decided to implement a communication subsystem, such as a TCP/IP protocol stack on each processor. Assuming that we might buy such a component, we still have to think about how the application objects (AlarmView, for example) will send messages off to remote application objects (such as Alarm). In the previous section, we elaborated on how the communication subsystem residing on each processor manages communications. Events can be treated as a particular type of message and sent using the very same mechanism.
Smart Pointers
Pointers provide a simple and powerful programmatic mechanism to reference objects, but their use is fraught with problems-dangling pointers, memory leaks, and so on. Pointers have no automatic means for ensuring that they are properly initialized, or that their memory is properly destroyed. The use of automatic pointers in C++ programs is, in fact, almost always an error because there is no way to guarantee that the objects to which they point will be destroyed when an exception is thrown.
The smart pointer pattern wraps pointers inside of classes. Because classes have constructors and destructors, their pre- and post-conditional invariants can easily be enforced. Invalid access can be stopped before it starts and the most common single source of C and C++ coding bugs can be plugged through the use of smart pointers. Smart pointers are so common, the standard library template (STL) even includes a smart pointer template called .
Detailed Design of the Gasp-o-matic
The detailed design concerns we’ll examine include the internals of a number of objects, which include details about:
- Mapping logical messages from the analysis model to object operations
- Support for safety and reliability with visibility and encapsulation, and optimization with derived attributes
Most objects are pretty simple internally. A few, though, always require significant work to make sure you got it just right. This might be because a complex algorithm is encapsulated, such as a fast Fourier transform (FFT) or Kalman filter, or because the internal data structuring is complex. For whatever reason, a small but important set of objects must always be dealt with in detailed design.
Mapping Messages to Methods
Several policies are used to map messages to class methods. The most common is a 1:1 map-each message that is identified in analysis and earlier design results in a method in the class of the same name. This policy is also common for attribute accessors and mutators. It works well when the messages come from objects within the same thread. When the messages come from objects in other threads or other processes, this approach is problematic. It’s possible to build mutual exclusion semaphores into the methods to ensure data integrity in the face of access across concurrent threads. Another approach is to use a single acceptor that parses the incoming message and dispatches it to an internally visible method. This approach is commonly used to handle accesses across processor boundaries.
Ensuring Data Integrity
In a safety-critical system, ensuring that the data is correct is important. If the user sets a tidal volume to 10 breaths per minute, you don’t want noise from an electrosurgical unit to change the value to 230.22 How can we ensure that we can detect and handle randomly changed values?
One solution is the application of the Checked Data Pattern.23 In this pattern, the object stores the data redundantly and checks the data for corruption on every read access. Following the recommendations of the TýV safety organization, two derived patterns are common: the One’s Complement Checked Data Pattern and the CRC Checked Data Pattern. In the former case, the safety-relevant data is stored once in normal format and once in its one’s complement form when the information is modified. Whenever the data is read, the accessor function reads the one’s complement version, inverts it, and compares it to the normal version. If they are the same, the value is returned. If they differ, then an exception is raised.
The CRC Checked Data Pattern works similarly, but instead applies a 16- or 32-bit CRC against the data. This format is used when the amount of data being protected during a single read access is large.
Other detailed design work includes refining visibility and accessing issues, adding parameter range, checking on input data, and so forth. Often this work is done in code, but at least some of it should be reflected in the design as well.
Sufficient for Real-Time Things
In this three-part series, we’ve covered a lot of material. In the first article, we took a cursory look at the UML diagrams, graphical syntax, and some of the important semantics. The most important diagrams for embedded systems folks are class diagrams, used to capture object structure, and Statecharts, used to capture reactive behavior. The use case and sequence diagrams are also very useful for exploring and extracting requirements. The deployment diagram is a "software" view of the hardware platform, useful for mapping software components to the hardware. Other diagrams exist within the UML, and the interested reader is encouraged to scour the references for all the details.
In the next article, we explored how the basic language defined by the UML could be used in a more-or-less real situation. So last month, we applied the UML to a real-time embedded system-the Gasp-o-matic. With use cases and scenarios, we extracted requirements and started to understand our problem. Then we applied a couple of object discovery strategies and built up a class diagram. We noticed that a number of these classes had interesting reactive behavior, and so we constructed statecharts for them.
In the last installment, we moved on to the design of the Gasp-o-matic. We looked at some of the architectural issues that surfaced and resolved them. Other architectural decisions were possible, but we weighed them and made our choices. We then looked at smaller collaborations of objects.
It’s clear from looking at this somewhat simplified example that OO methods in general, and the UML in particular, are an effective way for analyzing and designing real-time embedded systems. The UML has facilities to help with requirements identification and extraction, rigorous definition of behavior, and capturing object structure and relationships. It is supported by all major CASE tool vendors and is quickly replacing the older approaches such as the OMT, Booch, and Shlaer-Mellor methods.
As one of the people on the UML design committee, I tried to ensure that the needs of the real-time embedded community were considered in as much detail as those of other groups. As Grady Booch has noted, "The basic UML is sufficient for real-time things."24 I consult for a wide variety of companies who develop products from medical infusion pumps to advanced avionics systems, and the UML helps to model and develop systems that you can bet your life on.
Bruce Powel Douglass has almost 20 years of experience designing safety-critical embedded applications in a variety of hard real-time environments. He is on the Advisory Board of the Embedded Systems Conference and was on the oversight committee involved in the definition of the UML. His latest book, Real-Time UML: Developing Efficient Objects for Embedded System (Addison-Wesley-Longman, Reading, MA) is now available. He can be reached at bpd@ilogix.com.
References
1. For more detail, see Douglass, Bruce Powel. Real-Time UML: Developing Efficient Objects for Embedded Systems. Reading, MA: Addison-Wesley-Longman, 1998.
2. For more detail, see Douglass, Bruce Powel. Doing Hard Time: Using Object Oriented Programming and Software Patterns in Real Time Applications. Reading, MA: Addison-Wesley-Longman, 1998.
3. Douglass, Bruce Powel. "Real-Time Design Patterns," Embedded Systems Conference Proceedings, Spring 1998.
4. Gamma, Helm, Johnson, and Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
5. Speculation varies widely on just how limited that is.
6. The technical term for this is "software wedgie."
7. This will be obviated in our mechanical design by using incompatible connectors for the different gases.
8. See Douglass, Bruce Powel, "Reliable and Safe: Patterns and Practices for Designing Mission and Safety-Critical Systems," Software Development Conference Proceedings, Spring 1998, or
9. Storey, Neil. Safety Critical Computer Systems. Reading, MA: Addision-Wesley, 1996.
10. Douglass, Real-Time UML.
11. Douglass, "Real-Time Design Patterns."
12. A channel, in this context, is a set of software and hardware components that work together to achieve a particular purpose by sequentially routing data and control.
13. The overpressure valve will be implemented as a mechanical component in this case.
14. One of the problems with the Therac-25, a radiation treatment device that killed several patients, was that rapid repeated keystrokes could crash the system causing fatal radiation overdoses to occur.
15. Douglass, "Real-Time Design Patterns."
16. Klein, et. al. A Practitioner’s Guide for Real-Time Analysis. Norwell, MA: Kluwer, 1993.
17. Douglass, Real-Time UML.
18. Ibid.
19. Ibid.
20. Remember from last time that some alarms must be kept around for some period of time after they cease to be active to provide alarm hysteresis, should the condition reassert itself within a short period of time.
21. If you use a commercial OO design automation tool, all the machinery for managing events and timeouts is automatically generated from the statechart. If you’re working manually, as is assumed here, then you must build this machinery yourself.
22. Operating rooms have notoriously noisy power systems and harsh electrical environments.
23. Douglass, Doing Hard Time.
24. Private communication.