********************
Presentations
********************


I. Introduction to Dia Presentations
====================================
 
A presentation is an iDigi Dia module which is responsible for gathering
sample data from channels and re-presenting the data in another format. The
newly formatted data is usually sent over a network or written to a storage
device. 
 
Presentations gather sample data from the :class:`Channel Database
<channels.channel_database.ChannelDatabase>`. They do so either by
polling--for example, checking on a time interval if a channel of
interest has changed, or by interrupt--receiving an indication by
means of a call back function by subscribing to one or more channels.

All presentations in the Dia must meet the following requirements: 
   
   * They must inherit from
     :class:`~presentations.presentation_base.PresentationBase`.
   * They must have zero or more settings. 
   * They must implement the following
     :class:`~presentations.presentation_base.PresentationBase`
     interface methods:

      * apply_settings(): called when new settings are applied. 
      * start(): called when the presentation is started. 
      * stop(): called when the presentation is stopped. 

There are many types of presentations which can be implemented with the Dia.
Here are a few examples, some of which have already been implemented and
included with the Dia distribution: 

   * A command line interface for browsing channels, system
     settings, and historical logs. 
 
   * A single web page listing the current status of channels in
     the system. 
 
   * A module which creates a new XML document on a time interval
     and uploads this document to a remote server using HTTP. 
 
   * A module which uploads channel data automatically to the
     iDigi platform. 
 
   * A module which presents data as a set of Modbus registers. 
 
   * A module which watches a single channel and when this
     channel is True, it will send an e-mail containing the state
     of the system to a configured destination. 
 
In order to illustrate how one may go about creating their own Dia
Presentation we will walk through an example of what is needed to create
the last presentation in the above list: the SMTP e-mail presentation. 
 
 
II. Implementing a Presentation 
===============================
 
A. Where are the Existing Presentation Modules Located? 
-------------------------------------------------------
 
Existing presentation modules for the Dia are located in the
"src/presentations" directory of the distribution. Each individual
presentation is located in its own directory. For example, the console
presentation is stored in the "src/presentations/console" directory. 

 
B. The SMTP E-Mail Presentation Illustrated
-------------------------------------------
 
The SMTP presentation is located in the file
"src/presentations/smtp/smtp.py". It is implemented to monitor a single
channel for a sample value of "True" and when that value is found to
send an e-mail to a configured destination containing the current state
of all the channels on the system. If we open the source file in a text
editor we may begin to examine the implementation of the presentation. 
 
Below the copyright statement and triple-quoted documentation string we
will find the import statement. All presentations must, at a minimum, 
import the following items::
 
    from settings.settings_base import SettingsBase, Setting 
    from presentations.presentation_base import PresentationBase 
 
All presentation modules are implemented by defining a class which is
derived from
:class:`~presentations.presentation_base.PresentationBase`. A
:class:`~presentations.presentation_base.PresentationBase` class will
contain Settings objects (accessed via methods defined in
:class:`~settings.settings_base.SettingsBase`).
 
This presentation will need to validate e-mail addresses. It does this
by means of a regular expression. The SMTP presentation module will
therefore import the re module. 

The presentation module will also need to asynchronously manage its
outbox. That is to say, if the upstream SMTP server is busy the system
needs to continue running without pausing until the server responds.
This is done by running the potentially slow SMTP functions in their own
thread. Messages are sent from the system thread context to the SMTP
thread context by way of a Queue object. 
 
The total import list is thus given as::
 
    import threading
    import re
    import digi_smtplib as smtplib
    import socket

    from Queue import Queue
    from settings.settings_base import SettingsBase, Setting
    from presentations.presentation_base import PresentationBase
    from channels.channel_publisher import ChannelDoesNotExist
    from common.helpers.format_channels import dump_channel_db_as_text 
 
For more information on standard Python library functions, please see:
 
    http://docs.python.org/library/  

Below the imports section of the SMTP presentation module our
presentation class, SMTPHandler, is created. It derives from
:class:`~presentations.presentation_base.PresentationBase, threading.Thread`
as all presentation modules do::

 
    class SMTPHandler(PresentationBase): 

 
When an instance of this presentation class is created from the
:class:`Presentation Manager
<presentations.presentation_manager.PresentationManager>`, execution
will begin at the __init__() method. In the __init__() method, the
following tasks are completed:
 
   1. A handle to the core_services object is stored in a local
      class-private variable. This handle will be used later to look
      up other system objects (e.g. the :class:`Channel Database
      <channels.channel_database.ChannelDatabase>` and :class:`Channel
      Publisher <channels.channel_publisher.ChannelPublisher>`
      objects).
 
   2. A Queue object is created. 
 
   3. The settings "to_address", "from_address", "subject",
      "server_address", "port", and "monitored_channel" are
      created. 
 
   4. The :class:`~presentations.presentation_base.PresentationBase`
      constructor is called and it is handed references to the current
      object, the name of the current instance, and a list of defined
      settings.
 
When __init__() returns, the :class:`Presentation Manager
<presentations.presentation_manager.PresentationManager>`
will attempt to apply driver settings. This occurs in the
apply_settings() method.
 
Settings are applied in exactly the same manner as they are in Dia
device driver abstraction. Please see the section, "Introduction to Dia
Device Drivers".

.. todo:: Miguel I would review the following two paragraphs. They are 
          not up to date. See "src\presentations\smtp\smtp.py".

Aside from the standard set of operations to apply settings in the Dia
system
(:class:`~settings.settings_base.SettingsBase`.merge_settings(),
:class:`~settings.settings_base.SettingsBase`.verify_settings(),
:class:`~settings.settings_base.SettingsBase`.commit_settings()) below
the settings validations there is a section which retrieves the
"monitored_channel" name setting, fetches the :class:`Channel
Publisher <channels.channel_publisher.ChannelPublisher>` by way of the
Channel Manager via the Core Services object, and asks the
:class:`Channel Publisher
<channels.channel_publisher.ChannelPublisher>` to call the
SMTPHandler's queue_msg() function with the subscribed channel each
and every time it changes::
 
    ## subscribe to monitored_channel 
    cm = self.__core.get_service("channel_manager") 
    cp = cm.channel_publisher_get() 
    cp.subscribe(accepted['monitored_channel'], self.queue_msg) 

.. todo:: Miguel I would review source code. I think we should remove
          "self.apply_settings()" (line 158).
 
After settings are applied, the presentation's start() method will be
called. The SMTP presentation's start() method is very simple: it calls
the threading.Thread.start(self) method which will start a class member
function named run() in its own thread context. 
 
When the presentation begins its execution loop in the run() method, it
will immediately block execution on the queue object which had been
created in the __init__() method. We will return to what happens when an
item is available in the queue in a moment. Let us turn our attention to
how items are queued in the first place. 
 
Items are queued when the channel sample indicated by the
"monitored_channel" setting changes. When the channel sample changes,
it will call the queue_msg() method of the SMTPHandler instance. The
instance will first check to see if the channel now contains a True
value and then it will begin to construct an e-mail message containing
the state of all channels by accessing the :class:`Channel Database
<channels.channel_database.ChannelDatabase>`. The last action this
function will perform will be to place the e-mail message into the
queue.
 
Queuing the message will cause the SMTPHandler's thread to wake up in
the run() method.  The message will then be paired with the rest of the
e-mail server, to, and from settings from the Dia settings and finally
it will attempt to be queued using the Python SMTP library's sendmail()
function. 
 
The final method to consider is the stop() method. The stop method is
very simple. It checks to ensure that the driver is running, and if
so, it will set the running flag to False and signal the
Presentation's thread via the queue to terminate by sending a dummy
message.  Queuing anything with the start_flag set to False in this
driver will cause the mail outbox thread to terminate. Finally, the
stop() method returns True to the :class:`Presentation Manager
<presentations.presentation_manager.PresentationManager>`.


C. Defining a Presention in the Configuration File
--------------------------------------------------


 
D. A Summary of How Presentations Execute 
-----------------------------------------
 
   1. A presentation is loaded and its __init__() method is
      called. 
 
   2. Settings are applied by calling the driver's
      apply_settings() method. 
 
      a. First a call to
         :class:`~settings.settings_base.SettingsBase`.merge_settings()
         should be made to merge new settings from the pending
         registry and the running settings to the pending registry.
 
      b. Second, a call to
         :class:`~settings.settings_base.SettingsBase`.verify_settings()
         will return three items: a dictionary of accepted setting, a
         dictionary of rejected settings, and a dictionary of required
         settings that where not found.
 
      c. :class:`~settings.settings_base.SettingsBase`.commit_settings()
         should be called with a list of the settings that were
         successfully accepted and applied. This will adjust the
         device's running settings registry.

      d. Finally, the apply_settings method should return the
         list of settings accepted, rejected, and not_found. 
 
   3.  The presentation's start() method is called. The
       presentation is now available. 
 
   4.  The presentation's stop() method may be called. The
       presentation is expected to stop and the method should
       return True. Once the method has returned the Presentation
       Manager may restart the driver by calling the start() method
       once again. 
 
 
