Home / Data Monitor / Annex A
Page options

Annex A


 

Subject-Observer-Widget Relationship

Class Relationship

The lifetime of the observer is managed by the QWidget. If the QWidget is visible, it will instantiate one or more observers. For most dynamic widgets, one Observer should be sufficient. For widgets such as the Float and Binary Plots, there should be one Observer for each plot on the chart.

 

An Observer works very closely with the associated QWidget. Based on the constructor arguments, passed from the widget, the constructor of the observer will attach itself to the appropriate Subject.

Subject Class Hierarchy

The base class, mpt_var,  implements is the Subject. It has methods to attach and detach observers. It is a abstract class that defines virtual functions to pack and extract data.

 

value_var is the base class for all classes that actually contain data values. Inspired by Perl's ability to work with values and text, value_var provides virtual methods to convert data to any type. This is very important for dynamic widgets. For example, a "Float Field", uses value_var::to_double(), to convert the underlying value into a double. It does not need to know whether the attached message field was actually a generic_var<double> or generic_var<float> or generic_var<int> or even a vstring_var.

 

complex_var is a Composite class (composite design pattern). In this respect, mpt_var is a Component and value_var is actually a Leaf. It delegates the tasks of packing and extraction to its children.

 

This is an example of how applications use it:

class address_record : public complex_var

{

    generic_var<unsigned short> block_num;

    string_var<64> street_name;

    generic_var<unsigned int> postal_code;

    generic_var<char> country_code;

};

 

class employee_record : public complex_var

{

public:

    COMPLEXCONST(employee_record)

    generic_var<int> staff_id;

    string_var<32> first_name;

    string_var<32> last_name;

    address_record address;

    generic_var<double> salary;

};

 

Our applications, unlike Data Monitor, have fixed messages that are defined in the sample code above. This sample code is actually generated automatically from our online portal where our developers enter the required messages.

 

Data Monitor will need to create a factory that allows it to instantiate these messages dynamically from an XML description. We would retool our portal to generate the XML for Data Monitor once the schema has been defined.

 

generic_var is a template class that can be specialized for all basic data types, i.e. int, float, double, etc. A generic_var<int> is intelligent enough to unpack 4 bytes into itself and pack itself into 4 bytes and so on.

 

string_var was designed as a template class specialized to the length of the string. This is to eliminate the need to pass in the length in the constructor otherwise. However, this would not work if we are to use a factory. So, a yet to be implemented class, v_string_var will take its place (the length of the string will be passed as an argument in the constructor by the factory.)

 

XX_protocol_var is just an example, as there is no such thing as XX_protocol. It represents how all message protocols are implemented.  A message protocol extends complex_var to allow an application to send and receive data. XX_protocol_var will typically depend on a Singleton object - the XX_protocol_socket. This object abstracts the connection. For example, a connection can be a Serial (via COM port), TCP/IP, UDP, UDP-multicast, Digital I/O card, shared memory, reflective memory, message queue, etc. XX_protocol_var is responsible for tagging on the headers and checksums to the payload and sending it out through the channel, if needed. It is also responsible for receiving new packets from the channel, examining the header for the ID and deciding which instance of XX_protocol_var to send the packet to for unpacking. XX_protocol_var typically has two static methods, which we like to call incoming and outgoing, to get it to receive data and send data out.

 

mpt_var has a implements a system to notify when packet values have changed and need to be sent out. All classes derived from value_var will call setRMDirty (set remote dirty) when its values change. For example, in the assignment operator, we check if the value have changed and call setRMDirty. Similar logic is found in other methods that modify contents. The setRMDirty is recursively called upwards to its ancestors by mpt_var. For example, if,

 

employee.address.country_code = 'A'; // assume previously not A

 

This will cause employee.address.country_code.setRMDirty() to be called, which calls employee.address.setRMDirty(), which calls employee.setRMDIrty(). For complex_var this does nothing, because complex_var is not a message, it is only a C-like structure. XX_protocol_var overloads setRMDirty to set an internal flag, which we typically call dirty. When the dirty flag is set, it will be packed and a message sent out through the channel the next time XX_protocol_var::outgoing is called.

 

XX_protocol_var contains all the additional properties that it needs to handle messages, such has its ID. It typically also maintains a hash map of IDs to instances. This is needed to direct packets to the appropriate instance.

 

For example,

 

class employee_record : public XX_protocol_var

{

public:

    employee_record() : XX_protocol_var(0x123) /* Message ID x123 */

    generic_var<int> staff_id;

 

    

 

};

 

class company_record : public XX_protocol_var

{

public:

    company_record() : XX_protocol_var(0x231) /* Message ID x231 */

    generic_var<int> staff_id;

 

    

 

};

 

Our framework implements the separation of concerns -- between the application logic and the message handling. For example, an application may have some TCP_var objects which communicate through TCP/IP, and a couple of Discrete_IO_var objects which talk to a data acquisition card, and some Serial_var objects that talk to the COM port. From a developer point of view, all these are abstracted out. By assigning a value to a field of TCP_var, data goes out by TCP. By assigning a value to a field of Discrete_IO_var, it goes out to the card. The only thing to ensure is that the main loop looks like:

 

TCP_var::incoming();

Discrete_var::incoming();

Serial_var::incoming();

Observer::update_all();

TCP_var::outgoing();

Discrete_var::outgoing();

Serial_var::outgoing();

 

XX_protocol_var objects refer to a singleton XX_protocol_socket, typically implemented as a static property. The "incoming" and "outgoing" methods are therefore static methods. The XX_protocol_socket typically abstracts the protocol details, including how messages are read and written, starting and checking for the connection, etc.

 

A typical "incoming" method does the following:

  1. Checks whether if still connected, if not attempt to establish a connection. (A timeout for this is usually specified). If a connection is successfully established, perform some handshaking if required (eg. to subscribe for messages).
  2. Check if there is any messages waiting to be read. If there are, read the messages, check for the boundaries of a message, checksums, etc.
  3. When a message is received in entirety, check the ID of the message to determine which XX_protocol_var instance the message should be routed to. This is typically facilitated by a static map or hash_map which maps ID to a reference/pointer to the instance.
  4. The message is passed to the instance which extracts the message using the Composite Design Pattern.
  5. The leaf nodes will unpack the data into themselves. The leaf nodes will notify any attached observers. Notification will move up the object hierarchy. The leaf nodes will call the parent (if any), which will notfiy any attached observers, which will in turn call its parent as so on.
  6. There are two types of notification: Observer::setDirty, which means data was changed, and Observer::notDirty, which means data was not changed.
  7. Observer::notify only sets a flag "dirty". It does not update the widget yet. This is because Observer::notify may be expected to be called often and it is essential that it is a trivial function. The default behaviour of notDirty is to do nothing (i.e. it doesn't set a flag). This is the typical reaction as there is no need to update the display if data is not changed.
  8. In summary, incoming will check for data and unpack them into the respective XX_protocol_var instances and notify the attached observers.

A typical "outgoing" method does the following:

  1. Checks whether if still connected, if not attempt to establish a connection. If a connection is successfully established, perform some handshaking if required. (Same as incoming)
  2. Iterate through all XX_protocol_var checking if the dirty flag (set by the overloaded setRMDirty method) is set.
  3. For those that are set, retrieve the size of the message that is required by querying its children using the Composite pattern. Allocate memory for the message. Then again use the Composite pattern to pack the message.
  4. Calls the XX_protocol_socket to send the message. If sending is sucessful, clear the dirty flag.
  5. In summary, outgoing will check for "dirty" XX_protocol_var instances and pack them into messages and send them out.

 

The observer::updateall is a static method that iterates all observer objects calling the update method. All observers will be called, regardless of whether the dirty flag (either set by setDirty or notDirty) is set or not. This is to allow observers to be called at every cycle, and to allow observers to decide what needs to be done. (Some observers may need to do something every cycle, such as check for timeout). Typically, an observer will check its dirty flag and not do anything if it is not dirty.

 

Although observer::update is called every cycle, there could be a situation where two or more messages of the same ID is received in the cycle. This would result in messages being "lost". However, if observer::update simply updates the GUI, this is not a problem because it does not make a difference to the user. In situations where messages cannot be lost (eg. during recording), the extract function of the XX_protocol_var must be overloaded.

  

Handling of variable length records

 

Let's revisit our employee_record. Assume that we wish to keep a record of the of the man-hour spent on each project assigned to the staff. Let's assume also, that different staff may be involved in different number of projects, some may have one project, some may have many projects and some may be just hanging around drinking coffee.

 

class manhour_record : public complex_var

}

public:

COMPLEXCONST(manhour_record)

string_var<24> project_name;

generic_var<unsigned int> manhour;

array_var<unsigned int> daily_breakdown;

};

 

class employee_record : public complex_var

{

public:

    COMPLEXCONST(employee_record)

    generic_var<int> staff_id;

    string_var<32> first_name;

    string_var<32> last_name;

    address_record address;

    generic_var<double> salary;

flex_var<manhour_record> timecharge;

};

 

The class flex_var allows variable number of manhour_record objects to be instantiated. As flex_var is-a complex_var, it uses the complex_var 's implementation of managing its children. In extract, it will check the message to determine the number of children it needs to have and adjust its number children -if the message has more elements, it will instantiate additional children and vice versa. Applications attach themselves to the flex_var to check for the number of elements and the logic will handle accordingly.

 

This is unlikely to be suitable for the Data Monitor as our widgets need to attach to the elements themselves, and this is not possible if the flex_var may resize on each message received. Going back to our example, if we wish to configure Data Monitor to display the first four projects for an employee.

 

 

This would mean that Data Monitor will need to instantiate all 4 children for timecharge regardless of the number of elements in the received message. If an employee record with more than 4 projects are received, then the remaining elements will not be unpacked by the Composite pattern since there are only 4 fixed children. The Composite will need to have the intelligence to silently discard the remainder. Unpacking only 4 is not a problem since we only display the first 4 projects. When there are less than 4 elements, the library provides a setInvalid method which can be called to invalidate the field. The library typically sets all bits to 1 for all generic_var and clears string_var to an empty string. In the example above, we only receive 3 fields.

 

This idea may be extended to other fields that are not displayed, for example address and salary. So the idea of instantiating children on demand may extend to all complex_vars. We don't need "instantiation-on-demand" in our applications, so this will be unique to Data Monitor.

 

This would not affect the recording, since the message protocol object would be responsible to write the entire received message to the sinks.

 

Array_var is the other variable length field. A similar method can be used.

 

All dynamic and control widgets will present a field selection dialog to allow the user to select the message field to display.

 

 

The spin box for street_name will only allow 0-1, since street_name is a static array of 2 elements.

 

The spin box for timecharge will be from 0 to 16384, since flex_var is a variable length array, but we don't expect anything really large.

 

The spin box for daily_breakdown will be from 0 to 16384, since array_var is also a variable length array.

 

By default, the spin boxes will always show zero when the dialog is first displayed.

 

The selection of street_name[idx] will display the value of the idx-th element of street_name.

 

The selection of timecharge[idx] will display the number of elements in timecharge. The index, idx, will be ignored.

 

The selection of manhour of timecharge[idx], will display the manhour of the idx-th timecharge.

 

The selection of daily_breakdown[idx2] of timecharge[idx] will display the number of elements in daily_breakdown regardless of the value of idx2.

 

The selection of <<string>> which is a special value for array_var, will display the array contents of daily_breakdown of the idx-th timecharge as text. The to_const_char_ptr will be used on the array_var itself. The idx2 of daily breakdown will be ignored.

 

The selection of <<value>> which is a special value for array_var will display the value of the idx2-th element of daily_breakdown of the idx-th timecharge.

 

The possible interaction between the various software components is shown:

 

 

The factories are provided by the message protocol plugins.

 

The widget property pages are provided by the widget plugins.


    Post a comment

    Your Name or E-mail ID (mandatory)

     

    Note: Your comment will be published after approval of the owner.




     RSS of this page

    Author: Guest   Version: 1.2   Last Edited By: Guest   Modified: 18 Oct 2008