Overview

C is one of the programming languages that HAMR supports for implementing the application logic of AADL components. HAMR C-based applications can be executed on MacOSX, Linux, and seL4 platforms (referred to as “HAMR C execution platforms”).

This chapter provides an overview of the platform-independent aspects of HAMR C-based development. Other chapters describe how these concepts are specialized to different platforms. Making application code as platform-independent as possible (via uniform APIs and code organization) is a goal of HAMR. In fact, in most cases, component application code that doesn’t access underlying platform services can be moved between platforms immediately. The system build process and platform configuration aspects are specific to the underlying platforms.

HAMR generates C APIs for interacting with AADL ports and skeletons for entry point methods for AADL threading. The structure of the port APIs and threading entry points are designed to have a structure very similar to the Slang APIs and skeletons used in Chapter AADL Computational Paradigm and HAMR Fundamental Concepts to explain the overall AADL computational paradigm. The discussion in this chapter assumes that the reader has read and understood the presentation in Chapter AADL Computational Paradigm and HAMR Fundamental Concepts.

HAMR also generates libaries with a variety of C functions and macros to support creation and access of values for AADL/HAMR types.

Behind the scenes, the HAMR C-based AADL run-time infrastructure for port-based communication and threading is derived, via the Slang transpiler, from the Slang reference model of the AADL run-time serves along with Slang-based customizations for HAMR’s C execution platforms. This helps ensure consistency of the infrastructure’s execution semantics across different platforms.

A development environment for C of the developer’s choice may be used to support coding and debugging (the HAMR development team uses the JetBrains CLion IDE https://www.jetbrains.com/clion/). HAMR generates CMAKELists.txt files (which can be used for command-line-based compilation or imported into IDEs) to coordinate compilation of HAMR C-based component and system implementations.

Info

ToDo, From John, To Robby: Briefly describe the subset of C produced by the transpiler and any other facts about C that would impact its deployment. Eventually, we will likely have a stand-alone chapter on the Slang transpiler.

Code Organization

Recall from Chapter HAMR Project Organization and Workflow the suggested folder structure for HAMR projects. The diagram below expands the c folder to show the suggested structure of HAMR C projects.


   aadl/
   hamr/
   ├── bin/
   ├── src/
       ├── slang/
       └── c/
           ├── <platform infrastructure code folder>/
           |    ├── ...
           |    └── CMAKELists.txt
           |
           └── c-ext/
               ├── <component C1 application code folder>/
               |    ├── <component C1>.c 
               |    ├── <component C1>_api.c 
               |    └── <component C1>_api.h
               ├── ...
               ├── <component Cn application code folder>/
               ├── adapters/
               |    ├── <component C1 mangled name>/
               |    |    ├── <component C1>_adapter.c
               |    |    └── <component C1>_adapter.h
               |    ├── ...
               |    └── <component Cn mangled name>/
               | 
               ├── ext.c
               ├── ext.h 
               └── ipc.c 

The <platform infrastructure code folder> contains HAMR-generated infrastructure code for the chosen platform. This code should never be modified by the developer – succesive calls to HAMR code generation will overwrite these files.

Info

ToDo, From John, To Jason: Briefly describe the purpose of the CMAKELists.txt file. I assume that it is the primary method of configuring the build. Does the user ever need to modify it? Does it get overwritten with successive calls to the code generator?

** CMAKELists.txt is autogenerated. Jason notes that there are other CMAKELists.txt The organization of CMAKELists.txt and an overview of how to customize it for compilation is presented at the end of this chapter.

Developer-written application code goes in the file:c-ext folder, using the subfolders for each AADL thread component. In the files <component Cn>.c, HAMR will generate function skeletons for each thread entry point for the respective AADL thread component (e.g., for the compute entry point, either a time-triggered function for periodic threads, or input event handlers for sporadic threads will be generated) – all following the same structure as described in Chapter XX ToDo. When the HAMR code generator is called successively, the <component Cn>.c files will not be overwritten.

Programming the logic for the component consists of filling in code in the thread entry point skeletons. This code will typically call functions to send and receive data over AADL ports (defined in the HAMR-generated files <component C1>_api.h and <component C1>_api.c). The developer should not modify the <component C1>_api.h and <component C1>_api.c, and these files will be overwritten on successive invocations of HAMR code generation (e.g., the files may be changed to reflect changes to the component’s port declarations in the AADL model).

Other C files that support the application may be added (where??? ToDo – Jason complete).

The adapters/ folder (ToDo – Jason complete)

The ext.c, ext.h, and ipc.c files (ToDo – Jason complete)

Info

ToDo, From John, To Jason:

  • Check/complete description of folders/files above.

  • What files contain the macros that simplify the developer’s programming?

Thread Entry Points

Compute Entry Point – Periodic Threads

Following the structure presented in AADL Computational Paradigm and HAMR Fundamental Concepts, for periodic AADL thread components the root of the primary application logic for the AADL compute entry point is the timeTriggered function. This function will be repeatedly invoked by the underlying AADL and platform infrastructure at the rate corresponding to the Period property attached to the thread component in the AADL model.

HAMR generates a skeleton for the timeTriggered function (along with some other supporting functions) in the <component Cn>.c as described above. For the building control example, the following skeleton is generated in the file /ext-c/TempSensor_i_Impl/TempSensor_i_Impl.c for the TempSensor periodic thread.


   Unit bc_BuildingControl_TempSensor_i_Impl_timeTriggered_(
         STACK_FRAME
         bc_BuildingControl_TempSensor_i_Impl this) {
     
         // Application code goes here
   }

Most HAMR C functions take a STACK_FRAME argument to support debugging and traceability back to the higher-level Slang infrastructure. Developers don’t need to manipulate this argument – its values are populated by the underlying infrastructure. Its use in debugging is discussed at the end of this chapter. Most HAMR C functions associated with a component are passed a struct this that contains state information for the component. This struct information never needs to be manipulated directly by application code. It is created by HAMR initialization infrastructure code and then updated behind the scenes by each HAMR API call (further explanation is given at the end of the chapter).

Info

ToDo, From John, To Jason:

  • Check wording above
* The completed application code for this example thread includes reading the sensor value by a call to the driver for the hardware temperature sensor. The function uses HAMR-generated APIs in ext-c/TempSensor_i_Impl/TempSensor_i_Impl_api.* to interact with AADL ports to send values out of the component output ports (the TempSensor component does not have input ports – working with input ports is illustrated using the TempControl component in the section on sporadic threads below).

Since HAMR is designed for interoperability across multiple platforms and across programming languages including Slang and C, the function uses standardized macros and libaries for creation and access of type structures derived from AADL-level types and for other types (e.g., message strings used in logging) that are shared across Slang-supported infrastructure.

Comments in the code listing explain the use of the APIs and macros.


   Unit bc_BuildingControl_TempSensor_i_Impl_timeTriggered_(
         STACK_FRAME
         bc_BuildingControl_TempSensor_i_Impl this) {

      // Use HAMR-provided macro to declare structure to hold temperature value retrieve via temp sensor driver         
      DeclNewbc_BuildingControl_Temperature_i(value);
      // Call driver function to move the temperature reading into the "value" structure
      currentTempGet(&value);

      // Use auto-generated APIs in ext-c/TempSensor_i_Impl/TempSensor_i_Impl_api.h to interact with AADL ports
      //  .. put current temp reading in "currentTemp" AADL out data port 
      api_send_currentTemp__bc_BuildingControl_TempSensor_i_Impl(this, &value);
      //  .. queue "tempChanged" event on "tempChanged" AADL out event port 
      api_send_tempChanged__bc_BuildingControl_TempSensor_i_Impl(this);

      // Use HAMR-provided macros to declare strings interoperable with Slang infrastructure. 
      DeclNewString(msg);
      String__append((String) &msg, string("Sensed temperature: "));
      // Prepare Slang compatible type structure for logging message
      bc_BuildingControl_Temperature_i_string_((String) &msg, &value);

      // send message structure to logger 
      api_logInfo__bc_BuildingControl_TempSensor_i_Impl(this, (String) &msg);

      // Recall: Following AADL semantics, all values placed in output ports using the "send" functions as above are held
      // until this function completes and are then propagated after this function completes to connected consumer 
      // components by the underlying AADL and platform infrastructure. 
   }

Compute Entry Point – Sporadic Threads

Following the structure presented in AADL Computational Paradigm and HAMR Fundamental Concepts, for sporadic AADL thread components, the roots of the primary application logic for the AADL compute entry point are message handlers for input event and event data ports (more precisely, for input event and event data ports specified as dispatch triggers).

HAMR generates skeletons for message handlers for sporadic threads in the <component Cn>.c as described above. For the building control example, the following skeletons are generated in the file /ext-c/TempControl_i_Impl/TempControl_i_Impl.c for the TempControl sporadic thread.


   // HAMR-generated skeleton for fanAck input event data port 
   Unit bc_BuildingControl_TempControl_i_Impl_handlefanAck_(
        STACK_FRAME
        bc_BuildingControl_TempControl_i_Impl this,
        // message payload for incoming event data 
        bc_BuildingControl_FanAck_Type value) {

        // Application code goes here
   }

   // HAMR-generated skeleton for fanAck input event data port 
   Unit bc_BuildingControl_TempControl_i_Impl_handlesetPoint_(
        STACK_FRAME
        bc_BuildingControl_TempControl_i_Impl this,
        // message payload for incoming event data 
        bc_BuildingControl_SetPoint_i value) {

       // Application code goes here
   }

   // HAMR-generated skeleton for tempChanged input event port 
   Unit bc_BuildingControl_TempControl_i_Impl_handletempChanged_(
        STACK_FRAME
        bc_BuildingControl_TempControl_i_Impl this) 
        // no message payload for input event ports (only payloads for event data ports)
                                                  {
       // Application code goes here
   }

Each of these message handlers is programmed in a manner similar to the timeTriggered function for periodic threads – each handler uses HAMR-generated APIs in :file:ext-c/TempControl_i_Impl/TempControl_i_Impl_api.*. For example, shown below is the implementation of the handler for the setPoint event data port, along with a variable declaration to store the incoming set points (so that the set point values persist and are accessible by the component across multiple dispatches).


   // declare component local variable to store set point values 
   struct bc_BuildingControl_SetPoint_i setPoint;


   // completed implementation for setPoint input event data port handler
   Unit bc_BuildingControl_TempControl_i_Impl_handlesetPoint_(
         STACK_FRAME
         bc_BuildingControl_TempControl_i_Impl this,
         bc_BuildingControl_SetPoint_i value) {

      // copy the set point values to component-local state.  
      setPoint = *value;
   }

The handler implementation for the tempChanged event port is presented below. Note that since AADL events are simple buffer notifications (with no message payloads), there is no payload value passed to the handler (in contrast to the handler for the event data port above).


   Unit bc_BuildingControl_TempControl_i_Impl_handletempChanged_(
        STACK_FRAME
        bc_BuildingControl_TempControl_i_Impl this) {

       // Use Slang-transpiler macro for creating new strings 
       DeclNewString(msg);
       // Declare a local variable to hold the current temp structure 
       struct bc_BuildingControl_Temperature_i currentTemp;

       // Read the current temperature from the currentTemp data port.
       // Note that in AADL, data ports should always have values, so if the API has a false return value
       // then an error message is printed in the false branch of the conditional.
       if (api_get_currentTemp__bc_BuildingControl_TempControl_i_Impl(this, &currentTemp)) {
           
           // ToDo: From John, to Jason: I don't understand this step 
           struct bc_BuildingControl_Temperature_i tempInF = toFahrenheit(currentTemp);

           // compare the `degrees` field of the temperature structure to the high set point 
           if (tempInF.degrees > setPoint.high.degrees) {
               // if current temp exceeds the high set point, 
               // send a "fan on" command out of the fanCmd event data output port to cool environment
               api_send_fanCmd__bc_BuildingControl_TempControl_i_Impl(this, FanCmd_On);

               String__append((String) &msg, string("Sent fan command: "));
               bc_BuildingControl_FanCmd_Type_string_((String) &msg, FanCmd_On);
           }
           else if (tempInF.degrees < setPoint.low.degrees) {
               // if current temp is less than the low set point, 
               // send a "fan off" command out of the fanCmd event data output port to stop cooling environment

               api_send_fanCmd__bc_BuildingControl_TempControl_i_Impl(this, FanCmd_Off);

               String__append((String) &msg, string("Sent fan command: "));
               bc_BuildingControl_FanCmd_Type_string_((String) &msg, FanCmd_Off);
           }
           else {
               // if current temp is between high and low set points (inclusive)
               // don't change the on/off status of the fan 
               String__append((String) &msg, string("Temperature ok:"));
           }
           // creating a string that includes the current temperature for logging purposes 
           String__append((String) &msg, string(" - "));
           bc_BuildingControl_Temperature_i_string_((String) &msg, &currentTemp);

           api_logInfo__bc_BuildingControl_TempControl_i_Impl(this, (String) &msg);
       } else {
           printf("Unexpected: tempSensor should have also sent something on its currentTemp port\n");
       }
   }

Initialize Entry Point

As described in AADL Computational Paradigm and HAMR Fundamental Concepts, each AADL thread has an initialize entry point that is called during system initialization before system scheduling takes over the dispatching for all thread compute entry points.

The application developer fills in the code

C APIs Domain APIs

Finalize Entry Point

(not fully implemented now)

Port Communication APIs

Following a structure similar to HAMR Slang code generation, for each AADL thread component, HAMR will generate APIs for interacting with the ports of the component. The APIs generated for TempControl component are illustrated below.


   /**********************************************************************************
   * fanCmd : out event data port FanCmd
   **********************************************************************************/

   Unit BuildingControl_TempControl_i_sendfanCmd(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         building_control_gen_mixed_excludes_BuildingControl_FanCmd value);


   /**********************************************************************************
   * currentTemp : in data port Temperature.i
   ***********************************************************************************/

   bool BuildingControl_TempControl_i_getcurrentTemp(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         building_control_gen_mixed_excludes_BuildingControl_Temperature_i value);

   /**********************************************************************************
   * tempChanged : in event port
   **********************************************************************************/

   bool BuildingControl_TempControl_i_gettempChanged(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         art_Empty value);

   /**********************************************************************************
   * fanAck : in event data port FanAck
   **********************************************************************************/

   bool BuildingControl_TempControl_i_getfanAck(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         building_control_gen_mixed_excludes_BuildingControl_FanAck *value);

   /**********************************************************************************
   * setPoint : in event data port SetPoint.i
   **********************************************************************************/

   bool BuildingControl_TempControl_i_getsetPoint(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         building_control_gen_mixed_excludes_BuildingControl_SetPoint_i value);

   /**********************************************************************************
   * logging methods
   **********************************************************************************/

   void BuildingControl_TempControl_i_logInfo(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         String str);

   void BuildingControl_TempControl_i_logDebug(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         String str);

   void BuildingControl_TempControl_i_logError(
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this,
         String str);

Each API is passed a struct this that contains state information about the component. This struct information never needs to be manipulated directly by application code. It is created by initialization infrastructure code and then updated behind the scenes by each HAMR API calls.

Info

ToDo, From John, To Jason:

  • Give the intuition of the this structure and indicate where the information for this structure is defined.

The design of the APIs above does not have the flavor of hand-written C code, but it is constructed in this way to enable the C code to interoperate with code generated for out HAMR component implementations written in Slang and CakeML.

In the API for out event data port sendfanCmd,


   Unit building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl_handletempChanged_(
         STACK_FRAME
         building_control_gen_mixed_excludes_BuildingControl_TempControl_i_Impl this) {

      DeclNewString(msg);
      struct building_control_gen_mixed_excludes_BuildingControl_Temperature_i currentTemp;
      if (BuildingControl_TempControl_i_getcurrentTemp(this, &currentTemp)) {
         struct building_control_gen_mixed_excludes_BuildingControl_Temperature_i tempInF = toFahrenheit(currentTemp);

         if (tempInF.degrees > setPoint.high.degrees) {
               BuildingControl_TempControl_i_sendfanCmd(this, FanCmd_On);

               String__append((String) &msg, string("Sent fan command: "));
               building_control_gen_mixed_excludes_BuildingControl_FanCmd_string_((String) &msg, FanCmd_On);
         }
         else if (tempInF.degrees < setPoint.low.degrees) {
               BuildingControl_TempControl_i_sendfanCmd(this, FanCmd_Off);

               String__append((String) &msg, string("Sent fan command: "));
               building_control_gen_mixed_excludes_BuildingControl_FanCmd_string_((String) &msg, FanCmd_Off);
         }
         else {
               String__append((String) &msg, string("Temperature ok:"));
         }
         String__append((String) &msg, string(" - "));
         building_control_gen_mixed_excludes_BuildingControl_Temperature_i_string_((String) &msg, &currentTemp);
         BuildingControl_TempControl_i_logInfo(this, (String) &msg);
      } else {
         printf("Unexpected: tempSensor should have also sent something on its currentTemp port\n");
      }
   }

CMAKELists

STACK_FRAME