© Leon Starr, Andrew Mangogna and Stephen Mellor 2017

Leon Starr, Andrew Mangogna and Stephen Mellor, Models to Code, 10.1007/978-1-4842-2217-1_6

6. An Extended Example

Leon Starr, Andrew Mangogna2 and Stephen Mellor1

(1)San Francisco, California, USA

(2)Nipomo, California, USA

We presented the simple ATC application as an extended hello world example to demonstrate the fundamental steps in a pycca translation. That initial application addressed a small portion (workloads and shifts) of an isolated subject matter (air traffic control). It was defined by an integrated set of class, state, and action facets wrapped up in a single executable model package. In this chapter, we begin a new case study that requires coordination of multiple such executable packages which, in Executable UML, are called domains.

The code generated for the ATC application works, but it doesn’t do much. That’s because the application itself is only one portion of any deployed software system. You may have wondered, for example, how radar, user displays, alarm handling, logging, and other essential functionality would be incorporated into the ATC system.

Of course, you could simply extend the application models to incorporate all the required functionality, but in practice, this is a bad idea. The models would sprout excessive complexity, not just in the number of elements, but in the explosion of state combinations and awkward class relationships. The result would be an inconsistent set of abstractions in the model, and the entire analysis effort would bog down. This is symptomatic of what was called analysis paralysis at the time when modeling systems in this way was first undertaken.

It is better practice to organize the system into a set of relatively isolated domains. A domain represents a distinct set of abstractions focused on a particular subject matter. The ATC models, for example, constituted an application level of abstraction with regard to the subject matter of air traffic controller duties. Radar tracking would be an entirely different subject matter, as would the user interface and alarm handling. You can imagine entirely different class vocabularies in each model set and wholly different relationships and policies within each. The internal workings of each domain become opaque to adjoining domains, with only the fundamental services exposed via a variety of runtime and pre-runtime bindings.

A domain is a fully executable package that can be run and tested on its own. It may be modeled in Executable UML, another modeling language such as Simulink, or simply hand coded. Pycca can knit together Executable UML and domains hand coded in C. But if you do use another modeling language and can output the result in C code, you can use pycca to fold in the code.

The many benefits to a domain-based organization of a system (such as reduced complexity, platform independence, firewalling against changes, large-scale reuse, and so forth) are beyond the scope of this book. However, there is one benefit pertinent to model translation worth emphasizing here: domains provide a systematic way to bridge the chasm from the application level of abstraction to a complete implementation. This is done through a process of delegation. What one domain cannot do, it delegates to another. Where one domain lacks expertise, another domain must provide it. This chain of reliance continues until we get to the point where there’s nothing left to delegate. In other words, we have a complete running system.

For example, the models in the ATC domain knew when a controller was working too long without a break. But they did not know how to manifest this warning in the real world. That could be the job of a user-interface domain that can make events visible, or audible, or otherwise tangible in the real world. The UI domain should not have any built-in application-specific knowledge; otherwise, it would have little utility for other applications. Assuming for the moment that the UI domain is implemented as an existing set of libraries, there is no need to model ATC further. It will be necessary to configure only those UI libraries with information supplied by the ATC models and then somehow wire ATC events to the appropriate UI library calls. As you’ll see, pycca provides the somehow. On the other hand, a radar domain that knows all about tracks, signals, echo profiles, and such may need to be modeled to support the ATC domain with information about air traffic.

Ultimately, the ATC domain, and any other modeled domains, have their models run by the Model Execution (MX) domain, where the execution of models has been delegated. To sum up, when one domain delegates functionality, another domain must fill in the required services. This keeps happening until we hit solid ground, such as the MX domain or another existing domain. There is no unspecified layer where magic happens. All the software is deemed to live in a domain or is bridging code between domains.

From a model translation perspective, we need a way to specify the organization of models into domains and how domains, modeled or otherwise, interact with one another to define a complete software system. Almost all systems built from models consist of a combination of code generated from the models and manually written code, either existing or newly written. To see how all that works, we need to examine and generate code for a multidomain example, which is the purpose of our next case study.

The Automated Lubrication System

The Automated Lubrication System (ALS) is a service that controls the lubrication of mechanical equipment such as a vehicle engine, gear train, or heavy-lifting machinery. Although the applications of automated lubrication can vary considerably, the fundamental idea is a series of grease injectors, installed around and within the user’s equipment and controlled according to a programmable cyclic schedule.

Now, were it not for the potential, and likelihood, that things could go dangerously wrong, this would be a trivial system. But the ALS must carefully monitor pressures throughout the system to ensure that equipment is not damaged and that lubrication is performed adequately. The system must react to events that occur in the equipment and may permanently or temporarily shut down lubrication. For example, an idling engine may require different lubrication than one that is revving at high revolutions per minute (rpm).

The operator must be notified about routine maintenance, such as refilling lubricant, as well as minor and serious faults. And the operator must have a way to lock out unwanted injections when a piece of equipment is under maintenance. Care is taken to isolate units of equipment so that one can be locked out without shutting down lubrication everywhere. It is also necessary to support various diagnostic modes so that the operator can experiment with different lubrication parameters. To support a variety of user equipment and applications, critical operating parameters must be configurable.

As an example of one possible user application, Figure 6-1 shows how the lubrication of a wind turbine might be configured.

A421575_1_En_6_Fig1_HTML.jpg
Figure 6-1. Injector layout diagram

This wind turbine is lubricated in five locations delivered by three injectors. Injector 1 (IN1) is dedicated to the gearbox designated as one unit of machinery (M1), while injector 2 (IN2) lubricates the main shaft, designated M2. A third injector (IN3) lubricates a single location on the generator (M3). Note that injectors 1 and 3 not only share the same grease reservoir; they are identical designs. Injector 2, on the other hand, is a different model of injector, designed to deliver a special lubricant under higher pressure for the main shaft.

Each injector is driven by a dedicated lubrication cycle. All injectors are rigged with sensors to detect pressure. Lubrication is started and stopped by enabling and disabling each injector’s solenoid. Each injector is supplied by a lubricant reservoir. Injectors 1 and 2 are delivering lubricant to multiple sites. The delivery lines to these sites are not individually controllable, so each injection has the same effect on each line. Note also that each injector is dedicated to a single unit of machinery. This makes it possible to enable a safety lockout on one unit of machinery to stop lubrication without necessarily affecting the lubrication of neighboring machinery.

ALS Domains

Before jumping into the models, let’s take a look at the domains necessary to support the ALS, as shown in Figure 6-2.

A421575_1_En_6_Fig2_HTML.jpg
Figure 6-2. ALS domain dependencies
  • ❶ At the top level, we have the Lubrication domain. It is concerned with the primary purpose of our system, which is to manage the injectors to schedule cyclic lubrication. This domain is modeled.

  • ❷ The Lubrication domain needs up-to-date pressure values and events announcing key pressure thresholds. It also needs a way to convert a modeled event such as “Turn injector on” into an appropriate signal or command to the injector solenoids. Signal I/O (SIO) provides this service as indicated by the dashed dependency arrow. The SIO domain is shown in Chapter 7.

  • ❸ The Lubrication domain signals alarm conditions, but needs a way to manage systematic setting and clearing of alarms as well as management of alarm categories such as operator warnings, errors, and log items. The Alarms domain handles this job.

  • ❹ The Lubrication domain needs to know when to activate and deactivate lubrication schedules. It also needs a way to signal completion of schedules and other events to a human operator. It uses the UI domain for this purpose.

  • ❺ The Alarms domain needs to display alarms to a human operator and allow alarms to be cleared. It also uses the UI domain.

  • ❻ All modeled domains are translated to run on the Model Execution domain and need its services to implement model-level actions.

  • ❼ The Model Execution domain is coded in C. Yes, the C language is also a domain. This might seem strange at first, but C is its own distinct subject matter. It isn’t modeled in Executable UML, of course, but the C language is modeled to some degree as a grammar. The entire interface to the C domain is defined pre-runtime by writing a program. The more you think about it, the more you realize there isn’t anything special at all about a program language as a subject matter.

As you can see, this arrangement of domains creates a complete path from the application-level abstractions of the Lubrication domain through generic utility services, model execution, and ultimately, code. Each domain, modeled or not, represents a distinct subject matter. Note that domains yield a largely platform-independent partitioning. The underlying hardware, devices, and user-interface technology can change, but the subject matter organization remains intact. Contrast this with many traditional high-level software architecture diagrams, which are almost always platform specific.

Whether you view the domain interactions in a layering manner or as dependencies between domains, it is important to realize that neither view represents a functional partitioning of the system, also known as functional decomposition. Subject matter partitioning is based on cohesive classes and relationships without regard to specific functions. Each class model defines a vocabulary and rule set that defines the subject matter of a domain. Dividing a system by common data and rules is markedly different from dividing it by common functions. Domain partitioning is based on subject matter, and the analysis of the subject matter of a domain seeks to find a consistent set of abstractions directed to the role the domain plays in the overall system. The functionality of the system is manifested when the model executes, and any given functional feature of the system generally requires multiple domains, each to play its role. For the ALS, injecting lubricant involves both the Lubrication domain to determine the appropriate time and the SIO domain to actuate the hardware. Both domains use the Model Execution domain to interact and are ultimately coded in C.

In Chapter 8, we show how domain dependencies are specified and then implemented using pycca. For the remainder of this chapter, we examine the models in the Lubrication domain. Bridges to the SIO, Alarms, and UI domain are shown as we examine the state models and activities.

Lubrication Domain

We start by examining the Lubrication domain. This is the application level that addresses the primary business purpose of the system. All other domains are present to provide services directly or indirectly in support of the Lubrication domain’s needs. Because our purpose is to coordinate and manage lubricant injection, we would expect the application level to know about the injection equipment and the lubrication scheduling subject matter.

Lubrication Class Model

For a more readable illustration, we break the Lubrication class diagram into two adjacent pieces, Equipment and Schedule. This split is strictly for presentation purposes and has no implications on the model itself.

Figure 6-3 shows the equipment part of the class model, which focuses on physical components.

A421575_1_En_6_Fig3_HTML.jpg
Figure 6-3. Lubrication equipment classes

Lubricant is delivered to some type of equipment. This could be anything from a robot, to a jet engine, to a heavy-lifting construction vehicle. From the perspective of the ALS, the function of the lubricated equipment is not relevant, so it is represented as generic “machinery.” The particular values established for the Lubrication Schedule and Injection Spec attributes are determined, in part, by the specific needs and function of the lubricated Machinery. But this model is not concerned with the specifics of the Machinery and leaves it to a human to define acceptable values for the attributes to meet those needs. Those attribute values appear in the initial instance population for the domain.

Injector Designs

To accommodate a variety of injector designs, the model separates out those features that are particular to an individual injector and those that vary from one design to another. This is done using R4 in a common specification class modeling pattern. The detailed reasoning behind this pattern is covered in modeling books—for example, Executable UML: How to Build Class Models by Leon Starr (Prentice Hall, 2001) – See note about future update in the bibliography.

The Injectors in the example, IN1 and IN3, are both built to the same design specification, so they share the same Injector Design instance. IN2 is a different model of Injector, with a distinct set of design parameters, so it requires a separate Injector Design instance.

A Single Injection

We have enough of the model to consider the behavior of a single injection. To reduce the load on power and the local computer and network resources, an injector sits quietly until commanded. It does not even monitor internal pressure because it is unlikely that dangerous pressure will build up when the injector is inactive. Figure 6-4 shows a typical injection sequence.

A421575_1_En_6_Fig4_HTML.jpg
Figure 6-4. Example good and bad injection sequences

The Wakeup command causes the injector to begin monitoring pressure for error-reporting reasons. Ideally, the pressure built up from the previous injection will have dissipated by now, but that is not always the case. If any high pressure is detected, we want to report a warning. Regardless of how much the pressure fluctuates, at most one warning should be issued.

This monitoring interval ends when the Start command arrives. Now it’s time to inject lubricant. To do this, the Injector Design.Min delivery pressure must be achieved and maintained for the Injector Design.Good injection duration. Upon success, a good injection is reported, and the injector goes inactive again. Otherwise, each time the pressure drops too low, it is necessary to wait until it builds up and try again. If a Stop command is received, it means that too much time has elapsed. A bad injection is logged, and the injector goes inactive.

Controlling Lubrication Cycles

Now that we have a model of the equipment, we need to consider the cyclic schedule by which the injectors are controlled. At its simplest, a cycle of injection repeats with a specified delay between injections. A period of time must also be specified to allow the injector to begin monitoring pressure. We can also specify whether the cycle should run for a default count or continuously until stopped manually. A maximum number of low-lube cycles that will be tolerated before shutting off lubrication is also part of the lubrication program. Figure 6-5 illustrates the typical cyclic control session.

A421575_1_En_6_Fig5_HTML.jpg
Figure 6-5. A cyclic schedule of lubrication

With these concepts in mind, we can move on to the other half of thee Lubrication domain class model, which covers cyclic scheduling and control of injection. Figure 6-6 introduces two new classes to cover the scheduling requirements.

A421575_1_En_6_Fig6_HTML.jpg
Figure 6-6. The lubrication cycle classes

Lubrication Schedule

This is a program of cyclic lubrication that can be used with any model of Injector, although typically, the values will be set with a particular injector model and usage in mind. Each has a name and most important, the main interval, which determines the wait time between injections (Wait interval), and a prep interval, which determines the delay between wake-up and start (Monitor interval), is defined. If the operation should continue without any specific total, Default continuous operation is set to true. A default maximum number of cycles can be specified (Default max cycles), but it has no function if continuous operation is the default. Both of these defaults can be overridden in an Autocycle Session.

Autocycle Session

R1 designates a default Lubrication Schedule to use for each Injector. This association is consulted to choose an instance to relate along R2, where only one Lubrication Schedule can be controlling an Injector at a time, though the same Lubrication Schedule could be in use simultaneously. After a Lubrication Schedule has been selected and related across R2, an instance of Autocycle Session is created. The Autocycle Session tracks timing for a specific Injector along with a count of failed cycles.

The reason for R1 is that it is sometimes necessary to choose a nondefault Lubrication Schedule for a few cycles, often for diagnostic purposes. In that case, the previous Autocycle Session is deleted, and a new one, related to the nondefault Lubrication Schedule, is created (an Injector is controlled by only one Autocycle Session at a time). Once the temporary session has completed, usually by running through a requested number of cycles, control automatically resumes with the default Lubrication Schedule found on R1.

Example Population

The scenario illustrated in Figure 6-1 is represented by the population shown in Figure 6-7.

A421575_1_En_6_Fig7_HTML.jpg
Figure 6-7. Scenario’s initial instance population

State Models

The Autocycle Session, Injector, and Reservoir classes each have a state model. Let’s start by walking through the Injector state model. Again, the model is split to make the discussion easier.

Injector State Model

Figure 6-8 shows the top half of the Injector state model.

A421575_1_En_6_Fig8_HTML.jpg
Figure 6-8. Injector state model excerpt: Sleeping to Active
  • ❶ The Injector is in the SLEEPING state when it receives a Wakeup event, which will be sent from the Autocycle Session. The SIO domain is told to start monitoring sensor values for this Injector.

  • ❷ In the MONITORING state, the Injector isn’t doing anything, because SIO is doing all the sensor detection work. But, from this state, we react to the Above dissipation pressure event supplied by the SIO domain when it detects that pressure has risen above the Injector Design.Max dissipation pressure.

  • ❸ A Dissipation error should never be triggered more than once per cycle. To avoid raising multiple Dissipation error alarms, this state checks and sets the Injector.Dissipation error Boolean attribute value.

  • ❹ When the Start event is received from the Autocycle Session, it is necessary to verify that the target Machinery is not locked out before proceeding to inject.

  • ❺ Either a Lockout or a Stop event will put the Injector back to sleep.

Figure 6-9 shows the bottom half of the Injector state model.

A421575_1_En_6_Fig9_HTML.jpg
Figure 6-9. Injector state model excerpt: Active to Sleeping
  • ❶ The Injector solenoid is energized by sending the Inject signal to the SIO domain. From there, the Injector waits until the Injector Design.Min delivery pressure is attained.

  • ❷ This event can be triggered as a result of the check in the BUILDING PRESSURE state or supplied by SIO when the condition is detected.

  • ❸ When the pressure is high enough, lubrication is occurring. To get a good injection, this pressure must be maintained for the Injector Design.Good injection duration. This delayed event will be sent if all goes well while the Injector is in this state.

  • ❹ Otherwise, SIO will send the Below inject pressure event, which leads to the Not enough pressure state, where the delayed event is canceled and the Reservoir is notified. From there, the Injector continues to wait for the pressure to build in the next state.

  • ❺ If a long enough continuous injection is not achieved soon enough, the Autocycle Session will trigger the Stop event, which moves the Injector to the Quit low pressure injection state, possibly via the Cancel injection state, which cancels the Good injection delayed event.

  • ❻ But if all goes well, the Good injection delayed event occurs, the solenoid is de-energized via SIO, and the Injector goes back to sleep.

Autocycle Session State Model

The Autocycle Session state model is a bit large to display here, so we walk through a summary without the action language visible. Figure 6-10 highlights the primary part of the life cycle. All of the grayed-out areas pertain to suspend/resume and deactivation behavior.

A421575_1_En_6_Fig10_HTML.jpg
Figure 6-10. Autocycle Session state model overview
  • ❶ Let’s start with an active Autocycle Session in the WAIT INTERVAL state. Here, we are waiting for the delay between cycles to expire. According to R2 on the class model, there will be one such session instance per Injector at all times.

  • ❷ It is possible during this state that the session may be suspended. If this happens, we will go sit in the WAIT SUSPENDED state until resumed or deactivated. We must cancel the pending Get ready to lubricate delayed event, but not before saving the time remaining. Upon resuming, we will reschedule the Get ready to lubricate event for whatever time was remaining when we were suspended.

  • ❸ The scheduled event occurs, and we advance to the MONITOR INTERVAL state. This is where we send the Wakeup event to our Injector so it begins monitoring pressure. We now schedule the delayed Lubricate event for the interval defined in our Lubrication Schedule and wait. If suspension occurs in this state, the logic is similar to the previous, except that the intermediate time waited will not be saved.

  • ❹ The Lubricate event occurs, and we advance to the LUBE INTERVAL state. We send the Start event to our Injector and wait for one of two events. Either our Injector succeeds and it sends us the Good injection event, or too much time elapses and our scheduled Lube interval ended event occurs. The lubrication interval is determined by the value of Injector Design.Delivery window. In the first case, we can cancel the pending Lube interval ended event.

  • ❺ In the time-out case, we advance to the LOW PRESSURE LUBRICATION state, where we tell the Injector to stop and increment the Failed cycles count. The Injector will respond with either a Good injection or a Low pressure injection event. The first one can happen if our Lube Interval times out at roughly the same instant that the Injector succeeds. Either way, it is critical that we wait here for confirmation that the Injector has stopped before proceeding so that we don’t lose synchronization with the Injector states.

  • ❻ Either way, we advance to the Count cycle state, where the cycle is counted, and we check to see whether too many failed (low-lube) cycles have occurred. If so, we send the Too many low lube cycles event to our Reservoir so it can update its status. If we are operating in a continuous mode (nonstop repeating cycles), we go on to the next cycle. Otherwise, we see whether all requested cycles have been completed. If not, we also go on to the next cycle. Otherwise, with all requested cycles completed, we proceed to the NOT ACTIVE state.

  • ❼ Now let’s say that the user decides to run a different Lubrication Schedule on this Injector. We will require that the session is in the NOT ACTIVE state for this request to be processed. (It’s ignored in all other states.) A Change Schedule event will be received with the name of the new Lubrication Schedule. This puts us in a deletion state, where we fire off the New session creation event just before the present instance disappears. Entry into the NOT ACTIVE state will always start by comparing the currently controlling Lubrication Schedule with the default on R1. If they don’t match, it means that a temporary schedule has been running. This will result in a New session creation back to the designated default Lubrication Schedule.

  • ❽ A new instance of Autocycle Session is created with the input Schedule and Injector instances linked together and placed in the NOT ACTIVE state.

  • ❾ The user issues an Activate event along with the desired mode (continuous or not) and a desired cycle count, which should be greater than zero if the continuous mode was not requested. We’ll verify that we aren’t locked out by our Machinery before proceeding. If not locked out, we’ll set the appropriate attributes and jump into the WAIT INTERVAL state. Note that a lockout that occurs in any other state will be detected by the Machinery that signals the Deactivate event to us.

Reservoir State Model

The Reservoir keeps track of its fill states to provide useful alarms to maintenance. During an ordinary fluid cycle, we start out with a normal level of fluid and then, driven by Injector events, descend through the states of LOW, VERY LOW, and EMPTY. Figure 6-11 shows the complete state model for the Reservoir class.

A421575_1_En_6_Fig11_HTML.jpg
Figure 6-11. Reservoir state model diagram

Class Collaboration

The class collaboration diagram provides a nice overview of asynchronous (signal/event) and synchronous (methods invocation) interactions among state models within a domain. Class collaboration is not considered a separate facet of a domain model, because it can be derived from the contents of the three facets. But it is quite useful for devising a clean pattern of control within a domain (see Figure 14.8 on p. 245 of Executable UML: A Foundation for Model-Driven Architecture for a more complex example of control collaboration). Figure 6-12 shows how the classes of the Lubrication domain interact.

A421575_1_En_6_Fig12_HTML.gif
Figure 6-12. Class collaboration within the Lubrication domain
  • ❶ These are events signaled in the Autocycle Session state model and detected in the Injector state model.

  • ❷ This is a method defined on the Autocycle Session class. See Figure 6-13 for the method activities.

    A421575_1_En_6_Fig13_HTML.gif
    Figure 6-13. Class method activities
  • ❸ These are signals issued by Injector actions that are translated to corresponding SIO elements. The collaboration diagram makes no assumptions about the internal elements or interfaces of external domains beyond basic services provided. All we can tell from this diagram is that the events must be mapped to something in the SIO domain.

  • ❹ These events occur in the Injector state model and must be triggered by SIO. The specific mechanisms within SIO that accomplish this are unknown in the Lubrication domain.

  • ❺ Events from an instance to itself are not shown on the collaboration diagram. In this case, however, the event is sent from one instance of Autocycle Session to a different, newly created instance of the same class.

  • ❻ A processed signal value in the SIO domain is mapped to the Pressure attribute across the domain boundary. This attribute is read-only within the Lubrication domain.

Class Method and Other Activities

Activities in a domain are not limited to those within states. A class method is an instance-based activity defined on a class. A domain operation is an activity defined as part of a domain interface. An external domain can invoke another domain’s operation to invoke a class method, for example, without interacting with any state machine.

Figure 6-13 shows the methods defined on each of the Lubrication domain classes.

State Tables

A table for each state model must be completed before we are ready for pycca. The tables are filled out during the modeling process, and they must be complete before we begin specifying the translation. Some drawing tools will do this work for you.

As the Autocycle Session and Injector state tables are rather large, we examine selected excerpts in this chapter. You can download the tables as fully commented spreadsheets from the book’s website if you wish to study them further.

Figure 6-14 is the first excerpt from the Injector state table.

A421575_1_En_6_Fig14_HTML.jpg
Figure 6-14. Injector wait states

You may have noticed that some state names in the diagrams are named with all uppercase letters, whereas others are mixed case. This is an informal style for distinguishing wait and transitory states. A wait state is a state with an activity that does not send an event to itself to force a transition to another state. Instead, an instance that has completed its activity will either process a pending nonlocal event (an event not from this instance or delayed) or just wait for such an event to occur. Less formally, you can think of wait states as those in which an instance is waiting on a process to complete in the physical world, in some other domain or in some other instance, or for a delayed event to happen.

This means that all local events (nondelayed, self-to-self events) are marked Can’t Happen (CH) for wait states. That’s because a local event is generated in another state and processed by that same state, so it can’t happen here. So we can exclude all the local events, obtaining a smaller state table, and concentrate on nonlocal events.

Note the inclusion of the single delayed Good injection event. We can mark it as Can’t Happen in all states other than INJECTING AT PRESSURE if we can demonstrate that the event will either expire or be canceled before entering any of those other states. The format of the state table forces consideration of this possibility that is so easily overlooked otherwise!

Figure 6-15 shows the second state table excerpt for the Injector.

A421575_1_En_6_Fig15_HTML.jpg
Figure 6-15. Injector transitory states

All states in the preceding table are transitory, which means that each state is exited on a local (nondelayed, self-to-self) event. A transitory state makes it possible to execute an activity on one transition path leading to a wait state, thus allowing the possibility of other transition paths to that same state where different or possibly no activities are performed. Often transitory states are used to perform if-then logic leading to alternate exit transitions.

Note that each transitory state responds only to an event it sends to itself, for the following reasons:

  • The event will always be signaled by the state activity.

  • Events that an instance signals to itself are dispatched before any other event.

This means that all other events may be marked CH, as shown in the preceding table excerpt. Because CH will be marked for all the nonlocal events shown in the first excerpt, there isn’t much insight to be gained from viewing that part of the table.

Figure 6-16 shows the wait state excerpt of the Autocycle Session class.

A421575_1_En_6_Fig16_HTML.jpg
Figure 6-16. Autocycle Session wait states

The three delayed events are highlighted in the rightmost columns. Care has been taken to ensure that each delayed event is managed so that it does not occur in any state where it is not helpful.

Finally, Figure 6-17 shows the transitory states of the Autocycle Session class.

A421575_1_En_6_Fig17_HTML.jpg
Figure 6-17. Autocycle Session transitory states

The Reservoir state table is small enough to show entirely in Figure 6-18.

A421575_1_En_6_Fig18_HTML.jpg
Figure 6-18. Reservoir state table

Translating the Lubrication Domain

We undertake the translation of the Lubrication domain in the same way as we translated the ATC domain—namely, we transcribe the three facets into the pycca DSL. We apply the same decision process we used for the ATC domain to determine the attributes and how references between pycca classes will work. The state model is transcribed into transition and state statements, and the action language for each state is translated into C with the help of the pycca macros to handle model-level requests.

Rather than repeat sequences that have already been seen, we will focus on pycca constructs that have not already been presented. As always, the complete model and its translation are available as part of the online materials for the book. In this section, we start with translating associations that have an association class, followed by the translation of creation events and other operations as they arise in the Lubrication domain.

Translating Association Classes

In Chapter 3, we discussed implementing associations by decomposing the link storage based on each direction of traversal of the association. This was illustrated in the case of a simple association in Figure 3-5.

Referring to Figure 6-6, R2 is formalized by the Autocycle Session class. Not only does this class have referential attributes to formalize R2, but it has descriptive attributes and a state model. Figure 6-19 shows how R2 is decomposed into two sides with the Autocycle Session class serving as an intermediary.

A421575_1_En_6_Fig19_HTML.jpg
Figure 6-19. Decomposition with an association class

The R2 association has several interesting properties:

  • Each Autocycle Session instance corresponds to an instance of the R2 association itself.

  • Autocycle Session has two singular, unconditional references, one to Injector and one to Lubrication Schedule.

  • The conditionality and multiplicity of the two sides are inverted in the decomposition. So Lubrication Schedule is related to Autocycle Session as “0..*” and Injector is related to Autocycle Session as 1. This is a consequence of the singular, unconditional reference from Autocycle Session to each participant of R2.

When translating the Lubrication domain, we use the decomposed form of R2 to determine how to set up the references. For the Autocycle Session class, this appears as follows:

class Autocycle_Session
    reference R2_INJ -> Injector
    reference R2_LBS -> Lubrication_Schedule


    # Other attributes and state model for Autocycle Session
End

Navigating the R2 association is also a two-step action. So, to navigate from an instance of Injector to an instance of Lubrication Schedule requires the following:

// Assuming inj holds are reference to an injector instance.

ClassRefVar(Lubrication_Schedule, sched) = inj->R2->R2_LBS ;

Navigating Associative Relationships

In the previous section, we showed a simple example of navigating an associative relationship. In that case, the multiplicity from the participating class to the association class was singular. In this section, we show a many-to-many associative relationship using a linked list to implement the linkage with the association class. Recall that in pycca, we treat associative relationships as being composed of two separate paths. Consequently, the translation of navigation for an associative relationship requires two parts. Consider the association shown in Figure 6-20.

A421575_1_En_6_Fig20_HTML.jpg
Figure 6-20. Navigating an associative relationship

The decomposition of the associative relationship results in singular references to each participant in the association class:

class Track
    reference R2_Satellite -> Satellite
    reference R2_Station -> Station
end

The participants have their own links to the association class. Here we have chosen linked lists in anticipation that the association is dynamic:

class Satellite
    attribute (char const *ID)
    reference R2_Satellite ->>l Track
end


class Station
    attribute (char const *Name)
    reference R2_Station ->>l Track
end

Note that we have appended the class name to the reference names for each participant. We need to have different reference names because pycca will use those names directly in the generated structure, and we don’t want to have a naming conflict. We have chosen to append the class name because it is a convenient convention that lets us easily keep track of where the references originate or terminate.

Pycca will generate the following structures for the participants:

struct Satellite {
    struct mechinstance common_ ; // must be first !
    char const *ID ;
    rlink_t R2_Satellite ;
}


struct Station {
    struct mechinstance common_ ; // must be first !
    char const *Name ;
    rlink_t R2_Station ;
}

The preceding rlink_t R2_XX members serve as the terminus of a linked list and have the following structure:

typedef struct rlink {
    struct rlink *next ;
    struct rlink *prev ;
} rlink_t ;

The Track structure will have linked list pointers inserted into its structure for the linked list references defined by both the Satellite and Station classes:

struct Track {
    struct mechinstance common_ ; // must be first !
    struct Satellite *R2_Satellite ;
    struct Station *R2_Station ;
    rlink_t R2_Satellite__links ;   // ❶
    rlink_t R2_Station__links ;     // ❷
} ;
  • Track instances are linked together from the Satellite side, and this list contains those instances of Track related to an instance of Satellite.

  • ❷ Likewise for the Station instances. Note that the two linked lists plus the singular reference to the other participant gives us the many-to-many representation required by the association.

To navigate the association from Satellite to Station, we must first navigate to Track instances and from there follow the singular reference to a Station instance. Because the R2 association is many-to-many, we can have many Track instances related to a given Station instance, and so we need an iteration loop as we navigate from a Satellite instance to the related Track instances.

For a given instance of Satellite, to print the names of all the Stations, we would use the following code:

// ... assuming self is a reference to a Satellite instance ...

rlink_t *tracklink ;                                                                  // ❶

PYCCA_forAllLinkedInst(self, R2_Satellite, tracklink) {                               // ❷
    ClassRefVar(Track, tracki) = PYCCA_linkToInstRef(tracklink,Track, R2_Satellite) ; // ❸
    ClassRefVar(Station, stationi) = tracki->R2_Station ;                             // ❹
    printf("Satellite, %s, is being tracked by, %s ",
        self->ID, stationi->Name) ;
}
  • ❶ When using linked lists, we need a linked list pointer variable to hold our place in the iteration.

  • ❷ Pycca supplies a macro to set up the iteration loop.

  • ❸ The pointers in the linked list from the Satellite instance point to the R2_Satellite__links member of the Track instance and not to the beginning of the Track instance. What we really want is the pointer to the beginning of the instance element, because that is what an instance reference truly is. Pycca supplies a macro to perform the address arithmetic to get us from the R2_Satellite__links member back to the Track instance reference. This is a common idiom when an object can be linked onto multiple linked lists.

  • ❹ Finally, we can follow the singular link in the Station direction.

Creation Events

The two techniques for creating a class instance are synchronous creation and asynchronous creation. In synchronous creation, an activity makes a direct request to the MX runtime to create an instance, and that request is fulfilled immediately. The pycca macro that supports this is PYCCA_createInstance(). Class instances created this way are placed in their default initial state (if they have a state model), but the activity of the state is not executed. For example, all the instances that make up the initial instance population are considered to have been created in this way (there is no runtime cost for creating the initial instance population, because pycca arranges for the initial values to be placed in memory at compile time).

For the asynchronous case, instance creation is triggered by signaling a special creation event. The activity that signals this event continues to execute to completion (as per our usual execution rules). At some time in the future, when the event is dispatched, a new class instance is created, and the event is received by the newly created instance. From the newly created instance’s perspective, this is just a normal event. Consequently, the newly created instance makes a state transition, and the activity associated with the new state is executed (again per our usual execution rules of state machines).

From the modeling point of view, both techniques for instance creation have their uses. For the Lubrication domain, asynchronous creation is used to switch lubrication sessions. If you look back at Figure 6-10, at the top of the diagram the New session event causes a transition from a solid circle into the Creating state. The solid circle represents an initial pseudo-state, and the New session event is a creation event. The MX runtime will ensure that when the New session event is dispatched, an instance of Autocycle Session is created, it is placed in the initial pseudo-state, and the transition to the Creating state is taken, causing the activity to be executed.

Pycca has direct support for specifying a creation event. Next we show the required pycca statements. We have omitted much of the surrounding domain definition as well as the details of the Autocycle Session class definition to focus on the creation event:

# ... other parts of the Lube domain

class Autocycle_Session
    # ... attribute definitions, reference definitions, etc.


    machine
        default transition CH                            # ❶
        initial state Creating                           # ❷


        transition . - New_session -> Creating           # ❸

        # ... remaining parts of the state model definition
    end
end


# ... other parts of the Lube domain
  • ❶ As usual, we specify a default transition of CH and the ignored transitions explicitly.

  • ❷ Just because a state model has creation events does not mean it cannot be created synchronously. We have to specify the default initial state when instances of Autocycle Session are synchronously created.

  • ❸ The . (period) character designates the initial pseudo-state. Here we say that the New_session event transitions from the initial pseudo-state to the Creating state, and that makes it a creation event.

It is possible for a state model to have multiple creation events, although that is not a common situation. It is also possible to signal New_session as an ordinary transitioning event, although that does not happen in the Autocycle Session state model. It is also possible to signal an ordinary transitioning event as a creation event. That results in a CH transition, and the pycca runtime will respond with a fatal error.

Signaling a creation event translates to a slightly different code sequence. In the Autocycle Session state model, it is the Spawn new session state that signals the New session event:

New session(  in.Schedule,
    Injector: /R2/Injector.ID ) -> Autocycle Session
 // This event will create, and be delivered to,
 // a  new  instance  of  Autocycle  Session  to  replace
 // this one being deleted.

The action language uses syntax to imply that the New session event is signaled directly to the Autocycle Session class. This is a convenient way to distinguish a creation event, as signals are normally directed at class instances. The translation of this action language follows:

# ... other parts of the Lube domain

class Autocycle_Session
    # ... Autocycle Session attribute definitions, reference definitions, etc.


    machine
        # ... other parts of the Autocycle Session state model definition


        state Spawn_new_session(
            char const *schedule)
        {
            MechEcb new_session =
                    PYCCA_newCreationEventForThisClass(New_session, self) ;            // ❶
            PYCCA_eventParam(new_session, Autocycle_Session, New_session, schedule) =
                    rcvd_evt->schedule ;                                               // ❷
            PYCCA_eventParam(new_session, Autocycle_Session, New_session, injector) =
                    PYCCA_idOfRef(Injector, self->R2_INJ) ;
            PYCCA_postEvent(new_session) ;                                             // ❸


            self->R2_INJ->R2 = NULL ;                                                  // ❹
        }
        # ... remaining parts of the state model definition
    end
end


# ... other parts of the Lube domain
  • ❶ This allocates an ECB and marks the event as a creation event.

  • ❷ Because the event carries parameters, we need to fill in the values. When there are no parameters, we use the pycca macro PYCCA_generateCreation() to handle the entire operation.

  • ❸ Posting the event finishes the event-signaling process.

  • ❹ We need to clean up a reference from the Injector instance because this Autocycle_Session instance is about to be deleted. The next section discusses this automatic instance deletion.

Asynchronous Instance Deletion

Just as there are two ways to create class instances, there are also two ways to delete them. An activity may synchronously delete an instance by using the PYCCA_destroyInstance() macro. When the underlying runtime function returns, the instance no longer exists.

Referring to Figure 6-10 again, we notice that there is a transition from Spawn new session to a small circle and that the transition has no event label. The small circle represents a final pseudo-state. The transition to a final pseudo-state designates the Spawn new session as a final state. When an instance enters a final state, it is deleted after its activity is run. The MX runtime handles deleting the instance automatically. Pycca supports declaring any state to be a final state. For the Autocycle Session class, it appears as follows:

# ... other parts of the Lube domain

class Autocycle_Session
    # ... attribute definitions, reference definitions, etc.


    machine
        # ... other parts of the state model definition


        final state Spawn_new_session

        # ... remaining parts of the state model definition
    end
end


# ... other parts of the Lube domain

Because the instance is deleted when a final state activity is run, outgoing transitions from a final state are not allowed, and pycca will flag an error if one is defined.

Finally, we can return to the last line of code in the Spawn_new_session activity:

self->R2_INJ->R2 = NULL ;

It is necessary to NULL out the reference from the Injector instance to the Autocycle_Session because Spawn_new_session is a final state and about to be deleted. We do not want the Injector instance referring to something that is about to go away. We don’t have to deal with the references made by the Autocycle_Session instance because it will be deleted and therefore can’t refer to anything.

When the creation event is dispatched and a new Autocycle_Session instance is created, the activity of the Creating state will set the new references properly. In a time window between the end of the Spawn_new_session activity and the dispatch of the New_session creation event, the conditionality of the references implied by the R2 association is violated. That is, there is a time when the Injector instance is not controlled by any Lubrication Schedule. This does not violate our execution rules, because we have signaled the event that, when it is dispatched, will bring all the references back to a consistent state. You can think of this sequence as being part of a larger transaction on the data model, and any integrity rule checks are deferred until the transaction is completed, which occurs when the creation event is dispatched. The ST/MX runtime code does not actually perform any of these integrity checks, but other, more capable model execution domains might. For example, a model execution domain that uses a relational database management system to manage the domain data can easily provide transaction-based integrity checking.

Operations

Most of the algorithmic processing in a domain happens in the state activities. However, there are other means to factor processing into invocable units. For example, common code executed in multiple state activities should be factored into a single place. In this section, we show how code can be grouped into various types of operations.

Class Methods

A domain may define class methods to encapsulate processing performed on a particular class instance. For the Lubrication domain, several class methods were shown in Figure 6-13.

Pycca supports defining class methods as part of the specification of a class. Here we show the translation of the Max system pressure method from the Injector class:

Injector.Max system pressure()

    ALARM. Set pressure error()
    /R2/Autocycle Session.Deactivate()

Reacting to the lubricant pressure exceeding its maximum requires raising an alarm and invoking a method on the related Autocycle Session instance. These two actions need to be performed together and so are part of a single method.

In pycca terms, a class method is known as an instance operation and is defined as shown here:

# ... other parts of the Lube domain

class Injector
    # ... Injector attribute definitions, reference definitions, etc.


    instance operation Max_system_pressure() {
        ExternalOp(ALARM_Set_pressure_error)(PYCCA_idOfSelf) ;  // ❶
        ClassRefVar(Autocycle_Session, acs) = self->R2 ;        // ❷
        InstOp(Autocycle_Session, Deactivate)(acs) ;            // ❸
    }


    # ... remaining parts of the Injector definition
end


# ... other parts of the Lube domain
  • ❶ The ExternalOp() macro hides the naming conventions used by pycca to resolve external operation names.

  • ❷ Follow the association reference to obtain the instance of the Autocycle_Session that must be deactivated.

  • ❸ Because this is C, we must pass the reference to the instance explicitly. A more object-oriented language would probably do this for us. The InstOp() macro hides the naming conventions used by pycca for instance operations. Pycca uses a number of naming conventions to ensure that function names are unique within the generated C file.

Domain Operations

The Lubrication domain depends on the Signal I/O domain to detect when the pressure on an injector exceeds its maximum. When that happens, the Max system pressure method needs to be invoked for the overpressured Injector instance. To accomplish this, we need a function that can be invoked on the Lubrication domain that will find the correct instance of Injector and invoke the Max system pressure method.

In pycca terms, an operation that forms part of the service interface for a domain is called a domain operation. Domain operations provide the visible services that may be invoked on a domain at runtime. A domain starts in a well-known state defined by the initial instance population and the initial states of the active class instances. How a domain evolves over time depends on the initial configuration as well as the invocations to the service interface provided by the domain operations. You may think of the domain operations as forming an API for the domain but, unlike a conventional programming interface, a domain can come with a significant initial configuration and is not necessarily completely configured at runtime.

The following shows the domain operation definition that SIO may use to declare that the maximum pressure on an Injector has been exceeded:

# ... other parts of the Lube domain

domain operation
Injector_max_pressure(
    InstId_t injId)
{
    PYCCA_checkId(Injector, injId) ;                              // ❶
    ClassRefVar(Injector, inj) = PYCCA_refOfId(Injector, injId) ; // ❷
    if (IsInstInUse(inj)) {                                       // ❸
        InstOp(Injector, Max_system_pressure)(inj) ;
    }
}


# ... other parts of the Lube domain
  • ❶ Injectors are identified by a small integer number at the domain interface. Here we use a pycca macro to make sure we are not handed an out-of-bounds identifier.

  • ❷ Convert the identifier to an actual pointer reference to the requested Injector.

  • ❸ We must make sure the instance is not an empty storage slot.

External Operations

As domain operations provide the service interface for a domain, an external operation declares a service dependency for a domain. Each external operation that appears in an activity has a corresponding definition of its invocation interface. The following is an example of two external operation definitions:

# ... other parts of the Lube domain

external operation
SIO_Inject(InstId_t injectorId)
{
}


external operation
SIO_Stop_injecting(InstId_t injectorId)
{
}


# ... other parts of the Lube domain

The Lubrication domain will invoke these operations at the appropriate time to start and stop lube injection. How starting and stopping injection happens in the real world has been delegated to another domain, and the Lubrication domain assumes it will happen.

Notice that no code is included in the definition. Pycca will not emit code for the external operations themselves; that is typically supplied by bridge code. However, pycca will accept code as part of the external operation definition, and certain companion tools to pycca can create stubs for the external operations by using the included code.

We will have more to say about domain operations and external operations when we get to Chapter 8. There we will use the domain and external operations to bridge the Lubrication and Signal I/O domains.

Class-Based Operations

Pycca supports one more type of grouping for processing, the class operation. A class operation is similar to an instance operation, except that no instance reference is passed to the function. It is conceptually similar to class-based operations in conventional object-oriented programming languages.

Class operations are not part of the domain model (and, more to the point, they are intentionally not supported in xUML). They are strictly an implementation artifact. In a domain model, all operations on classes are provided by constructs of the action language. For example, creating and destroying an instance of a class is provided for by the action language. It is also possible in the action language to find a subset of the class instances based on the value of an expression. All the operations on classes are provided generically by the action language constructs.

When those action language constructs are translated, however, we must devise an implementation to accomplish the action language intent. To illustrate this, we will consider the Creating state of the Autocycle Session. Recall that in Figure 6-10, the Creating state is the one entered upon receiving the New session creation event. Its activity is shown here:

// Link Schedule and Injector together to create this instance
Lubrication Schedule( Name: in.Schedule ) &R2 Injector( ID: in.Injector )
Created -> me

The first line of the activity says to find the instance of Lubrication Schedule whose name is passed as a parameter and relate it across R2 to the instance of Injector whose ID is also given as a parameter. To do this, we need to search the instances of Lubrication Schedule to find the one of interest.

When considering how to translate this, two broadly different approaches can be taken:

  • We can write a generic runtime expression evaluator that would operate across the instances of an arbitrary class and evaluate an expression that contains variable terms bound to the class attributes.

  • We can write a type-specific piece of code suitable to evaluate a particular expression across the instances of a particular class.

For the types of target systems we are considering, and because we are coding in a statically typed language, we always choose the second approach. To write a generic runtime expression evaluator is a large and complicated task, and the amount of code required has to be amortized across the entire application to make it worthwhile. Unlike a database management system that must support ad hoc queries on the data set that are not known in advance, the data queries for a domain are fixed by the activities in the model and known at translation time.

Usually, the number of distinct expressions that must be evaluated is small, and writing a type-specific piece of code for each one usually results in less code than a generic solution. Some object-oriented implementation languages support type-safe generic programming (for example, C++ using templates), and in those languages a simple search may require only “a one-liner.” For C, we will have to roll up our sleeves and code each expression evaluation on its own or, as we see with pycca, perform some preprocessor macro gyrations.

In pycca, implementing a search on class instances is most easily realized as a class operation. For the Lubrication Schedule class, the search for a matching schedule appears as follows:

# ... other parts of the Lube domain

class Lubrication_Schedule
    # ... Lubrication Schedule attribute definitions, reference definitions, etc.


    class operation findByName(char const *name) : (struct Lubrication_Schedule *) {  // ❶
        ThisClassRefVar(ls) ;                                                         // ❷
        PYCCA_selectOneStaticInstOfThisClassWhere(ls, strcmp(ls->Name, name) == 0)    // ❸
        return ls == ThisClassEndStorage ? NULL : ls ;                                // ❹
    }


    # ... remaining parts of the Lubrication Schedule definition
end


# ... other parts of the Lube domain
  • ❶ The return type is indicated after a colon (:) character. In this case, we return a pointer to a Lubrication_Schedule instance.

  • ❷ We need a variable to act as an iterator for the search and to hold the result.

  • ❸ The operation is common enough that pycca provides a macro that expands to perform a linear search of the class instances. The second argument to the macro is a Boolean expression that evaluates to true if the instance matches.

  • ❹ Because the Name attribute is an identifier, we know that there can be at most one instance of Lubrication Schedule that matches the input name value. If the iterator reached the end of the storage for class instances, we use NULL to indicate we did not find a match.

The following is the translation of the Creating state activity with the invocation of the findByName() operation:

# ... other parts of the Lube domain

class Autocycle_Session
    # ... attribute definitions, reference definitions, etc.


    machine
        # ... other parts of the state model definition
        state Creating(
            char const *schedule,
            unsigned injector)
        {
            ClassRefVar(Lubrication_Schedule, ls) =
                ClassOp(Lubrication_Schedule, findByName)(rcvd_evt->schedule) ;       // ❶
            assert(ls != NULL) ;                                                      // ❷
            ClassRefVar(Injector, inj) = PYCCA_refOfId(Injector, rcvd_evt->injector) ;
            self->R2_LBS = ls ;
            self->R2_INJ = inj ;
            inj->R2 = self ;


            PYCCA_generateToSelf(Created) ;
        }


        # ... remaining parts of the state model definition
    end
end


# ... other parts of the Lube domain
  • ❶ Again, pycca provides a macro to hide the naming conventions used.

  • ❷ We insist that a Lubrication_Schedule instance is found.

The search for a matching schedule implemented by the findByName() class operation is a simple linear search across the array of Lubrication Schedule instances. That is what the PYCCA_selectOneStaticInstOfThisClassWhere() macro provides. For a small number of instances, this is probably the best approach. But what happens if the number of instances of Lubrication Schedule is substantially larger, say 1,000? If the number of instances is large and the frequency of invoking the search is high, then the linear search technique could become a performance bottleneck.

We are obliged to state our strongly held belief that performance optimizations should always be based on performance measurements and not thought experiments (a.k.a. guessing). That said, we can code the findByName() operation to use a search algorithm that displays better performance characteristics when faced with larger instance populations. For example, we can take advantage of pycca’s placement of initial instances into their storage array in the order they are defined. If we define the instances of Lubrication_Schedule in alphabetical order by the Name attribute, we can use the standard C library function, bsearch(), to find a matching instance using a binary search. Because the search is for equality of an identifier, another alternative would be to keep a parallel hash map data structure and use a hash function to compute an index into an array of pointers to the class instances corresponding to where the Name attribute hashes. If the number of instances of Lubrication Schedule is static (as we have translated it here), it is possible to compute a perfect hash function for the schedule names. Searching is a well-researched aspect of computer science, and we want to select the appropriate algorithms based on the computational demands of the particular situation; that selection can have a large impact on the quality of the implementation.

It is important to recognize that regardless of how the search is performed, the model logic of the activity is preserved, and the model is nondestructively transformed into the implementation. The action language states that the instance must be found. How that is accomplished is strictly the purview of the implementation, and choosing one search technique over another has zero impact on the logic specified by the model.

Summary

An Automatic Lubrication System (ALS) was introduced, featuring multiple domains. A domain is an executable package of content with a set of semantics at the same level of abstraction. Organization by domain is orthogonal to the practice of modeling. A domain may be either modeled or hand coded. Domains are organized by a principle of delegation. Whatever content is excluded from consideration in one domain may be delegated to another. Ultimately, all concepts essential to a software system must be present in a domain. To make implementation possible, the chain of domain delegation must always culminate in one or more existing implemented domains.

The ALS consists of the Lubrication, Signal I/O (SIO), User Interface (UI), and Alarms domain. These run on top of the model execution domain that is implemented in C. This chapter described the topmost domain, Lubrication.

The Lubrication domain manages a set of injectors according to a programmable cyclic schedule. System pressure and safety lockouts are monitored as part of the lubrication process. Interaction among the state models within the Lubrication domain and external domains are coordinated with a class collaboration diagram.

Various state actions rely on services supplied by the SIO and Alarms domains. Additionally, the UI interacts by interacting with the Autocycle Session class.

Translation examples of yet unused constructs in pycca were shown. This included signaling creation events and factoring code into instance, domain, external, and class operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset