Implementing a Device Interface: Property

The Property provides a template representing a certain setting or value in your device. This, for example, can be the voltage preset in a power supply device or the current this device measures actually on the connected consumer. The software running on the device can update the Property and thereupon notify the GUI about the new value. On the other hand, the GUI application can modify the Property causing certain operations to be executed in the device. As such, Properties build an integral element of the interface separating the GUI application and the underlying device. Furthermore, Properties abstract the device specific data reducing them to simple values like Voltage.

Usually, you use the Property template when implementing the Device Interface class. In such case, you add as many Properties to the Interface as values possible in your device. For example, if your device controls a pump you can add a Pump Flow Property controlling the volume flow rate. If the pump has additional temperature and current sensors supervising its correct function you could manage more Properties e.g. Pump Temperature or Pump Current.

Please note, the here explained Device Interface Property is based on the more generic Property member found in the Chora programming language. It enhances the generic property by additional features needed for the integration with the device and implements functionality to trigger notifications when the value of the Property is changed.

The figure below demonstrates the both principle functions of a Property: modifying and obtaining a value available in the device. In this example we assume that the GUI controls a laboratory power supply device. In such device, the user can setup the desired voltage and the device measures and displays the consumed current. The corresponding Device Interface is composed thus from two properties Voltage and Current. These Properties are connected to GUI components.

The first component limits to display the value stored actually in the Property Current. This value is measured e.g. by a sensor and as soon as the value changes the software running on the device executes the code section on top of the figure causing the Property Current to be updated to the just estimated new value. This, in turn, causes the Value Display widget found in component 1 and connected to the Property Current to be notified. The widget updates the displayed value automatically.

The second GUI component contains a Horizontal Slider widget allowing the user to setup the desired voltage. When the user drags on the slider, the Property Voltage connected to the slider is changed accordingly. This, in turn, executes the code in the associated OnSetVoltage method. Its implementation contains a section of native code, where some C function (here an imaginary function write_dac_voltage_value()) is called assuming that the corresponding DAC (digital analog converter) does control the power supply voltage. Additionally, OnSetVoltage broadcasts a notification, triggering so the Value Display widget found in component 2 to also update its appearance:

The usage of Properties in the Device Interface does not imply that your GUI application has to be built from widgets. In fact, the Properties can be be read and modified from any Chora code existing in your GUI application. To react to an alternation of a Property value dedicated Property Observer exists where you can implement code to handle the notification. Nevertheless, you should know that the widgets are already prepared to allow simple connection to Properties without needing to write a single line of code.

The Properties and the Property Observers help to separate the GUI components from the device functionality by what the structure of the entire GUI application can be simplified. The following sections are intended to provide you an introduction and useful tips of how to add and use Properties. To better understand the underlying concepts we also recommend you to read the chapter Implementing a Device Interface as well as Integrating with the device.

Add a new Property

Device Properties are usually used when implementing an interface between the GUI application and the device. Accordingly, you add the Properties to a Device Interface class:

If you have not done it yet, add a new Device Interface to your project.

Ensure that Composer shows the Device Interface class where you want to add the new Property.

Then ensure that the Templates window is visible.

In Templates window switch to the folder Device.

In the folder locate the template Property.

Drag & Drop the template into the Composer window:

In the screenshot above you see that new added Device Properties consist of four members:

Member

Description

Property

This property member represents the concrete Device Property and stores its value. It can be compared with a variable. The GUI application can read the property or assign new values to it.

OnSetProperty

This onset method implements code to execute when the GUI application assigns a new value to the property. Thereupon the onset method can relay the alternation to the device or middleware. It can modify the value directly in the device.

OnGetProperty

This onget method implements code to execute when the GUI application evaluates (reads) the property. Thereupon the onget method can relay the query operation to the device or middleware. It can read the value directly from the device.

UpdateProperty

This method serves as the entry point for the external software running on the device. When the device detects an alternation of an interesting value the external software may invoke this method and notify so the GUI application about a new value for the corresponding property.

The members are accompanied by an annotation providing helpful tips how to proceed. If undesired, you can select and delete the annotation. Similarly, the usage of UpdateProperty, OnSetProperty and OnGetProperty is optional. It depends in fact on the concrete application case how the value represented by the Property is changed. We distinguish following cases:

1. The value is modified by the GUI application only

In this case the device (or the middleware) stores and evaluates the value but it will never change it by itself. Only the GUI application may alternate the value. This could be, for example, the preset voltage in a power supply device. For this purpose:

Adapt the OnGetProperty method to read the value from the device.

Adapt the OnSetProperty method to update the value in the device respectively.

The method UpdateProperty is not needed in this case and can be deleted.

2. The value is modified by the device (middleware) only

In such case the GUI application limits to evaluate the value. Eventual value alternations are made or detected by the underlying device only. This could be, for example, the strength of the electric current measured actually in a power supply device. For this purpose:

The member OnSetProperty is not needed in this case and can be deleted.

The member OnGetProperty is not needed in this case and can be deleted.

The middleware or the device should invoke UpdateProperty method each time the value is modified by code running in the device or middleware.

3. The value is modified by the GUI application and by the device (middleware)

In such case both, the device and the GUI application evaluate and modify one and the same value. This could be, for example, a settings value the device and the user (via GUI) can modify. For this purpose:

Adapt the OnSetProperty method to update the value in the device respectively.

The member OnGetProperty is not needed in this case and can be deleted.

The middleware or the device should invoke UpdateProperty method each time the value is modified by code running in the device or middleware.

Name the Property

The names of newly added Properties start per default with Property. That can lead to confusion especially when your Device Interface consists of many Properties. We recommend thus immediately after adding a new Property to name it according to the corresponding value in the device (e.g. to Current). For this purpose:

First ensure, that the property brick is selected.

Press the key F2 or select the menu item EDITRename ....

Enter the new name in the Inspector window.

The name of the property member is relevant for the GUI application only. The name of its associated Update method, in turn, plays an important role when it comes to integrate the Property with the external software running in the device. In such case after renaming a Property you should therefore also adapt the name of this method to be obvious. Per default this name starts with UpdateProperty. Rename the method so that it addresses the affected value (e.g. UpdateCurrent). For this purpose:

First ensure, that the method brick is selected.

Press the key F2 or select the menu item EDITRename ....

Enter the new name in the Inspector window.

TIP

If the device never changes the value of the Property (the Property is changed exclusively by the GUI application), there is no need to maintain the method UpdateProperty. You can delete the method.

Inspect the Property

As long as the property member is selected you can inspect and modify its attributes conveniently in the Inspector window as demonstrated with the attribute Type in the screenshot below:

This is in so far worth mentioning as all following sections describe diverse features of the Property by explicitly referring to its corresponding attributes. If you are not familiar with the concept of attributes and the usage of Inspector window, please read first the preceding chapter Compositing component appearance.

Specify the data type of the Property

Properties provide uniform access to data values managed in the software running on the device. Depending on their function the values store integers, booleans, strings or something else. Consequently, the data type of each Property has to correspond to (or be the superset of) the reflected value. For example, if the value in the device is intended to store a boolean, the Property should be configured to do the same. New added Properties are configured per default with int32 data type. If this is not what you expect, you can change it as described below:

First ensure, that the property brick is selected.

In Inspector locate its attribute Type.

Modify the attribute so that it corresponds to the of the value in the device. For example, if the value stores a C char array (C zero terminated string), configure the attribute to be string too. With the Inspector Assistant you can conveniently select the right type.

TIP

It is in fact not always prudent to configure the Property to match 1:1 the data type of the associated value. For example, if the device stores an unsigned short value (unsigned 16-bit integer), it is legitimate to leave the Property being configured with its default int32 data type. Doing this has the advantage that the Property can be connected directly to a widget and as such serve as a kind of data source.

Depending on your application case you will eventually also need to adapt the Update method attendant to the Property. This method exists for the integration purpose with the extern software running on the device. When the software detects an alternation of the respective value it invokes the method updating so the Property in the GUI application. To work correctly, however, the parameter declaration of the Update method has to correspond to the data type of the Property. This, of course, is only necessary when the Update method is really needed in your application case. If the method is never invoked from the extern software, you can simply delete it ignoring the following instructions.

The Update method is per default declared with a single parameter of type int32 matching the default data type of each new added Property. The editing of method parameters takes place in the declaration area of the Code Editor window. How you edit the declaration of an existing parameter is explained in the section Edit an existing method parameter. For example, if you have changed the data type of the Property to string you should adapt accordingly the unique parameter of this method as shown in the figure below:

Specify the default (initial) value of the Property

Each Property can manage internally a copy of the respective value. When the GUI application reads the Property, it reads the content of this internal storage. When the software running on the device detects an alternation of the value it may update the Property accordingly by invoking the associated UpdateProperty method. This storage is in fact an internal variable of the specified data type. As such it can be configured with an initialization expression:

First ensure, that the property brick is selected.

In Inspector locate its attribute Default.

Modify the attribute so that it contains an expression resulting in the desired default value for the Property.

Specifying the default value is relevant especially in cases when the Property can be evaluated in the GUI application still before the device had the possibility to update it. In such case the GUI application reads the predetermined default value. New added Properties are configured with the expression 0 (zero) matching their default data type int32. If you change the data type of the Property you should also adapt the attribute Default to be a valid expression corresponding to the data type. For example, if the Property is configured with string data type, the attribute Default should be a valid string literal like "".

Implement the OnSet method of the Property

If the Property serves as interface to control data values or settings in the device, the implementation of the Property has to take care of all appropriate device specific operations. For example, if the GUI application uses the Property to determine the desired voltage in a power supply device, modifying the Property should adjust a DC converter existing in it. You implement this functionality in the onset method accompanying the Property. Each time the GUI assigns a new value to the Property, the onset method is executed automatically permitting your implementation to access hardware specific GPIOs, use operating system services or call other software routines existing outside of the GUI application.

Being a highly platform independent development tool, Embedded Wizard does not allow the GUI application to simply access the hardware or any other external software routines. To achieve such effect, you have to implement the desired hardware access or invocation of external routines directly in the native syntax of your target system. This is usually C, C++ or it is JavaScript if you are developing a GUI application for the WebGL platform.

Embedded Wizard, however, can understand the syntax of its own programming language Chora only. The unique possibility to integrate the native code with the platform independent Chora is thus to enclose it explicitly within a native statement. Then, during Code Generation the native code is simply taken over and mixed together with all the other generated code. The following example demonstrates the implementation of such onset method associated to a Voltage Property:

// The property doesn't change -> nothing to do. if ( pure Voltage == value ) return; // Remember the new value in the internal memory of the property. pure Voltage = value; // Following section encloses code to execute natively on the device. native ( value ) { write_dac_voltage_value( value ); } // Notify all associated property observers. notifyobservers ^Voltage;

All onset methods follow the same implementation scheme. In the first step they verify whether the just assigned new value is equal to the stored already in the internal memory of the Property addressed by the keyword pure. If this is not the case, the new value is stored in this internal memory. Then follows a native code section enclosing all the operations to execute on the device when the Property is changed. Finally, at the end the methods trigger notifications to all observers tracking alternations of the Properties.

In this example, we assumed that the desired voltage is determined by setting a DAC (digital analog converter) via an imaginary C function write_dac_voltage_value(). In the practice you will of course implement native code using real functions or you will access real CPU register existing in your target device. With the native statement you are thus flexible to perform any device specific operation. It's finally up to you what is performed in the onset method.

When implementing native code in C or C++ syntax, it is obligatory to have the used functions being declared in advance. Similarly when the native code is using type definitions available in your device software, these definitions need to be known when the code snippet enclosed in native statement is compiled by the C compiler. In the simplest case you can add the necessary declarations inline together with the native statement. The following example declares the write_dac_voltage_value() C function. The C compiler knows thereupon what to do with the invocation of this function:

native ( value ) { /* Declaration of an external C function */ extern void write_dac_voltage_value( int aValue ); /* Invocation of this function */ write_dac_voltage_value( value ); }

In more sophisticated cases, it is suitable to include complete header files containing all the necessary function and data type declarations. This approach is solved ideally by using the inline code member accompanying per default the Device Interface. This is explained in the section Add middleware and other external code to the project.

Please note, during Prototyping the Chora compiler ignores all native statements and reports only a warning. To avoid this Chora warning you can use the conditional compilation directives like $if .. $endif to conditionally exclude the native statement:

$if !$prototyper native ( value ) { [...] } $endif

Implement the OnGet method of the Property

If the Property serves as interface to directly read data values from the device, the implementation of the Property has to take care of all appropriate device specific operations. For example, if the GUI application uses the Property to query a setting stored in a device, evaluating the Property should read the corresponding value in the device or middleware. You implement this functionality in the onget method accompanying the Property. Each time the GUI evaluates the Property, the onget method is executed automatically permitting your implementation to access hardware specific GPIOs, memory, use operating system services or call other software routines existing outside of the GUI application.

Being a highly platform independent development tool, Embedded Wizard does not allow the GUI application to simply access the hardware or any other external software routines. To achieve such effect, you have to implement the desired hardware access or invocation of external routines directly in the native syntax of your target system. This is usually C, C++ or it is JavaScript if you are developing a GUI application for the WebGL platform.

Embedded Wizard, however, can understand the syntax of its own programming language Chora only. The unique possibility to integrate the native code with the platform independent Chora is thus to enclose it explicitly within a native statement. Then, during Code Generation the native code is simply taken over and mixed together with all the other generated code. The following example demonstrates the implementation of such onget method associated to a Voltage Property:

// Use a local variable to exchange the data with the native code var int32 result; // Following section encloses code to execute natively on the device. native ( result ) { result = read_dac_voltage_value(); } // ... and return the value return result;

All onget methods follow the same implementation scheme. In the first step declare a local variable needed to receive the data from the native code. Then follows a native code section enclosing all the operations to execute on the device when the Property is read. The read value has to be stored in the local variable. Finally, at the end the methods return the value stored in the local variable.

In this example, we assumed that the voltage is queried by reading a DAC (digital analog converter) via an imaginary C function read_dac_voltage_value(). In the practice you will of course implement native code using real functions or you will access real CPU register existing in your target device. With the native statement you are thus flexible to perform any device specific operation. It's finally up to you what is performed in the onget method.

When implementing native code in C or C++ syntax, it is obligatory to have the used functions being declared in advance. Similarly when the native code is using type definitions available in your device software, these definitions need to be known when the code snippet enclosed in native statement is compiled by the C compiler. In the simplest case you can add the necessary declarations inline together with the native statement. The following example declares the read_dac_voltage_value() C function. The C compiler knows thereupon what to do with the invocation of this function:

native ( result ) { /* Declaration of an external C function */ extern int read_dac_voltage_value( void ); /* Invocation of this function */ result = read_dac_voltage_value( ); }

In more sophisticated cases, it is suitable to include complete header files containing all the necessary function and data type declarations. This approach is solved ideally by using the inline code member accompanying per default the Device Interface. This is explained in the section Add middleware and other external code to the project.

Please note, during Prototyping the Chora compiler ignores all native statements and reports only a warning. To avoid this Chora warning you can use the conditional compilation directives like $if .. $endif to conditionally exclude the native statement:

$if !$prototyper native ( result ) { [...] } $endif

Update the Property

If the value represented by the Property can change independently (outside of the GUI application), the software managing this value has to notify the GUI application about its each alternation. It has to update the Property. This software is not part of the GUI application and as such it is considered as extern. It can be seen as a kind of operating system or middleware controlling the device. The GUI application and the extern software form a unity. Modifications made on Properties within the GUI application are reported to the extern software with the aim to perform respective operations. In turn, the alternation caused or detected by the extern software will propagate in the reverse direction.

Consequently, to update a Property the extern software has to invoke a function existing in the GUI application. This function represents the Update method explained in the sections above. The primary aspect to know is thus the name of the function to call. The second and not less important aspect is the instance of the Device Interface class containing the Property. This is fundamental, because Chora methods can be executed in context of the corresponding instance only. For your convenience, such instance is prepared automatically when you add a new Device Interface to your project. You recognize it as a global autoobject named per default Device:

How to estimate the name and invoke the function have to be considered differently depending on your development environment C, C++ or JavaScript (depending on your used Platform Package). Similarly the access to the global Device autoobject is also an environment specific aspect. You should also take in account the possibility to update the Property directly from the GUI application instead of by the extern software routines. These different application cases are addressed individually in following three sections.

Update the Property in C and C++ compatible systems

Following example demonstrates how to update a Property from C (or C++) code. In the first part we use the function EwGetAutoObject to obtain access to the global instance of the Device Interface class containing the interesting Property. In the second part we invoke the C function representing the corresponding Update method within the Device Interface. In the parameter of the function we provide the new value of the Property. The #include preprocessor directive at the beginning of the code snippet ensures that all necessary function declarations are provided before they are used. That's all:

#include "Application.h" [...] /* Obtain access to the Device Interface instance */ ApplicationDeviceClass device = EwGetAutoObject( &ApplicationDevice, ApplicationDeviceClass ); int newValue = 1200; /* e.g. 1200 mA */ /* Invoke the function to update the Property. Provide the new value of the Property. */ ApplicationDeviceClass__UpdateCurrent( device, newValue );

The identifiers used in the first part correspond to the names of the Device Interface class and its associated global autoobject instance. These identifiers are simply derived by concatenating the name of the unit containing the Device Interface class with the name of the class or autoobject itself. The name of the header file used in the #include directive corresponds also to the name of the unit itself. The following figure demonstrates how the diverse identifiers are determined:

Similarly in the second part, the name of the function to invoke is composed of the unit containing the Device Interface class, the name of the Device Interface class itself, a double underscore __ and the name of the method intended to update the Property. The following figure demonstrates how the above used function name is derived:

Update the Property in JavaScript compatible systems

Following example demonstrates how to update the Property from JavaScript code. In the first part we use the function _GetAutoObject to obtain access to the global instance of the Device Interface class containing the interesting Property. In the second part we invoke the JavaScript function representing the corresponding Update method within the Device Interface. In the parameter of the function we provide the new value of the Property. That's all:

// Obtain access to the Device Interface instance var device = EmWiApp._GetAutoObject( EmWiApp.Application.Device ); var newValue = 1200; // e.g. 1200 mA // Invoke the function to update the Property. Provide the // new value of the Property. device.UpdateCurrent( newValue );

The identifiers used in the first part correspond to the name of the global autoobject instance representing the Device Interface and the unit containing the Device Interface. The identifier EmWiApp represents the instance of the GUI application itself. Please note, that this default name EmWiApp can be modified by specifying another name in the attribute ApplicationName. Similarly in the second part, the name of the function to invoke matches the name of the method intended to update the Property. The following figure demonstrates how the diverse identifiers are determined:

Update the Property locally from Chora code

Updating of Properties can also occur locally from the Chora language. In this case no extern software is involved and the source of the event is the GUI application itself. As such, Properties are well suited to be used for internal communication between GUI components and e.g. the underlying business logic implemented in Embedded Wizard. Updating a Property directly from the GUI application is also practicable when during Prototyping of the GUI application such event needs to be simulated.

According to the explanation in the section above to update the Property you have to invoke its Update method in context of the Device Interface autoobject containing this Property. The advantage of a global autoobject is that it can be accessed from everywhere in your application. You should only take care of the correct addressing the instance by using its complete name composed of the unit containing the autoobject, the :: (double colon) delimiter and the name of the autoobject itself. For example, the autoobject from the above screenshots is addressed by the expression Application::Device. A GUI component containing a push button could thereupon implement following code to update an integer Property to the value 0 (zero) every time the user activates the push button:

Application::Device.UpdateCurrent( 0 );

Multi-threading and interrupt service routines

Embedded Wizard created applications are not thread safe. Calling functions of Embedded Wizard in context of a thread other than the GUI thread will result in unpredictable behavior including very difficult to understand system crashes. Similarly, calling Embedded Wizard directly from an interrupt service routine is poison. When you integrate the GUI application with the underlying software or operating system you should note the following:

If you intend to invoke a function of the GUI application, you have to ensure that it occurs always in context of the GUI thread.

If your application uses several threads intended to update Properties in the GUI application, relay such update events to the GUI thread first.

To relay the update events you can use interprocess communication services of your operating system (e.g. message queues, pipes, signals, etc.).

In simple application cases you can implement the communication between the threads by using shared variables. After the foreign thread set the variable the GUI thread can detect it and update the Property in the GUI application.

The modification of data values by interrupt service routines have also be relayed to the GUI thread. Again use interprocess communication services or shared variables.

If you are developing an application for the WebGL Platform Package, ignore the above indications as multi-threading and interrupt service routines are actually not relevant when implementing JavaScript code.

To process an update event relayed from a foreign thread or an interrupt service routine you will need to adapt the main loop of the GUI application. Typically, this function processes user inputs, timer expirations and performs screen updates. Now, you will need to extend it to also process and dispatch the foreign messages. Most of our provided Build Environments are already prepared to allow you a simple adaptation of the main loop. Please take a look at the module DeviceDriver.c found in the Build Environment. It contains following three functions:

/* The following function is executed at the initialization time of the GUI application. */ void DeviceDriver_Initialize( void ) { } /* The following function is executed shortly before the GUI application terminates. */ void DeviceDriver_Deinitialize( void ) { } /* The following function is executed periodically. The returned value has to indicated whether the function has processed any data (!=0) or not (==0). */ int DeviceDriver_ProcessData( void ) { return 0; }

The functions DeviceDriver_Initialize and DeviceDriver_Deinitialize are intended to perform code at startup and shutdown of the GUI application. Here you can, for example, setup a message queue to receive notifications from a foreign thread or initialize other operating system services essential for the communication with foreign threads.

The third function DeviceDriver_ProcessData is the most interesting one. This function is called periodically by the GUI thread and its aim is to check whether there are some important notifications to process, e.g. whether there are messages waiting in a message queue. If this is the case, the function should peek the message and depending on its content invoke a corresponding GUI function to update a Property. The following code demonstrates the approach:

/* Process messages provided from other threads */ int DeviceDriver_ProcessData( void ) { msg_t msg; /* Peek a message from the queue, if any */ if ( message_queue_peek( &msg ) == 0 ) return 0; /* Evaluate the message type */ if ( msg.id == MSG_CURRENT_VALUE_CHANGED ) { /* Obtain access to the Device Interface instance */ ApplicationDeviceClass device = EwGetAutoObject( &ApplicationDevice, ApplicationDeviceClass ); int newValue = msg.value; /* Invoke the function to update the Property */ ApplicationDeviceClass__UpdateCurrent( device, newValue ); /* A Property has been updated */ return 1; } /* No interesting message for GUI */ return 0; }

If you are developing a device running without any operating system (so-called bare metal), the potential problem of how to exchange data between a worker and the GUI threads disappears. What remains is a problem of processing updates generated by interrupt service routines. As mentioned above, invoking GUI code from an interrupt service routine has to be avoided in any case. Since there is no operating system, the unique possibility to exchange the information between the both partners is to use a shared variable.

In such bare metal application case, the interrupt service routine after detecting a particular event increments a global counter variable. The function DeviceDriver_ProcessData compares periodically the value of this counter and if it detects an alternation updates the respective Property in the GUI application:

volatile unsigned int SharedCounter = 0; unsigned int ProcessedCounter = 0; volatile int CurrentValue = 0; /* The interrupt service routine after modifying the CurrentValue variable just increments a counter variable */ void InterruptServiceRoutine( void ) { CurrentValue = read_some_adc_register(); SharedCounter++; } /* Detect and process alternations of the shared variable */ int DeviceDriver_ProcessData( void ) { /* Any update reported by the interrupt service routine? */ if ( SharedCounter != ProcessedCounter ) { /* Obtain access to the Device Interface instance */ ApplicationDeviceClass device = EwGetAutoObject( &ApplicationDevice, ApplicationDeviceClass ); /* Invoke the function to update the Property */ ApplicationDeviceClass__UpdateCurrent( device, CurrentValue ); /* Also adjust the reference counter to ensure that each reported update is processed only once */ ProcessedCounter++; /* A Property has been updated */ return 1; } /* No interesting data to update */ return 0; }

If you are developing an application for a target system where no adequate Build Environment is available, you will need to take care of the necessary adaptation directly in the main() function of the GUI application. This function contains a while loop where all user inputs are dispatched and screen updates are triggered periodically. Just modify the loop so that it peeks messages from a message queue or checks shared variables as explained above. For example:

volatile unsigned int SharedCounter = 0; unsigned int ProcessedCounter = 0; volatile int CurrentValue = 0; /* The interrupt service routine after modifying the CurrentValue variable just increments a counter variable */ void InterruptServiceRoutine( void ) { CurrentValue = read_some_adc_register(); SharedCounter++; } /* The main function */ void main( void ) { XEnum cmd = CoreKeyCodeNoKey; [...] /* Endless loop, until the 'power' key is pressed... */ while ( cmd != CoreKeyCodePower ) { int events = 0; int timers = 0; int signals = 0; int devices = 0; int touch; XPoint point; /* Any event reported by the interrupt service routine? */ if ( SharedCounter != ProcessedCounter ) { /* Obtain access to the Device Interface instance */ ApplicationDeviceClass device = EwGetAutoObject( &ApplicationDevice, ApplicationDeviceClass ); /* Invoke the function to update the Property */ ApplicationDeviceClass__UpdateCurrent( device, CurrentValue ); /* Also adjust the reference counter to ensure that each reported update is processed only once */ ProcessedCounter++; /* A Property has been updated */ devices = 1; } [...] } [...] }

Use the Property within the GUI application

As explained in the section above, Properties are typically implemented within a Device Interface class. Consequently, the Properties can be used (read or modified) only in context of an instance of such class. Before a Property can be accessed it is thus necessary to create a new or estimate an existing Device Interface instance. For your convenience, such instance is prepared automatically when you add a new Device Interface to your project. You recognize it as a global autoobject named per default Device:

The advantage of a global autoobject is that it can be accessed from everywhere in your application. You should only take care of the correct addressing the instance by using its complete name composed of the unit containing the autoobject, the :: (double colon) delimiter and the name of the autoobject itself. For example, the autoobject shown in the screenshot above is addressed by the expression Application::Device. A GUI component containing a slider could thereupon implement following code to modify the Voltage Property every time the user drags on the slider. Similarly, it can evaluate the Property Current and display it e.g. within a Text view:

// Write access to the Property 'Voltage' Application::Device.Voltage = Slider.CurrentValue; // Read access to the Property 'Current' Text.String = string( Application::Device.Current );

If your GUI application is composed of widgets it is even much easier to use the Properties. For example, the Horizontal Slider widget can be connected directly to a Property existing in the Device Interface. When the user drags on the slider, the Property is adjusted implicitly. On the other hand, if the software running on the device reports an alternation of the respective value, the slider reacts to this notification assuming the corresponding position. In this case the connection between the GUI widget and the Property is established without writing a single line of code.

Following example project demonstrates the both above explained approaches of using the Device Interface Properties. The project consists of a GUI component containing a Horizontal Slider and a Push Button. The slider is connected to the Device Interface property Voltage. Accordingly, dragging on it modifies this Property. The button, in turn is connected to a slot method which sets the Property Voltage to the value 0. Since the example is detached from a real device, it limits to log a message that the Property has been changed. In practice the implementation could modify GPIO pins, etc. and so control the voltage in the power supply:

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 9.00

React to the alternation of the Property

If the Property is connected to one of the ready-to-use widgets, the widgets react automatically to notifications generated when the Property is modified. The affected widgets update thereupon their appearance implicitly. If you implement your own widgets or other GUI components depending on the update notifications, use the Property Observer for this purpose. You add such observers to your GUI components wherever appropriate and configure them to be connected to the original Property. When the Property is alternated all associated observers are notified automatically.

Use Properties for internal communication

In the preceding description we focused mainly on the usage of Properties as communication service between the extern software running on your device and the GUI application. Such Properties were accommodated consequently in the Device Interface. Properties, however, can also be used outside of a Device Interface for internal communication purpose. This approach helps to follow the Model-View-Controller software design pattern focused on the separation of the application in visual (View) and functional (Model) software blocks.

It permits as well a simply decoupling of existing GUI components. For example, you can add the Property to an ordinary GUI component. Then you add Property Observer to other components interested in this Property. When instantiating these depending components just initialize the Observer embedded inside them to be connected with the right Property. Once the connection is established, the depending components can react to Property alternations generated by the original GUI component.