- Refactor the ntfsend and ntfsubscribe python samples to make use of
  the new pyosaf utils implementation of enhancement ticket #2602.
- Add a new ntfread sample to demonstrate usage of the new NtfReader
  utils.
---
 python/samples/ntfread      | 411 ++++++++++++++++++++
 python/samples/ntfsend      | 922 ++++++++++++++++++++++++++++++++------------
 python/samples/ntfsubscribe | 795 ++++++++++++++++++++++++--------------
 3 files changed, 1591 insertions(+), 537 deletions(-)
 create mode 100755 python/samples/ntfread
 mode change 100644 => 100755 python/samples/ntfsend
 mode change 100644 => 100755 python/samples/ntfsubscribe

diff --git a/python/samples/ntfread b/python/samples/ntfread
new file mode 100755
index 0000000..a70552b
--- /dev/null
+++ b/python/samples/ntfread
@@ -0,0 +1,411 @@
+#! /usr/bin/env python
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+# pylint: disable=unused-argument
+"""
+ntfread tool used to read alarm and/or security alarm notifications from NTF
+
+Run ntfread --help/-h for more detail on usage
+"""
+from __future__ import print_function
+import sys
+import argparse
+from datetime import datetime, tzinfo, timedelta
+
+from pyosaf import saNtf
+from pyosaf.saAis import eSaAisErrorT
+from pyosaf.utils.ntf.reader import NtfReader
+
+
+class UTCOffsetTZ(tzinfo):
+    """ Time zone represented as UTC hour-offset """
+    def __init__(self, hour_offset):
+        """ UTCOffsetTZ constructor """
+        self._hour_offset = hour_offset
+        super(UTCOffsetTZ, self).__init__()
+
+    def utcoffset(self, dt):
+        """ Return offset of local time from UTC, in minutes east of UTC """
+        return timedelta(hours=self._hour_offset)
+
+    def dst(self, dt):
+        """ Return the daylight saving time (DST) adjustment, in minutes east
+        of UTC, or None if DST information isn't known
+        """
+        return timedelta(0)
+
+    def tzname(self, dt):
+        """ Return the time zone name represented in UTC hour-offset format as
+        'UTC+/-offset'
+        """
+        if self._hour_offset > 0:
+            tzname = "UTC+%02d:00" % self._hour_offset
+        elif self._hour_offset < 0:
+            tzname = "UTC-%02d:00" % self._hour_offset
+        else:  # hour_offset = 0
+            tzname = "UTC"
+
+        return tzname
+
+
+def satime_to_readable_datetime(sa_time, format_str=None):
+    """ Convert the given SaTimeT time value to readable datetime string
+    representation in ISO 8601 format
+
+    Args:
+        sa_time (SaTimeT): SaTimeT time value
+        format_str (str): Format string for datetime output representation
+
+    Returns:
+        str: Datetime representation in format specified by format_str, or in
+             ISO 8601 format 'YYYY-MM-DDTHH:MM:SS' if format_str not provided
+    """
+    time_sec = sa_time / 10**9  # Convert the time from nsec to sec 1000000000
+
+    # Calculate the UTC offset for time zone information
+    naive_dt = datetime.fromtimestamp(time_sec)
+    utc_offset = (naive_dt - datetime.utcfromtimestamp(time_sec))
+    hour_offset = utc_offset.total_seconds() // 3600
+    utc_offset_tz = UTCOffsetTZ(hour_offset)
+
+    aware_dt = naive_dt.replace(tzinfo=utc_offset_tz)
+
+    if format_str is not None:
+        return aware_dt.strftime(format_str)
+
+    return aware_dt.isoformat(' ')
+
+
+def _validate_ntf_class_id(arg):
+    """ Validate the input NtfClassId with format 'VENDOR_ID.MAJOR_ID.MINOR_ID'
+
+    Args:
+        arg (str): The NtfClassId to validate
+
+    Returns:
+        SaNtfClassIdT: The validated NtfClassId
+    """
+    if arg.count('.') != 2:
+        msg = "%r is not in correct format 'VENDOR_ID.MAJOR_ID.MINOR_ID'" % arg
+        raise argparse.ArgumentTypeError(msg)
+    if not arg.split('.')[0] or not arg.split('.')[1] or not arg.split('.')[2]:
+        msg = "%r does not have enough required values" % arg
+        raise argparse.ArgumentTypeError(msg)
+
+    # Validate element type
+    try:
+        vendor_id = int(arg.split('.')[0])
+        major_id = int(arg.split('.')[1])
+        minor_id = int(arg.split('.')[2])
+    except ValueError:
+        msg = "%r must consist of all integers" % arg
+        raise argparse.ArgumentTypeError(msg)
+
+    return saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+
+
+def print_notification_header(header_info):
+    """ Print information of the header in the received notification
+
+    Args:
+        header_info (NotificationInfo): NotificationInfo structure containing
+            information of the notification header
+    """
+    dt_format_str = "%c %Z"
+    print ('notificationId = %d\n'
+           'eventType = %s\n'
+           'notificationObject = "%s"\n'
+           'notifyingObject = "%s"\n'
+           'notificationClassId = %d.%d.%d\n'
+           'eventTime = %d (%s)\n'
+           'additionalText = "%s"' %
+           (header_info.notification_id,
+            saNtf.eSaNtfEventTypeT.whatis(header_info.event_type),
+            header_info.notification_object,
+            header_info.notifying_object,
+            header_info.ntf_class_id.vendorId,
+            header_info.ntf_class_id.majorId,
+            header_info.ntf_class_id.minorId,
+            header_info.event_time,
+            satime_to_readable_datetime(header_info.event_time, dt_format_str),
+            header_info.additional_text))
+    if header_info.additional_info:
+        print("numAdditionalInfo = %d" % len(header_info.additional_info))
+        for add_info in header_info.additional_info:
+            print ("- Info ID: %d\n"
+                   "  Info Type: %s (%d)\n"
+                   "  Info Value: %s" %
+                   (add_info.info_id,
+                    saNtf.eSaNtfValueTypeT.whatis(add_info.info_type),
+                    add_info.info_type,
+                    add_info.info_value))
+    print()
+
+
+def print_alarm_notification(notification):
+    """ Print information of the alarm notification found from reading NTF
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the found alarm notification
+    """
+    print("===  %s - Alarm  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("probableCause = %s" %
+          saNtf.eSaNtfProbableCauseT.whatis(notification.probable_cause))
+    print("perceivedSeverity = %s" %
+          saNtf.eSaNtfSeverityT.whatis(notification.perceived_severity))
+
+    if args.verbose:
+        print("trend = %s" %
+              saNtf.eSaNtfSeverityTrendT.whatis(notification.trend))
+        print("thresholdInformation:\n"
+              "- Threshold ID: %d\n"
+              "  Threshold Value Type: %s (%d)\n"
+              "  Threshold Value: %s\n"
+              "  Threshold Hysteresis: %s\n"
+              "  Observed Value: %s\n"
+              "  Arm Time: %s" %
+              (notification.threshold_information.threshold_id,
+               saNtf.eSaNtfValueTypeT.whatis(
+                   notification.threshold_information.threshold_value_type),
+               notification.threshold_information.threshold_value_type,
+               notification.threshold_information.threshold_value,
+               notification.threshold_information.threshold_hysteresis,
+               notification.threshold_information.observed_value,
+               satime_to_readable_datetime(
+                   notification.threshold_information.arm_time)))
+        print("numSpecificProblems = %d" % len(notification.specific_problems))
+        for spec_problem in notification.specific_problems:
+            print("- Problem ID: %d\n"
+                  "  Problem Class ID: %d.%d.%d\n"
+                  "  Problem Type: %s (%d)\n"
+                  "  Problem Value: %s" %
+                  (spec_problem.problem_id,
+                   spec_problem.problem_class_id.vendorId,
+                   spec_problem.problem_class_id.majorId,
+                   spec_problem.problem_class_id.minorId,
+                   saNtf.eSaNtfValueTypeT.whatis(spec_problem.problem_type),
+                   spec_problem.problem_type, spec_problem.problem_value))
+        print("numMonitoredAttributes = %d" %
+              len(notification.monitored_attrs))
+        for monitored_attr in notification.monitored_attrs:
+            print("- Attribute ID: %d\n"
+                  "  Attribute Type: %s (%d)\n"
+                  "  Attribute Value: %s" %
+                  (monitored_attr.attribute_id,
+                   saNtf.eSaNtfValueTypeT.whatis(
+                       monitored_attr.attribute_type),
+                   monitored_attr.attribute_type,
+                   monitored_attr.attribute_value))
+        print("numProposedRepairActions = %d" %
+              len(notification.proposed_repair_actions))
+        for repair_action in notification.proposed_repair_actions:
+            print("- Action ID: %d\n"
+                  "  Action Value Type: %s (%d)\n"
+                  "  Action Value: %s" %
+                  (repair_action.action_id,
+                   saNtf.eSaNtfValueTypeT.whatis(
+                       repair_action.action_value_type),
+                   repair_action.action_value_type,
+                   repair_action.action_value))
+
+
+def print_security_alarm_notification(notification):
+    """ Print information of the security alarm notification found from reading
+    NTF
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the found security alarm notification
+    """
+    print("===  %s - Security Alarm  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("probableCause = %s\n"
+          "severity = %s\n"
+          "Security Alarm Detector Type: %s (%d)\n"
+          "Security Alarm Detector Value: %s\n"
+          "Service User Type: %s (%d)\n"
+          "Service User Value: %s\n"
+          "Service Provider Type: %s (%d)\n"
+          "Service Provider Value: %s" %
+          (saNtf.eSaNtfProbableCauseT.whatis(notification.probable_cause),
+           saNtf.eSaNtfSeverityT.whatis(notification.severity),
+           saNtf.eSaNtfValueTypeT.whatis(
+               notification.security_alarm_detector.value_type),
+           notification.security_alarm_detector.value_type,
+           notification.security_alarm_detector.value,
+           saNtf.eSaNtfValueTypeT.whatis(notification.service_user.value_type),
+           notification.service_user.value_type,
+           notification.service_user.value,
+           saNtf.eSaNtfValueTypeT.whatis(
+               notification.service_provider.value_type),
+           notification.service_provider.value_type,
+           notification.service_provider.value))
+
+
+# Define command line arguments for ntfread
+parser = argparse.ArgumentParser(
+    description='A SAF NTF client used to read alarm and/or security alarm '
+                'notifications from NTF,\nwhich match optionally provided '
+                'filter argument(s).\nWithout any argument, all alarm and '
+                'security alarm notifications will be read by default.',
+    formatter_class=argparse.RawTextHelpFormatter)
+
+parser.add_argument('--alarmOnly', '-a', action='store_true',
+                    help='read alarm notifications only')
+parser.add_argument('--securityAlarmOnly', '-y', action='store_true',
+                    help='read security alarm notifications only')
+parser.add_argument('--searchMode', '-m', metavar='1...7', type=int,
+                    choices=range(1, 8),
+                    help='numeric value of SaNtfSearchModeT\n'
+                         '(SA_NTF_SEARCH_BEFORE_OR_AT_TIME...'
+                         'SA_NTF_SEARCH_ONLY_FILTER)')
+parser.add_argument('--eventTime', '-T', metavar='TIME', type=int,
+                    help='notification time (in nanosecond) to search for')
+parser.add_argument('--notificationId', '-i', metavar='NOTIF_ID', type=int,
+                    help='notification id to search for')
+parser.add_argument('--alarmEventTypes', '-e', metavar='16384...16389',
+                    type=int, nargs='+', choices=range(16384, 16390),
+                    help='numeric value of alarm SaNtfEventTypeT\n'
+                         '(SA_NTF_ALARM_NOTIFICATIONS_START...'
+                         'SA_NTF_ALARM_ENVIRONMENT)')
+parser.add_argument('--securityAlarmEventTypes', '-E', metavar='20480...20485',
+                    type=int, nargs='+', choices=range(20480, 20486),
+                    help='numeric value of security alarm SaNtfEventTypeT\n'
+                         '(SA_NTF_SECURITY_ALARM_NOTIFICATIONS_START...'
+                         'SA_NTF_TIME_VIOLATION)')
+parser.add_argument('--notificationClassIds', '-c',
+                    type=_validate_ntf_class_id,
+                    metavar='VENDOR_ID.MAJOR_ID.MINOR_ID', nargs='+',
+                    help='notification class identifier\n'
+                         'VENDOR_ID: SaUint32T integer value\n'
+                         'MAJOR_ID: SaUint16T integer value\n'
+                         'MINOR_ID: SaUint16T integer value')
+parser.add_argument('--notificationObjects', '-n', metavar='NOTIFICATION_OBJ',
+                    type=str, nargs='+',
+                    help='notification object (string value)')
+parser.add_argument('--notifyingObjects', '-N', metavar='NOTIFYING_OBJ',
+                    type=str, nargs='+',
+                    help='notifying object (string value)')
+parser.add_argument('--probableCauses', '-p', metavar='0...74',
+                    type=int, nargs='+', choices=range(75),
+                    help='numeric value of SaNtfProbableCauseT\n'
+                         '(SA_NTF_ADAPTER_ERROR...SA_NTF_UNSPECIFIED_REASON)')
+parser.add_argument('--perceivedSeverities', '-s', metavar='0...5', type=int,
+                    nargs='+', choices=range(6),
+                    help='numeric value of alarm SaNtfSeverityT\n'
+                         '(clear=0,ind,warn,min,maj,critical=5)')
+parser.add_argument('--trends', '-t', metavar='0...2', type=int,
+                    nargs='+', choices=range(3),
+                    help='numeric value of SaNtfSeverityTrendT')
+parser.add_argument('--severities', '-S', metavar='0...5', type=int,
+                    nargs='+', choices=range(6),
+                    help='numeric value of security alarm SaNtfSeverityT\n'
+                         '(clear=0,ind,warn,min,maj,critical=5)')
+parser.add_argument('--searchYounger', '-g', action='store_true',
+                    help='SA_NTF_SEARCH_YOUNGER\n'
+                         '(search in descending chronological order w.r.t '
+                         'previously read notification)')
+parser.add_argument('--verbose', '-v', action='store_true',
+                    help='print all information from alarm notifications')
+
+# Parse command line arguments for user input, if any
+args = parser.parse_args()
+
+notification_type = None
+if args.alarmOnly and args.securityAlarmOnly:
+    print ("ERROR: Cannot use both --alarmOnly and --securityAlarmOnly\n")
+    sys.exit(0)
+elif args.alarmOnly:
+    notification_type = saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM
+elif args.securityAlarmOnly:
+    notification_type = \
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM
+
+search_mode = args.searchMode
+event_time = args.eventTime if args.eventTime is not None else 0
+notif_id = args.notificationId if args.notificationId is not None else 0
+
+search_criteria = None
+if search_mode is not None:
+    search_criteria = \
+        saNtf.SaNtfSearchCriteriaT(search_mode, event_time, notif_id)
+
+event_types = args.alarmEventTypes
+if args.securityAlarmEventTypes is not None:
+    if event_types is not None:
+        for evt_type in args.securityAlarmEventTypes:
+            event_types.append(evt_type)
+    else:
+        event_types = args.securityAlarmEventTypes
+
+# Initialize an NTF reader
+reader = NtfReader()
+init_rc = reader.init()
+if init_rc != eSaAisErrorT.SA_AIS_OK:
+    print("ERROR: saNtfInitialize FAILED, rc = %s\n" %
+          eSaAisErrorT.whatis(init_rc))
+    sys.exit(1)
+
+# Set notification read filter values, if any, provided by the users
+if search_criteria is not None:
+    reader.set_search_criteria(search_criteria)
+if args.searchYounger:
+    reader.set_search_direction(
+        saNtf.eSaNtfSearchDirectionT.SA_NTF_SEARCH_YOUNGER)
+if event_types is not None:
+    reader.set_filter_event_types(event_types)
+if args.notificationClassIds is not None:
+    reader.set_filter_ntf_class_ids(args.notificationClassIds)
+if args.notificationObjects is not None:
+    reader.set_filter_notification_objects(args.notificationObjects)
+if args.notifyingObjects is not None:
+    reader.set_filter_notifying_objects(args.notifyingObjects)
+if args.probableCauses is not None:
+    reader.set_filter_probable_causes(args.probableCauses)
+if args.perceivedSeverities is not None:
+    reader.set_filter_perceived_severities(args.perceivedSeverities)
+if args.trends is not None:
+    reader.set_filter_trends(args.trends)
+if args.severities is not None:
+    reader.set_filter_severities(args.severities)
+
+# Start reading notifications
+if notification_type is not None:
+    rc = reader.read([notification_type])
+else:
+    rc = reader.read()
+
+if rc != eSaAisErrorT.SA_AIS_OK:
+    print("ERROR: Reading notifications FAILED, rc = %s\n" %
+          eSaAisErrorT.whatis(rc))
+    sys.exit(1)
+
+# Print out the found notifications
+for notif_type, notif_info in reader:
+    print()
+    if notif_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
+        print_alarm_notification(notif_info)
+    else:
+        print_security_alarm_notification(notif_info)
+    print()
+
+# Finalize the reader
+reader.finalize()
diff --git a/python/samples/ntfsend b/python/samples/ntfsend
old mode 100644
new mode 100755
index c31a9ba..f36cbcd
--- a/python/samples/ntfsend
+++ b/python/samples/ntfsend
@@ -1,251 +1,679 @@
 #! /usr/bin/env python
-
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+""" ntfsend tool used to send a type of NTF notification
+
+Run ntfsend --help/-h for more detail on usage
+"""
+from __future__ import print_function
+import sys
 import argparse
 import time
 
-from pyosaf import saNtf, saAis
-from pyosaf.utils import ntf
-
-
-def construct_additional_info(additional_info_string):
-    ''' Constructs an AdditionalInfo instance from the given string.
-
-        The string must be of the format ID,TYPE,VALUE
-    '''
-
-    info_id = int(additional_info_string.split(',')[0])
-    info_type = int(additional_info_string.split(',')[1])
-    info_value = ','.join(additional_info_string.split(',')[2:])
-
-    return ntf.AdditionalInfo(info_id, info_type, info_value)
-
-
-if __name__ == '__main__':
-
-    # Parse command line arguments
-    parser = argparse.ArgumentParser(
-        description='ntfsend is a SAF NTF client used to send a notificaiton.')
-
-    parser.add_argument('--notificationType', '-T', metavar='0x1000...0x5000',
-                        default='0x4000',
-                        help='numeric value of SaNtfNotificationTypeT'
-                        
'(obj_create_del=0x1000,attr_ch,state_ch,al,sec_al=0x5000)')
-    parser.add_argument('--eventType', '-e', metavar='4096...24589',
-                        help='numeric value of SaNtfEventTypeT'
-                        
'(SA_NTF_OBJECT_NOTIFICATIONS_START...SA_NTF_HPI_EVENT_OTHER)')
-    parser.add_argument('--eventTime', '-E', metavar='TIME',
-                        default=saAis.saAis.SA_TIME_UNKNOWN,
-                        help='numeric value of SaTimeT')
-    parser.add_argument('--notificationClassId', '-c', metavar='VE,MA,MI',
-                        default='162,1,1',
-                        help='vendorid, majorid, minorid')
-    parser.add_argument('--notificationObject', '-n', metavar='NOT_OBJ',
-                        default="",
-                        help='notification object (string value)')
-    parser.add_argument('--notifyingObject', '-N', metavar='NOTIFY_OBJ',
-                        default="",
-                        help='notififying object (string value)')
-    parser.add_argument('--additionalText', '-a', metavar='TEXT',
-                        default="",
-                        help='additional text (string value)')
-    parser.add_argument('--probableCause', '-p', metavar='0..74',
-                        help='numeric value SaNtfProbableCauseT'
-                        'SA_NTF_ADAPTER_ERROR to SA_NTF_UNSPECIFIED_REASON')
-    parser.add_argument('--perceivedSeverity', '-s', metavar='0...5',
-                        type=int, default='4',
-                        help='severity numeric value'
-                        '(clear=0,ind,warn,min,maj,crit=5)')
-    parser.add_argument('--repeatSends', '-r', metavar='NUM',
-                        default=1, type=int,
-                        help='send the same notifification NUM times')
-    parser.add_argument('--burstTimeout', '-b', metavar='TIME',
-                        default=0, type=int,
-                        help='send burst of NUM repeatSends '
-                        '[default: 1] and sleep TIME (usec)'
-                        'between each burst, will continue for ever')
-    parser.add_argument('--additionalInfo', '-i', metavar='ID,TYPE,VALUE',
-                        help='additional information'
-                        'ID: SaNtfElementIdT integer value'
-                        'TYPE: numeric value SaNtfValueTypeT, only '
-                        'SA_NTF_VALUE_STRING=11 is supported')
-
-    args = parser.parse_args()
-
-    # Fill in arguments
-    vendor_id = int(args.notificationClassId.split(',')[0])
-    major_id = int(args.notificationClassId.split(',')[1])
-    minor_id = int(args.notificationClassId.split(',')[2])
-
-    event_time = int(args.eventTime)
-
-    severity = int(args.perceivedSeverity)
-
-    additional_text = args.additionalText
-    notification_object = args.notificationObject
-    notifying_object = args.notifyingObject
-
-    # Initialize the NTF library
-    ntf.initialize()
-
-    # Send the notification
-    ntf_type = int(args.notificationType, 0)
-
-    if ntf_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE:
-
-        # Create sample attributes
-        attributes = []
-
-        attr1 = ntf.Attribute()
-
-        attr1.attribute_id = 1
-        attr1.attribute_type = saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT16
-        attr1.attribute_value = 23
-
-        attributes.append(attr1)
-
-        attr2 = ntf.Attribute()
-
-        attr2.attribute_id = 2
-        attr2.attribute_type = saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32
-        attr2.attribute_value = -3
-
-        attributes.append(attr2)
-
-        # Create sample additional info
-        additional_info = []
-        if args.additionalInfo:
-            
additional_info.append(construct_additional_info(args.additionalInfo))
-
-        # Send the notification
-        for i in range(0, args.repeatSends):
-            ntf.send_object_create_notification(vendor_id,
-                                                major_id,
-                                                minor_id, 
-                                                
additional_text=additional_text,
-                                                
notification_object=notification_object,
-                                                
notifying_object=notifying_object,
-                                                attributes=attributes,
-                                                event_time=event_time,
-                                                
additional_info=additional_info)
-
-            time.sleep(args.burstTimeout)
-
-    elif ntf_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM:
-
-        # Create security alarm sample fields
-
-        detector = ntf.SecurityAlarmDetector(
-            value=15,
-            value_type=saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32
-        )
-
-        user = ntf.ServiceUser(
-            value=-2,
-            value_type=saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT16
-        )
-
-        provider = ntf.ServiceProvider(
-            value=128,
-            value_type=saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT32
-        )
-
-        # Send the notification
-        for i in range(0, args.repeatSends):
-            ntf.send_security_alarm_notification(vendor_id, major_id, 
minor_id, severity,
-                                                 detector, user, provider,
-                                                 
additional_text=additional_text,
-                                                 
notification_object=notification_object,
-                                                 
notifying_object=notifying_object,
-                                                 event_time=event_time)
-
-            time.sleep(args.burstTimeout)
-
-    elif ntf_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
-
-        for i in range(0, args.repeatSends):
-            ntf.send_alarm_notification(vendor_id, major_id, minor_id, 
severity,
-                                        additional_text=additional_text,
-                                        
notification_object=notification_object,
-                                        notifying_object=notifying_object,
-                                        event_time=event_time)
-
-            time.sleep(args.burstTimeout)
-
-    elif ntf_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE:
-
-        # Fill in sample attribute changes
-
-        attr0 = ntf.AttributeChange()
-
-        attr0.attribute_id = 0
-        attr0.attribute_type = saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32
-        attr0.old_attribute_present = saAis.eSaBoolT.SA_FALSE
-        attr0.new_attribute_value = 1
-
-        attr1 = ntf.AttributeChange()
-
-        attr1.attribute_id = 1
-        attr1.attribute_type = saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32
-        attr1.old_attribute_present = saAis.eSaBoolT.SA_TRUE
-        attr1.old_attribute_value = 8
-        attr1.new_attribute_value = -4
-
-        changed_attributes = [attr0, attr1]
-
-        # Send the notification
-        for i in range(0, args.repeatSends):
-            ntf.send_attribute_change_notification(vendor_id, major_id, 
minor_id,
-                                                   
additional_text=additional_text,
-                                                   
notification_object=notification_object,
-                                                   
notifying_object=notifying_object,
-                                                   event_time=event_time,
-                                                   
changed_attributes=changed_attributes)
-
-            time.sleep(args.burstTimeout)
-
-    elif ntf_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE:
-
-        # Fill in sample state changes
-        changes = []
-
-        change0 = ntf.StateChange()
-
-        change0.state_id = 1
-        change0.old_state_present = False
-        change0.new_state = 0
-
-        change1 = ntf.StateChange()
-
-        change1.state_id = 2
-        change1.old_state_present = False
-        change1.new_state = 5
-
-        change2 = ntf.StateChange()
-
-        change2.state_id = 1
-        change2.old_state_present = True
-        change2.old_state = 0
-        change2.new_state = 1
-
-        change3 = ntf.StateChange()
-
-        change3.state_id = 2
-        change3.old_state_present = True
-        change3.old_state = 5
-        change3.new_state = 10
-
-        changes.append(change0)
-        changes.append(change1)
-        changes.append(change2)
-        changes.append(change3)
-
-        # Send the notification
-        for i in range(0, args.repeatSends):
-            ntf.send_state_change_notification(vendor_id, major_id, minor_id,
-                                               additional_text=additional_text,
-                                               
notification_object=notification_object,
-                                               
notifying_object=notifying_object,
-                                               event_time=event_time,
-                                               state_changes=changes)
-
-            time.sleep(args.burstTimeout)
+from pyosaf import saNtf
+from pyosaf.saAis import eSaAisErrorT
+from pyosaf.utils.ntf import agent as ntf
+from pyosaf.utils.ntf.producer import NtfProducer
+
+NOTIFICATIONS_TYPE_MASK = saNtf.saNtf.SA_NTF_NOTIFICATIONS_TYPE_MASK
+
+
+class InputValidation(object):
+    """ This class consists of methods to validate different types of input
+    argument for the ntfsend tool
+    """
+    @staticmethod
+    def validate_ntf_class_id(arg):
+        """ Validate the input NtfClassId with format
+        'VENDOR_ID.MAJOR_ID.MINOR_ID'
+
+        Args:
+            arg (str): The NtfClassId to validate
+
+        Returns:
+            SaNtfClassIdT: The validated NtfClassId
+        """
+        if arg.count('.') != 2:
+            msg = "%r is not in correct format " \
+                  "'VENDOR_ID.MAJOR_ID.MINOR_ID'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split('.')[0] or not arg.split('.')[1] \
+                or not arg.split('.')[2]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        # Validate element type
+        try:
+            vendor_id = int(arg.split('.')[0])
+            major_id = int(arg.split('.')[1])
+            minor_id = int(arg.split('.')[2])
+        except ValueError:
+            msg = "%r must consist of all integers" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        return saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+
+    @staticmethod
+    def validate_id_type_value(arg):
+        """ Validate the input argument with format 'ID,TYPE,VALUE'
+
+        Args:
+            arg (str): The argument to validate
+
+        Returns:
+            tuple: The validated argument as (ID, TYPE, VALUE)
+        """
+        if arg.count(',') != 2:
+            msg = "%r is not in correct format 'ID,TYPE,VALUE'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1] \
+                or not arg.split(',')[2]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        # Validate element type
+        try:
+            _id = int(arg.split(',')[0])
+        except ValueError:
+            msg = "%r - ID must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        try:
+            _type = int(arg.split(',')[1])
+            if _type not in saNtf.eSaNtfValueTypeT.reverse_lookup:
+                msg = "%r - TYPE is not a valid SaNtfValueTypeT type" % arg
+                raise argparse.ArgumentTypeError(msg)
+        except ValueError:
+            msg = "%r - TYPE must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        _value = arg.split(',')[2]
+
+        return _id, _type, _value
+
+    @staticmethod
+    def validate_type_value(arg):
+        """ Validate the input argument with format 'TYPE,VALUE'
+
+        Args:
+            arg (str): The argument to validate
+
+        Returns:
+            tuple: The validated argument as (TYPE, VALUE)
+        """
+        if arg.count(',') != 1:
+            msg = "%r is not in correct format 'TYPE,VALUE'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        # Validate TYPE element
+        try:
+            _type = int(arg.split(',')[0])
+            if _type not in saNtf.eSaNtfValueTypeT.reverse_lookup:
+                msg = "%r - TYPE is not a valid SaNtfValueTypeT type" % arg
+                raise argparse.ArgumentTypeError(msg)
+        except ValueError:
+            msg = "%r - TYPE must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        _value = arg.split(',')[1]
+
+        return _type, _value
+
+    @staticmethod
+    def validate_changed_attribute(arg):
+        """ Validate the input changedAttributes argument with format
+        'ID,TYPE,OLD_PRESENT,OLD_VAL,NEW_VAL'
+
+        Args:
+            arg (str): The changedAttributes argument to validate
+
+        Returns:
+            tuple: The validated changedAttributes argument as
+                (ID, TYPE, OLD_PRESENT, OLD_VALUE, NEW_VALUE)
+        """
+        if arg.count(',') != 4:
+            msg = "%r is not in correct format " \
+                  "'ID,TYPE,OLD_PRESENT,OLD_VAL,NEW_VAL'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1] \
+                or not arg.split(',')[2] or not arg.split(',')[4]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        # Validate element type
+        try:
+            _id = int(arg.split(',')[0])
+        except ValueError:
+            msg = "%r - ID must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        try:
+            _type = int(arg.split(',')[1])
+            if _type not in saNtf.eSaNtfValueTypeT.reverse_lookup:
+                msg = "%r - TYPE is not a valid SaNtfValueTypeT type" % arg
+                raise argparse.ArgumentTypeError(msg)
+        except ValueError:
+            msg = "%r - TYPE must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        if arg.split(',')[2] not in ['True', 'False']:
+            msg = "%r - OLD_PRESENT must be either 'True' or 'False'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        _old_present = True if arg.split(',')[2] == 'True' else False
+
+        _old_value = arg.split(',')[3]
+        if _old_present and not _old_value:
+            msg = "%r - OLD_VAL is missing for OLD_PRESENT=True" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        _new_value = arg.split(',')[4]
+
+        return _id, _type, _old_present, _old_value, _new_value
+
+    @staticmethod
+    def validate_changed_state(arg):
+        """ Validate the input changedStates argument with format
+        'ID,OLD_PRESENT,OLD_STATE,NEW_STATE'
+
+        Args:
+            arg (str): The changedStates argument to validate
+
+        Returns:
+            tuple: The validated changedStates argument as
+                (ID, OLD_PRESENT, OLD_STATE, NEW_STATE)
+        """
+        if arg.count(',') != 3:
+            msg = "%r is not in correct format " \
+                  "'ID,OLD_PRESENT,OLD_STATE,NEW_STATE'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1] \
+                or not arg.split(',')[3]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        # Validate element type
+        try:
+            _id = int(arg.split(',')[0])
+        except ValueError:
+            msg = "%r - ID must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        if arg.split(',')[1] not in ['True', 'False']:
+            msg = "%r - OLD_PRESENT must be either 'True' or 'False'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        _old_present = True if arg.split(',')[1] == 'True' else False
+
+        if _old_present:
+            try:
+                if not arg.split(',')[2]:
+                    msg = "%r - OLD_STATE is missing for " \
+                          "OLD_PRESENT=True" % arg
+                    raise argparse.ArgumentTypeError(msg)
+                _old_state = int(arg.split(',')[2])
+            except ValueError:
+                msg = "%r - OLD_STATE must be an integer" % arg
+                raise argparse.ArgumentTypeError(msg)
+        else:
+            _old_state = arg.split(',')[2]
+
+        try:
+            _new_state = int(arg.split(',')[3])
+        except ValueError:
+            msg = "%r - NEW_STATE must be an integer" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        return _id, _old_present, _old_state, _new_state
+
+    @staticmethod
+    def validate_specific_problem(arg):
+        """ Validate the input specificProblems argument with format
+        'ID,TYPE,VALUE[,VENDOR_ID.MAJOR_ID.MINOR_ID]'
+
+        Args:
+            arg (str): The specificProblems argument to validate
+
+        Returns:
+            tuple: The validated specificProblems argument as
+                (ID, TYPE, VALUE, SaNtfClassIdT) if problemClassId is provided,
+                or (ID, TYPE, VALUE, None) if problemClassId is not provided
+        """
+        if arg.count(',') < 2 or arg.count(',') > 3:
+            msg = "%r is not in correct format " \
+                  "'ID,TYPE,VALUE[,VENDOR_ID.MAJOR_ID.MINOR_ID]'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1] \
+                or not arg.split(',')[2]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        arg_list = arg.split(',')
+        # Validate the required ID,TYPE,VALUE part
+        required_part = ','.join(arg_list[:3])
+        _id, _type, _value = \
+            InputValidation.validate_id_type_value(required_part)
+
+        # Validate the problemClassId part, if provided
+        if len(arg_list) == 4 and arg_list[3]:
+            _class_id = InputValidation.validate_ntf_class_id(arg_list[3])
+        else:
+            _class_id = None
+
+        return _id, _type, _value, _class_id
+
+    @staticmethod
+    def validate_threshold_information(arg):
+        """ Validate the input thresholdInformation argument with format
+        'ID,TYPE,THRES_VALUE,HYST,OBSRV_VALUE[,ARM_TIME]'
+
+        Args:
+            arg (str): The thresholdInformation argument to validate
+
+        Returns:
+            tuple: The validated thresholdInformation argument as
+                (ID, TYPE, THRES_VALUE, HYST, OBSRV_VALUE, ARM_TIME) if
+                armTime is provided, or
+                (ID, TYPE, THRES_VALUE, HYST, OBSRV_VALUE, CURRENT_TIME) if
+                armTime is not provided
+        """
+        if arg.count(',') < 4 or arg.count(',') > 5:
+            msg = "%r is not in correct format " \
+                  "'ID,TYPE,THRES_VALUE,HYST,OBSRV_VALUE[,ARM_TIME]'" % arg
+            raise argparse.ArgumentTypeError(msg)
+        if not arg.split(',')[0] or not arg.split(',')[1] \
+                or not arg.split(',')[2] or not arg.split(',')[3] \
+                or not arg.split(',')[4]:
+            msg = "%r does not have enough required values" % arg
+            raise argparse.ArgumentTypeError(msg)
+
+        arg_list = arg.split(',')
+        # Validate the required ID,TYPE,VALUE part
+        required_part = ','.join(arg_list[:3])
+        _id, _type, _thres_value = \
+            InputValidation.validate_id_type_value(required_part)
+
+        _thres_hyst = arg_list[3]
+        _obsrv_value = arg_list[4]
+
+        if len(arg_list) == 6 and arg_list[5]:
+            _arm_time = arg_list[5]
+        else:
+            _arm_time = int(time.time() * 10**9)
+
+        return _id, _type, _thres_value, _thres_hyst, _obsrv_value, _arm_time
+
+
+def fill_notification_header():
+    """ Fill the notification header with user-provided or default values """
+    producer.set_event_type(event_type)
+    if args.notificationObject is not None:
+        producer.set_notification_object(args.notificationObject)
+    if args.notifyingObject is not None:
+        producer.set_notifying_object(args.notifyingObject)
+    if args.notificationClassId is not None:
+        producer.set_class_id(args.notificationClassId)
+    if args.eventTime is not None:
+        producer.set_event_time(args.eventTime)
+    if args.additionalText is not None:
+        producer.set_additional_text(args.additionalText)
+    if additional_info:
+        producer.set_additional_info(additional_info)
+
+
+def send_notification(notif_type_desc):
+    """ Send notification(s) of a specific type
+
+    Args:
+        notif_type_desc (str): Type of notification to send
+    """
+    ntf_send = {'Object Create/Delete Notification':
+                    producer.send_object_create_delete_notification,
+                'Attribute Change Notification':
+                    producer.send_attribute_change_notification,
+                'State Change Notification':
+                    producer.send_state_change_notification,
+                'Alarm Notification':
+                    producer.send_alarm_notification,
+                'Security Alarm Notification':
+                    producer.send_security_alarm_notification}
+
+    repeat_count = 0
+    if args.burstTimeout:
+        print("*** Send burst of %d notifications every %4.3f seconds ***\n" %
+              (args.repeatSends, (args.burstTimeout / 1000000.0)))
+    while True:
+        try:
+            # Send the notification for 'repeatSends' times
+            for _ in range(args.repeatSends):
+                send_rc = ntf_send[notif_type_desc]()
+                if send_rc != eSaAisErrorT.SA_AIS_OK:
+                    print("ERROR: ntfsend FAILED, rc = %s\n" %
+                          eSaAisErrorT.whatis(send_rc))
+                    sys.exit(1)
+                repeat_count += 1
+                print("Send %s successfully! send_count=%d" %
+                      (notif_type_desc, repeat_count))
+            print()
+            # Keep sending the same notification forever if burstTimeout is set
+            if not args.burstTimeout:
+                break
+            time.sleep(args.burstTimeout / 1000000.0)
+        except KeyboardInterrupt:
+            sys.exit(0)
+
+
+# Define command line arguments for ntfsend
+parser = argparse.ArgumentParser(
+    description='A SAF NTF client used to send notifications.\nWithout any '
+                'argument, a default alarm notification will be sent.',
+    formatter_class=argparse.RawTextHelpFormatter)
+
+parser.add_argument('--notificationType', '-T', metavar='0x1000...0x5000',
+                    default='0x4000', choices=['0x1000', '0x2000', '0x3000',
+                                               '0x4000', '0x5000'],
+                    help='hexadecimal value of SaNtfNotificationTypeT\n'
+                         '(obj_create_delete=0x1000,attr_change,state_change,'
+                         'alarm,sec_alarm=0x5000)')
+parser.add_argument('--eventType', '-e', metavar='4096...20485',
+                    type=int, default=16384,
+                    choices=sorted(saNtf.eSaNtfEventTypeT.reverse_lookup)[:22],
+                    help='numeric value of SaNtfEventTypeT\n'
+                         '(SA_NTF_OBJECT_NOTIFICATIONS_START...'
+                         'SA_NTF_TIME_VIOLATION)')
+parser.add_argument('--eventTime', '-E', metavar='TIME', type=int,
+                    help='numeric value of SaTimeT in nanosecond')
+parser.add_argument('--notificationClassId', '-c',
+                    type=InputValidation.validate_ntf_class_id,
+                    metavar='VENDOR_ID.MAJOR_ID.MINOR_ID',
+                    help='notification class identifier\n'
+                         'VENDOR_ID: SaUint32T integer value\n'
+                         'MAJOR_ID: SaUint16T integer value\n'
+                         'MINOR_ID: SaUint16T integer value')
+parser.add_argument('--notificationObject', '-n', metavar='NOTIFICATION_OBJ',
+                    type=str, help='notification object (string value)')
+parser.add_argument('--notifyingObject', '-N', metavar='NOTIFYING_OBJ',
+                    type=str, help='notifying object (string value)')
+parser.add_argument('--additionalText', '-a', metavar='TEXT', type=str,
+                    help='additional text (string value)')
+parser.add_argument('--additionalInfo', '-i', metavar='ID,TYPE,VALUE',
+                    type=InputValidation.validate_id_type_value, nargs='+',
+                    help='additional information\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: additional info value')
+parser.add_argument('--sourceIndicator', '-I', metavar='1...3',
+                    type=int, choices=range(1, 4),
+                    help='numeric value of SaNtfSourceIndicatorT\n'
+                         '(SA_NTF_OBJECT_OPERATION...'
+                         'SA_NTF_UNKNOWN_OPERATION)')
+parser.add_argument('--objectAttributes', metavar='ID,TYPE,VALUE',
+                    type=InputValidation.validate_id_type_value, nargs='+',
+                    help='object create/delete attributes\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: object attribute value')
+parser.add_argument('--changedAttributes',
+                    metavar='ID,TYPE,OLD_PRESENT,OLD_VALUE,NEW_VALUE',
+                    type=InputValidation.validate_changed_attribute, nargs='+',
+                    help='changed attributes of AttributeChange notification\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'OLD_PRESENT: boolean value (True or False)\n'
+                         'OLD_VALUE: old attribute value '
+                         '(ignored if OLD_PRESENT=False)\n'
+                         'NEW_VALUE: new attribute value')
+parser.add_argument('--changedStates',
+                    metavar='ID,OLD_PRESENT,OLD_STATE,NEW_STATE',
+                    type=InputValidation.validate_changed_state, nargs='+',
+                    help='changed states of StateChange notification\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'OLD_PRESENT: boolean value (True or False)\n'
+                         'OLD_STATE: SaUint16T integer value '
+                         '(ignored if OLD_PRESENT=False)\n'
+                         'NEW_STATE: SaUint16T integer value')
+parser.add_argument('--probableCause', '-p', metavar='0...74',
+                    type=int, choices=range(75),
+                    help='numeric value of SaNtfProbableCauseT\n'
+                         '(SA_NTF_ADAPTER_ERROR...SA_NTF_UNSPECIFIED_REASON)')
+parser.add_argument('--perceivedSeverity', '-s', metavar='0...5',
+                    type=int, choices=range(6),
+                    help='numeric value of SaNtfSeverityT\n'
+                         '(SA_NTF_SEVERITY_CLEARED...'
+                         'SA_NTF_SEVERITY_CRITICAL)')
+parser.add_argument('--trend', '-t', metavar='0...2', type=int,
+                    default=1, choices=range(3),
+                    help='numeric value of SaNtfSeverityTrendT\n'
+                         '(SA_NTF_TREND_MORE_SEVERE...'
+                         'SA_NTF_TREND_LESS_SEVERE)')
+parser.add_argument('--monitoredAttributes', metavar='ID,TYPE,VALUE',
+                    type=InputValidation.validate_id_type_value, nargs='+',
+                    help='monitored attributes of alarm notification\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: monitored attribute value')
+parser.add_argument('--specificProblems',
+                    metavar='ID,TYPE,VALUE[,VENDOR_ID.MAJOR_ID.MINOR_ID]',
+                    type=InputValidation.validate_specific_problem, nargs='+',
+                    help='specific problems of alarm notification '
+                         '(the problemClassId is optional)\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: specific problem value\n'
+                         'VENDOR_ID: SaUint32T integer value\n'
+                         'MAJOR_ID: SaUint16T integer value\n'
+                         'MINOR_ID: SaUint16T integer value')
+parser.add_argument('--thresholdInformation',
+                    metavar='ID,TYPE,THRES_VALUE,HYST,OBSRV_VALUE[,ARM_TIME]',
+                    type=InputValidation.validate_threshold_information,
+                    help='threshold information of alarm notification '
+                         '(the armTime is optional)\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'THRES_VALUE: threshold value\n'
+                         'HYST: threshold hysteresis value\n'
+                         'OBSRV_VALUE: observed value\n'
+                         'ARM_TIME: arm time value in nanosecond')
+parser.add_argument('--proposedRepairActions', metavar='ID,TYPE,VALUE',
+                    type=InputValidation.validate_id_type_value, nargs='+',
+                    help='proposed repair actions of alarm notification\n'
+                         'ID: SaNtfElementIdT integer value\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: proposed repair action value')
+parser.add_argument('--severity', '-S', metavar='1...5',
+                    type=int, choices=range(1, 6),
+                    help='numeric value of SaNtfSeverityT for security alarm\n'
+                         '(SA_NTF_SEVERITY_INDETERMINATE...'
+                         'SA_NTF_SEVERITY_CRITICAL)')
+parser.add_argument('--securityAlarmDetector', metavar='TYPE,VALUE',
+                    type=InputValidation.validate_type_value,
+                    help='security alarm detector of security alarm '
+                         'notification\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: security alarm detector value')
+parser.add_argument('--serviceUser', metavar='TYPE,VALUE',
+                    type=InputValidation.validate_type_value,
+                    help='service user of security alarm notification\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: service user value')
+parser.add_argument('--serviceProvider', metavar='TYPE,VALUE',
+                    type=InputValidation.validate_type_value,
+                    help='service provider of security alarm notification\n'
+                         'TYPE: numeric value of SaNtfValueTypeT\n'
+                         'VALUE: service provider value')
+parser.add_argument('--repeatSends', '-r', metavar='NUM', default=1, type=int,
+                    help='send the same notification NUM times')
+parser.add_argument('--burstTimeout', '-b', metavar='TIME', default=0,
+                    type=int, help='send burst of NUM repeatSends (default=1) '
+                                   'and sleep TIME (usec)\n'
+                                   'between each burst, will continue forever')
+
+# Parse command line arguments for user input, if any
+# Default values will be used otherwise
+args = parser.parse_args()
+
+notification_type = int(args.notificationType, 0)
+event_type = args.eventType
+if (event_type & NOTIFICATIONS_TYPE_MASK) != notification_type:
+    print("ERROR: Either eventType or notificationType is missing, or they "
+          "do not correspond with each other\n")
+    sys.exit(0)
+
+additional_info = []
+if args.additionalInfo is not None:
+    for add_info in args.additionalInfo:
+        info_id = add_info[0]
+        info_type = add_info[1]
+        info_value = add_info[2]
+        additional_info.append(
+            ntf.AdditionalInfo(info_id, info_type, info_value))
+
+# Initialize an NTF producer
+producer = NtfProducer()
+init_rc = producer.init()
+if init_rc != eSaAisErrorT.SA_AIS_OK:
+    print("ERROR: saNtfInitialize FAILED, rc = %s\n" %
+          eSaAisErrorT.whatis(init_rc))
+    sys.exit(1)
+
+# Fill the notification header with user-provided or default values
+fill_notification_header()
+
+if notification_type \
+        == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE:
+    if args.sourceIndicator is not None:
+        producer.set_source_indicator(args.sourceIndicator)
+    if args.objectAttributes is not None:
+        obj_attributes = []
+        for obj_attr in args.objectAttributes:
+            attr_id = obj_attr[0]
+            attr_type = obj_attr[1]
+            attr_value = obj_attr[2]
+            obj_attributes.append(
+                ntf.Attribute(attr_id, attr_type, attr_value))
+        producer.set_object_attributes(obj_attributes)
+    # Send object create/delete notification
+    send_notification("Object Create/Delete Notification")
+
+elif notification_type \
+        == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE:
+    if args.sourceIndicator is not None:
+        producer.set_source_indicator(args.sourceIndicator)
+    if args.changedAttributes is not None:
+        changed_attributes = []
+        for ch_attr in args.changedAttributes:
+            ch_attr_id = ch_attr[0]
+            ch_attr_type = ch_attr[1]
+            old_attr_present = ch_attr[2]
+            old_attr_value = ch_attr[3]
+            new_attr_value = ch_attr[4]
+            changed_attributes.append(
+                ntf.AttributeChange(ch_attr_id, ch_attr_type, new_attr_value,
+                                    old_attr_present, old_attr_value))
+        producer.set_changed_attributes(changed_attributes)
+    # Send attribute change notification
+    send_notification("Attribute Change Notification")
+
+elif notification_type \
+        == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE:
+    if args.sourceIndicator is not None:
+        producer.set_source_indicator(args.sourceIndicator)
+    if args.changedStates is not None:
+        state_changes = []
+        for ch_state in args.changedStates:
+            state_id = ch_state[0]
+            old_state_present = ch_state[1]
+            old_state = ch_state[2]
+            new_state = ch_state[3]
+            state_changes.append(ntf.StateChange(state_id, new_state,
+                                                 old_state_present, old_state))
+        producer.set_state_changes(state_changes)
+    # Send state change notification
+    send_notification("State Change Notification")
+
+elif notification_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
+    if args.probableCause is not None:
+        producer.set_probable_cause(args.probableCause)
+    if args.perceivedSeverity is not None:
+        producer.set_perceived_severity(args.perceivedSeverity)
+    if args.trend is not None:
+        producer.set_trend(args.trend)
+    if args.monitoredAttributes is not None:
+        monitored_attrs = []
+        for mon_attr in args.monitoredAttributes:
+            mon_attr_id = mon_attr[0]
+            mon_attr_type = mon_attr[1]
+            mon_attr_value = mon_attr[2]
+            monitored_attrs.append(
+                ntf.Attribute(mon_attr_id, mon_attr_type, mon_attr_value))
+        producer.set_monitored_attributes(monitored_attrs)
+    if args.specificProblems is not None:
+        specific_problems = []
+        for problem in args.specificProblems:
+            prob_id = problem[0]
+            prob_type = problem[1]
+            prob_value = problem[2]
+            prob_class_id = problem[3]
+            specific_problems.append(
+                ntf.SpecificProblem(prob_id, prob_class_id,
+                                    prob_type, prob_value))
+        producer.set_specific_problems(specific_problems)
+    if args.thresholdInformation is not None:
+        threshold_id = args.thresholdInformation[0]
+        threshold_type = args.thresholdInformation[1]
+        threshold_value = args.thresholdInformation[2]
+        threshold_hysteresis = args.thresholdInformation[3]
+        observed_value = args.thresholdInformation[4]
+        arm_time = args.thresholdInformation[5]
+        threshold_info = \
+            ntf.ThresholdInformation(threshold_id, threshold_type,
+                                     threshold_value, threshold_hysteresis,
+                                     observed_value, arm_time)
+        producer.set_threshold_information(threshold_info)
+    if args.proposedRepairActions is not None:
+        proposed_repair_actions = []
+        for repair_action in args.proposedRepairActions:
+            action_id = repair_action[0]
+            action_type = repair_action[1]
+            action_value = repair_action[2]
+            proposed_repair_actions.append(
+                ntf.ProposedRepairAction(action_id, action_type, action_value))
+        producer.set_proposed_repair_actions(proposed_repair_actions)
+    # Send alarm notification
+    send_notification("Alarm Notification")
+
+elif notification_type \
+        == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM:
+    if args.probableCause is not None:
+        producer.set_probable_cause(args.probableCause)
+    if args.severity is not None:
+        producer.set_severity(args.severity)
+    if args.securityAlarmDetector is not None:
+        sec_alarm_detector = \
+            ntf.SecurityAlarmDetector(args.securityAlarmDetector[1],
+                                      args.securityAlarmDetector[0])
+        producer.set_security_alarm_detector(sec_alarm_detector)
+    if args.serviceUser is not None:
+        srv_user = ntf.ServiceUser(args.serviceUser[1], args.serviceUser[0])
+        producer.set_service_user(srv_user)
+    if args.serviceProvider is not None:
+        srv_provider = ntf.ServiceProvider(args.serviceProvider[1],
+                                           args.serviceProvider[0])
+        producer.set_service_provider(srv_provider)
+    # Send security alarm notification
+    send_notification("Security Alarm Notification")
diff --git a/python/samples/ntfsubscribe b/python/samples/ntfsubscribe
old mode 100644
new mode 100755
index b885815..dc78825
--- a/python/samples/ntfsubscribe
+++ b/python/samples/ntfsubscribe
@@ -1,307 +1,522 @@
 #! /usr/bin/env python
-
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+# pylint: disable=unused-argument
+"""
+ntfsubscribe tool used to subscribe for incoming notifications from NTF
+
+Run ntfsubscribe --help/-h for more detail on usage
+"""
+from __future__ import print_function
+import sys
 import select
-import ctypes
-import datetime
 import argparse
-
-from pyosaf import saNtf, saAis
-from pyosaf.utils import ntf
-
-def SaNameT_to_string(name):
-    ''' Converts an instance of SaNameT to a Python string'''
-
-    return ctypes.create_string_buffer(name.value, name.length).value
-
-def p_char_to_string(p_char, length):
-    ''' Converts a char pointer with a missing NULL pointer to a string with 
the
-        given length'''
-
-    return ctypes.create_string_buffer(p_char, length).value
-
-def SaTimeT_to_date_string(sa_time):
-    ''' Returns a string representation for the given SaTimeT instance'''
-
-    milli_seconds = sa_time / 1000000
-
-    return datetime.datetime.fromtimestamp(milli_seconds/1000).isoformat()
-
-def print_notification_header(header):
-    ''' Prints the given notification header'''
-
-    class_id = header.notificationClassId.contents
-
-    print '''eventType = %s
-notificationObject = "%s"
-notifyingObject = "%s"
-notificationClassId = %d.%d.%d (0x0)
-additionalText = "%s"''' % (
-    saNtf.eSaNtfEventTypeT.whatis(header.eventType.contents.value),
-    SaNameT_to_string(header.notificationObject.contents),
-    SaNameT_to_string(header.notifyingObject.contents),
-    class_id.vendorId,
-    class_id.majorId,
-    class_id.minorId,
-    header.additionalText[0:header.lengthAdditionalText]
-)
-
-
-def print_alarm_notification(notification):
-    ''' Prints the given alarm notification'''
-
-    header = notification.notificationHeader
-
-    print '===  %s - Alarm  ===' % 
SaTimeT_to_date_string(header.eventTime.contents.value)
-
-    print_notification_header(header)
-
-    print 'probableCause = %s' % \
-       
saNtf.eSaNtfProbableCauseT.whatis(notification.probableCause.contents.value)
-    print 'perceivedSeverity = %s' % \
-       
saNtf.eSaNtfSeverityT.whatis(notification.perceivedSeverity.contents.value)
+from datetime import datetime, tzinfo, timedelta
+
+from pyosaf import saNtf
+from pyosaf.saAis import eSaAisErrorT, eSaDispatchFlagsT
+from pyosaf.utils.ntf import agent as ntf
+from pyosaf.utils.ntf.subscriber import NtfSubscriber
+
+
+class UTCOffsetTZ(tzinfo):
+    """ Time zone represented as UTC hour-offset """
+    def __init__(self, hour_offset):
+        """ UTCOffsetTZ constructor """
+        self._hour_offset = hour_offset
+        super(UTCOffsetTZ, self).__init__()
+
+    def utcoffset(self, dt):
+        """ Return offset of local time from UTC, in minutes east of UTC """
+        return timedelta(hours=self._hour_offset)
+
+    def dst(self, dt):
+        """ Return the daylight saving time (DST) adjustment, in minutes east
+        of UTC, or None if DST information isn't known
+        """
+        return timedelta(0)
+
+    def tzname(self, dt):
+        """ Return the time zone name represented in UTC hour-offset format as
+        'UTC+/-offset'
+        """
+        if self._hour_offset > 0:
+            tzname = "UTC+%02d:00" % self._hour_offset
+        elif self._hour_offset < 0:
+            tzname = "UTC-%02d:00" % self._hour_offset
+        else:  # hour_offset = 0
+            tzname = "UTC"
+
+        return tzname
+
+
+def satime_to_readable_datetime(sa_time, format_str=None):
+    """ Convert the given SaTimeT time value to readable datetime string
+    representation in ISO 8601 format
+
+    Args:
+        sa_time (SaTimeT): SaTimeT time value
+        format_str (str): Format string for datetime output representation
+
+    Returns:
+        str: Datetime representation in format specified by format_str, or in
+             ISO 8601 format 'YYYY-MM-DDTHH:MM:SS' if format_str not provided
+    """
+    time_sec = sa_time / 10**9  # Convert the time from nsec to sec 1000000000
+
+    # Calculate the UTC offset for time zone information
+    naive_dt = datetime.fromtimestamp(time_sec)
+    utc_offset = (naive_dt - datetime.utcfromtimestamp(time_sec))
+    hour_offset = utc_offset.total_seconds() // 3600
+    utc_offset_tz = UTCOffsetTZ(hour_offset)
+
+    aware_dt = naive_dt.replace(tzinfo=utc_offset_tz)
+
+    if format_str is not None:
+        return aware_dt.strftime(format_str)
+
+    return aware_dt.isoformat(' ')
+
+
+def _validate_ntf_class_id(arg):
+    """ Validate the NtfClassId filter argument with format
+    'VENDOR_ID.MAJOR_ID.MINOR_ID'
+
+    Args:
+        arg (str): The NtfClassId to validate
+
+    Returns:
+        SaNtfClassIdT: The validated NtfClassId
+    """
+    if arg.count('.') != 2:
+        msg = "%r is not in correct format " \
+              "'VENDOR_ID.MAJOR_ID.MINOR_ID'" % arg
+        raise argparse.ArgumentTypeError(msg)
+    if not arg.split('.')[0] or not arg.split('.')[1] \
+            or not arg.split('.')[2]:
+        msg = "%r does not have enough required values" % arg
+        raise argparse.ArgumentTypeError(msg)
+
+    # Validate element type
+    try:
+        vendor_id = int(arg.split('.')[0])
+        major_id = int(arg.split('.')[1])
+        minor_id = int(arg.split('.')[2])
+    except ValueError:
+        msg = "%r must consist of all integers" % arg
+        raise argparse.ArgumentTypeError(msg)
+
+    return saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+
+
+def print_notification_header(notif_info):
+    """ Print information of the header in the received notification
+
+    Args:
+        notif_info (NotificationInfo): NotificationInfo structure containing
+            information of the notification header
+    """
+    dt_format_str = "%c %Z"
+    print ('notificationId = %d\n'
+           'eventType = %s\n'
+           'notificationObject = "%s"\n'
+           'notifyingObject = "%s"\n'
+           'notificationClassId = %d.%d.%d\n'
+           'eventTime = %d (%s)\n'
+           'additionalText = "%s"' %
+           (notif_info.notification_id,
+            saNtf.eSaNtfEventTypeT.whatis(notif_info.event_type),
+            notif_info.notification_object,
+            notif_info.notifying_object,
+            notif_info.ntf_class_id.vendorId,
+            notif_info.ntf_class_id.majorId,
+            notif_info.ntf_class_id.minorId,
+            notif_info.event_time,
+            satime_to_readable_datetime(notif_info.event_time, dt_format_str),
+            notif_info.additional_text))
+    if notif_info.additional_info:
+        print("numAdditionalInfo = %d" % len(notif_info.additional_info))
+        for add_info in notif_info.additional_info:
+            print ("- Info ID: %d\n"
+                   "  Info Type: %s (%d)\n"
+                   "  Info Value: %s" %
+                   (add_info.info_id,
+                    saNtf.eSaNtfValueTypeT.whatis(add_info.info_type),
+                    add_info.info_type,
+                    add_info.info_value))
+    print()
 
 
 def print_object_create_delete_notification(notification):
-    ''' Prints the given object create delete notification'''
-
-    header = notification.notificationHeader
-
-    print '===  %s - Object Create/Delete  ===' % 
SaTimeT_to_date_string(header.eventTime.contents.value)
-
-    print_notification_header(header)
-
-    print 'sourceIndicator = %s' % \
-       
saNtf.eSaNtfSourceIndicatorT.whatis(notification.sourceIndicator.contents.value)
-
-    print
-
-    print 'numAttributes: %d' % notification.numAttributes
-
-    for i in range(0, notification.numAttributes):
-
-        c_attribute = notification.objectAttributes[i]
-
-        print '- Attribute ID: %d -' % c_attribute.attributeId
-        print ' Attribute Type: (%d) %s' % (
-           c_attribute.attributeType,
-           saNtf.eSaNtfValueTypeT.whatis(c_attribute.attributeType)
-        )
-
-        print ' Attribute Value: %d' % \
-           saNtf.unmarshalSaNtfValue(saAis.BYREF(c_attribute.attributeValue),
-                                     c_attribute.attributeType)
+    """ Print information of the received object create/delete notification
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the received object create/delete notification
+    """
+    print("===  %s - Object Create/Delete  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("sourceIndicator = %s" %
+          saNtf.eSaNtfSourceIndicatorT.whatis(notification.source_indicator))
+    print("numAttributes = %d" % len(notification.object_attributes))
+    for obj_attr in notification.object_attributes:
+        print("- Attribute ID: %d\n"
+              "  Attribute Type: %s (%d)\n"
+              "  Attribute Value: %s" %
+              (obj_attr.attribute_id,
+               saNtf.eSaNtfValueTypeT.whatis(obj_attr.attribute_type),
+               obj_attr.attribute_type, obj_attr.attribute_value))
 
 
 def print_attribute_change_notification(notification):
-    ''' Prints the given attribute change notification'''
-
-    header = notification.notificationHeader
-
-    print '===  %s - Attribute Change  ===' % 
SaTimeT_to_date_string(header.eventTime.contents.value)
-
-    print_notification_header(header)
-
-    print 'sourceIndicator = %s' % \
-       
saNtf.eSaNtfSourceIndicatorT.whatis(notification.sourceIndicator.contents.value)
-
-    print
-
-    print 'numAttributes: %d' % notification.numAttributes
-
-    for i in range(0, notification.numAttributes):
-
-        c_attribute = notification.changedAttributes[i]
-
-        print '''- Attribute ID: %d -
- Attribute Type: (%d) %s''' % (
-    c_attribute.attributeId,
-    c_attribute.attributeType,
-    saNtf.eSaNtfValueTypeT.whatis(c_attribute.attributeType)
-)
-
-        if c_attribute.oldAttributePresent:
-            print ''' Old Attribute Present: Yes
- Old Attribute Value: %s''' % \
-    saNtf.unmarshalSaNtfValue(saAis.BYREF(c_attribute.oldAttributeValue),
-                              c_attribute.attributeType)
-        else:
-            print ' Old Attribute Present: No'
-
-        print ' Attribute Value: %s' % \
-           
saNtf.unmarshalSaNtfValue(saAis.BYREF(c_attribute.newAttributeValue),
-                                     c_attribute.attributeType)
+    """ Print information of the received attribute change notification
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the received attribute change notification
+    """
+    print("===  %s - Attribute Change  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("sourceIndicator = %s" %
+          saNtf.eSaNtfSourceIndicatorT.whatis(notification.source_indicator))
+    print("numAttributes = %d" % len(notification.changed_attributes))
+    for changed_attr in notification.changed_attributes:
+        print("- Attribute ID: %d\n"
+              "  Attribute Type: %s (%d)\n"
+              "  Old Attribute Present: %s\n"
+              "  New Attribute Value: %s" %
+              (changed_attr.attribute_id,
+               saNtf.eSaNtfValueTypeT.whatis(changed_attr.attribute_type),
+               changed_attr.attribute_type,
+               bool(changed_attr.old_attribute_present),
+               changed_attr.new_attribute_value))
+        if changed_attr.old_attribute_present:
+            print("  Old Attribute Value: %s" %
+                  changed_attr.old_attribute_value)
 
 
 def print_state_change_notification(notification):
-    ''' Prints the given state change notification'''
-
-    header = notification.notificationHeader
-
-    print '===  %s - State Change  ===' % 
SaTimeT_to_date_string(header.eventTime.contents.value)
-
-    print_notification_header(header)
-
-    print 'sourceIndicator = %s' % \
-       
saNtf.eSaNtfSourceIndicatorT.whatis(notification.sourceIndicator.contents.value)
-
-    print
-
-    i = 0
-    for c_state in notification.changedStates:
+    """ Print information of the received state change notification
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the received state change notification
+    """
+    print("===  %s - State Change  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("sourceIndicator = %s" %
+          saNtf.eSaNtfSourceIndicatorT.whatis(notification.source_indicator))
+    print("numStateChanges = %d" % len(notification.state_changes))
+    for changed_state in notification.state_changes:
+        print("- State ID: %d\n"
+              "  Old State Present: %s\n"
+              "  New State: %s" %
+              (changed_state.state_id, bool(changed_state.old_state_present),
+               changed_state.new_state))
+        if changed_state.old_state_present:
+            print("  Old State: %s" % changed_state.old_state)
 
-        if i == notification.numStateChanges:
-            break
 
-        i = i + 1
-
-        print '- State ID: %d -' % c_state.stateId
-
-        if c_state.oldStatePresent:
-            print ' Old State Present: Yes'
-            print ' Old State: %s' % c_state.oldState
-        else:
-            print ' Old State Present: No'
-
-        print ' New State: %s' % c_state.newState
+def print_alarm_notification(notification):
+    """ Print information of the received alarm notification
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the received alarm notification
+    """
+    print("===  %s - Alarm  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("probableCause = %s" %
+          saNtf.eSaNtfProbableCauseT.whatis(notification.probable_cause))
+    print("perceivedSeverity = %s" %
+          saNtf.eSaNtfSeverityT.whatis(notification.perceived_severity))
+
+    if args.verbose:
+        print("trend = %s" %
+              saNtf.eSaNtfSeverityTrendT.whatis(notification.trend))
+        print("thresholdInformation:\n"
+              "- Threshold ID: %d\n"
+              "  Threshold Value Type: %s (%d)\n"
+              "  Threshold Value: %s\n"
+              "  Threshold Hysteresis: %s\n"
+              "  Observed Value: %s\n"
+              "  Arm Time: %s" %
+              (notification.threshold_information.threshold_id,
+               saNtf.eSaNtfValueTypeT.whatis(
+                   notification.threshold_information.threshold_value_type),
+               notification.threshold_information.threshold_value_type,
+               notification.threshold_information.threshold_value,
+               notification.threshold_information.threshold_hysteresis,
+               notification.threshold_information.observed_value,
+               satime_to_readable_datetime(
+                   notification.threshold_information.arm_time)))
+        print("numSpecificProblems = %d" % len(notification.specific_problems))
+        for spec_problem in notification.specific_problems:
+            print("- Problem ID: %d\n"
+                  "  Problem Class ID: %d.%d.%d\n"
+                  "  Problem Type: %s (%d)\n"
+                  "  Problem Value: %s" %
+                  (spec_problem.problem_id,
+                   spec_problem.problem_class_id.vendorId,
+                   spec_problem.problem_class_id.majorId,
+                   spec_problem.problem_class_id.minorId,
+                   saNtf.eSaNtfValueTypeT.whatis(spec_problem.problem_type),
+                   spec_problem.problem_type, spec_problem.problem_value))
+        print("numMonitoredAttributes = %d" %
+              len(notification.monitored_attrs))
+        for monitored_attr in notification.monitored_attrs:
+            print("- Attribute ID: %d\n"
+                  "  Attribute Type: %s (%d)\n"
+                  "  Attribute Value: %s" %
+                  (monitored_attr.attribute_id,
+                   saNtf.eSaNtfValueTypeT.whatis(
+                       monitored_attr.attribute_type),
+                   monitored_attr.attribute_type,
+                   monitored_attr.attribute_value))
+        print("numProposedRepairActions = %d" %
+              len(notification.proposed_repair_actions))
+        for repair_action in notification.proposed_repair_actions:
+            print("- Action ID: %d\n"
+                  "  Action Value Type: %s (%d)\n"
+                  "  Action Value: %s" %
+                  (repair_action.action_id,
+                   saNtf.eSaNtfValueTypeT.whatis(
+                       repair_action.action_value_type),
+                   repair_action.action_value_type,
+                   repair_action.action_value))
 
 
 def print_security_alarm_notification(notification):
-    ''' Prints the given security alarm notification'''
-
-    header = notification.notificationHeader
-
-    print '===  %s - Security Alarm  ===' % 
SaTimeT_to_date_string(header.eventTime.contents.value)
-
-    print_notification_header(header)
-
-    print'''probableCause = %s
-severity = %s
-Security Alarm Detector Type: %d
-Security Alarm Detector Value: %d
-Service User Type: %d
-Service User Value: %d
-Service Provider Type: %d
-Service Provider Value: %d
-''' % (
-    
saNtf.eSaNtfProbableCauseT.whatis(notification.probableCause.contents.value),
-    saNtf.eSaNtfSeverityT.whatis(notification.severity.contents.value),
-    notification.securityAlarmDetector.contents.valueType,
-    
saNtf.unmarshalSaNtfValue(saAis.BYREF(notification.securityAlarmDetector.contents.value),
-                              
notification.securityAlarmDetector.contents.valueType),
-    notification.serviceUser.contents.valueType,
-    
saNtf.unmarshalSaNtfValue(saAis.BYREF(notification.serviceUser.contents.value),
-                              notification.serviceUser.contents.valueType),
-    notification.serviceProvider.contents.valueType,
-    
saNtf.unmarshalSaNtfValue(saAis.BYREF(notification.serviceProvider.contents.value),
-                              notification.serviceProvider.contents.valueType)
-)
-
-
-def notification_received(subscription_id, c_p_notification):
-    ''' Handle received notifications'''
-
-    notification_type = c_p_notification.contents.notificationType
-
-    print
-
-    if notification_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
-        notification = c_p_notification.contents.notification.alarmNotification
-
-        print_alarm_notification(notification)
-
-    elif notification_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE:
-        notification = 
c_p_notification.contents.notification.objectCreateDeleteNotification
-
-        print_object_create_delete_notification(notification)
-
-    elif notification_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE:
-        notification = 
c_p_notification.contents.notification.attributeChangeNotification
-
-        print_attribute_change_notification(notification)
-
-    elif notification_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE:
-        notification = 
c_p_notification.contents.notification.stateChangeNotification
-
-        print_state_change_notification(notification)
-
-    elif notification_type == 
saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM:
-        notification = 
c_p_notification.contents.notification.securityAlarmNotification
-
-        print_security_alarm_notification(notification)
-
-
-if __name__ == '__main__':
-
-    # Parse the parameters
-    parser = argparse.ArgumentParser(
-        description='ntfsubscribe is a SAF NTF client used to subscribe for 
all incoming notifications.')
-
-    parser.add_argument('--timeout', '-t', metavar='TIME',
-                        default=1,
-                        help='timeout (sec) waiting for notification')
-
-    parser.add_argument('--alarm', '-a',
-                        dest='alarm', action='store_true',
-                        help='subscribe for only alarm notifications')
-
-    parser.add_argument('--objectCreateDelete', '-o',
-                        dest='object_create_delete', action='store_true',
-                        help='subscribe for only objectCreateDelete 
notifications')
-
-    parser.add_argument('--attributeChange', '-c',
-                        dest='attribute_change', action='store_true',
-                        help='subscribe for only attributeChange 
notifications')
-
-    parser.add_argument('--stateChange', '-s',
-                        dest='state_change', action='store_true',
-                        help='subscribe for only stateChange notifications')
-
-    parser.add_argument('--securityAlarm', '-y',
-                        dest='security_alarm', action='store_true',
-                        help='subscribe for only securityAlarm notifications')
-
-    args = parser.parse_args()
-
-    print args.timeout
-
-    # Initialize the NTF library
-    ntf.initialize(notification_callback=notification_received)
-
-    # Subscribe for notifications
-    subscriptions = []
-
-    if args.object_create_delete:
-        
subscriptions.append(saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE)
-
-    if args.alarm:
-        subscriptions.append(saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM)
-
-    if args.attribute_change:
-        
subscriptions.append(saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE)
-
-    if args.state_change:
-        
subscriptions.append(saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE)
-
-    if args.security_alarm:
-        
subscriptions.append(saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM)
-
-    if subscriptions == []:
-        ntf.subscribe_for_notifications()
-    else:
-        ntf.subscribe_for_notifications(notification_types=subscriptions)
-
-    # Get selection object for the implementer
-    selection_object = ntf.SELECTION_OBJECT.value
-
-    # Wait for next OI event or one second timeout
-    inputs = [selection_object]
-    outputs = []
-
-    # Loop and wait for notifications
-    while True:
-
-        readable, writable, exceptional = \
-        select.select(inputs, outputs, inputs, args.timeout)
-
-        if selection_object in readable:
-            ntf.dispatch(saAis.eSaDispatchFlagsT.SA_DISPATCH_ALL)
+    """ Print information of the received security alarm notification
+
+    Args:
+        notification (NotificationInfo): NotificationInfo structure containing
+            information of the received security alarm notification
+    """
+    print("===  %s - Security Alarm  ===" %
+          satime_to_readable_datetime(notification.event_time))
+    print_notification_header(notification)
+    print("probableCause = %s\n"
+          "severity = %s\n"
+          "Security Alarm Detector Type: %s (%d)\n"
+          "Security Alarm Detector Value: %s\n"
+          "Service User Type: %s (%d)\n"
+          "Service User Value: %s\n"
+          "Service Provider Type: %s (%d)\n"
+          "Service Provider Value: %s" %
+          (saNtf.eSaNtfProbableCauseT.whatis(notification.probable_cause),
+           saNtf.eSaNtfSeverityT.whatis(notification.severity),
+           saNtf.eSaNtfValueTypeT.whatis(
+               notification.security_alarm_detector.value_type),
+           notification.security_alarm_detector.value_type,
+           notification.security_alarm_detector.value,
+           saNtf.eSaNtfValueTypeT.whatis(notification.service_user.value_type),
+           notification.service_user.value_type,
+           notification.service_user.value,
+           saNtf.eSaNtfValueTypeT.whatis(
+               notification.service_provider.value_type),
+           notification.service_provider.value_type,
+           notification.service_provider.value))
+
+
+def print_received_notification(subscription_id, notif_type, notif_info):
+    """ Test callback for subscribed notifications, which simply prints
+    information of every received notification
+
+    Args:
+        subscription_id (SaNtfSubscriptionIdT): The subscription id previously
+            provided by the subscriber when subscribing for this type of
+            notification
+        notif_type (SaNtfNotificationTypeT): Type of the received notification
+        notif_info (NotificationInfo): NotificationInfo structure containing
+            information of the received notification
+    """
+    print("\nSubscription ID: %d" % subscription_id)
+    if notif_type == \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE:
+        print_object_create_delete_notification(notif_info)
+    elif notif_type == \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE:
+        print_attribute_change_notification(notif_info)
+    elif notif_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE:
+        print_state_change_notification(notif_info)
+    elif notif_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
+        print_alarm_notification(notif_info)
+    elif notif_type == \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM:
+        print_security_alarm_notification(notif_info)
+    print()
+
+
+# Define command line arguments for ntfsubscribe
+parser = argparse.ArgumentParser(
+    description='A SAF NTF client used to subscribe for incoming '
+                'notifications matching optionally provided filter '
+                'argument(s).\nIf not specified, all types of notification '
+                'will be subscribed to by default.',
+    formatter_class=argparse.RawTextHelpFormatter)
+
+parser.add_argument('--subscriptionId', '-i', metavar='ID', type=int,
+                    default=1, help='id for the notification subscription')
+parser.add_argument('--timeout', '-t', metavar='TIME', type=int,
+                    help='timeout (sec) to wait for notification')
+parser.add_argument('--verbose', '-v', action='store_true',
+                    help='print all information from alarm notifications')
+parser.add_argument('--objectCreateDelete', '-o', action='store_true',
+                    help='subscribe for objectCreateDelete notifications only')
+parser.add_argument('--attributeChange', '-c', action='store_true',
+                    help='subscribe for attributeChange notifications only')
+parser.add_argument('--stateChange', '-s', action='store_true',
+                    help='subscribe for stateChange notifications only')
+parser.add_argument('--alarm', '-a', action='store_true',
+                    help='subscribe for alarm notifications only')
+parser.add_argument('--securityAlarm', '-y', action='store_true',
+                    help='subscribe for securityAlarm notifications only')
+parser.add_argument('--eventTypes', '-e', metavar='4096...20485',
+                    type=int, nargs='+',
+                    choices=sorted(saNtf.eSaNtfEventTypeT.reverse_lookup)[:22],
+                    help='numeric value of SaNtfEventTypeT\n'
+                         '(SA_NTF_OBJECT_NOTIFICATIONS_START...'
+                         'SA_NTF_TIME_VIOLATION)')
+parser.add_argument('--notificationClassIds', '-C',
+                    type=_validate_ntf_class_id,
+                    metavar='VENDOR_ID.MAJOR_ID.MINOR_ID', nargs='+',
+                    help='notification class identifier\n'
+                         'VENDOR_ID: SaUint32T integer value\n'
+                         'MAJOR_ID: SaUint16T integer value\n'
+                         'MINOR_ID: SaUint16T integer value')
+parser.add_argument('--notificationObjects', '-n', metavar='NOTIFICATION_OBJ',
+                    type=str, nargs='+',
+                    help='notification object (string value)')
+parser.add_argument('--notifyingObjects', '-N', metavar='NOTIFYING_OBJ',
+                    type=str, nargs='+',
+                    help='notifying object (string value)')
+parser.add_argument('--sourceIndicators', '-I', metavar='1...3',
+                    type=int, nargs='+', choices=range(1, 4),
+                    help='numeric value of SaNtfSourceIndicatorT\n'
+                         '(SA_NTF_OBJECT_OPERATION...'
+                         'SA_NTF_UNKNOWN_OPERATION)')
+parser.add_argument('--changedStateIds', metavar='STATE_ID',
+                    type=int, nargs='+',
+                    help='changed state id of StateChange notification')
+parser.add_argument('--probableCauses', '-p', metavar='0...74',
+                    type=int, nargs='+', choices=range(75),
+                    help='numeric value of SaNtfProbableCauseT\n'
+                         '(SA_NTF_ADAPTER_ERROR...SA_NTF_UNSPECIFIED_REASON)')
+parser.add_argument('--perceivedSeverities', '-r', metavar='0...5', type=int,
+                    nargs='+', choices=range(6),
+                    help='numeric value of alarm SaNtfSeverityT\n'
+                         '(clear=0,ind,warn,min,maj,critical=5)')
+parser.add_argument('--trends', '-T', metavar='0...2', type=int,
+                    nargs='+', choices=range(3),
+                    help='numeric value of SaNtfSeverityTrendT')
+parser.add_argument('--severities', '-S', metavar='0...5', type=int,
+                    nargs='+', choices=range(6),
+                    help='numeric value of security alarm SaNtfSeverityT\n'
+                         '(clear=0,ind,warn,min,maj,critical=5)')
+
+# Parse command line arguments for user input, if any
+# Default values will be used otherwise
+args = parser.parse_args()
+
+notif_type_subscriptions = []
+
+if args.objectCreateDelete:
+    notif_type_subscriptions.append(
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE)
+if args.attributeChange:
+    notif_type_subscriptions.append(
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE)
+if args.stateChange:
+    notif_type_subscriptions.append(
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE)
+if args.alarm:
+    notif_type_subscriptions.append(
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM)
+if args.securityAlarm:
+    notif_type_subscriptions.append(
+        saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM)
+
+# Initialize an NTF subscriber
+subscriber = NtfSubscriber()
+rc = subscriber.init(print_received_notification)
+if rc != eSaAisErrorT.SA_AIS_OK:
+    print("ERROR: saNtfInitialize FAILED, rc = %s\n" %
+          eSaAisErrorT.whatis(rc))
+    sys.exit(1)
+
+# Set notification subscribe filter values, if any, provided by the users
+if args.eventTypes is not None:
+    subscriber.set_filter_event_types(args.eventTypes)
+if args.notificationClassIds is not None:
+    subscriber.set_filter_ntf_class_ids(args.notificationClassIds)
+if args.notificationObjects is not None:
+    subscriber.set_filter_notification_objects(args.notificationObjects)
+if args.notifyingObjects is not None:
+    subscriber.set_filter_notifying_objects(args.notifyingObjects)
+if args.sourceIndicators is not None:
+    subscriber.set_filter_source_indicators(args.sourceIndicators)
+if args.changedStateIds is not None:
+    state_changes = []
+    for state_id in args.changedStateIds:
+        state_changes.append(ntf.StateChange(state_id, 0, False, 0))
+    subscriber.set_filter_changed_states(state_changes)
+if args.probableCauses is not None:
+    subscriber.set_filter_probable_causes(args.probableCauses)
+if args.perceivedSeverities is not None:
+    subscriber.set_filter_perceived_severities(args.perceivedSeverities)
+if args.trends is not None:
+    subscriber.set_filter_trends(args.trends)
+if args.severities is not None:
+    subscriber.set_filter_severities(args.severities)
+
+# Subscribe for user-specified notification types or all types by default
+if notif_type_subscriptions:
+    rc = subscriber.subscribe(args.subscriptionId, notif_type_subscriptions)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        print("ERROR: saNtfNotificationSubscribe FAILED, rc = %s\n" %
+              eSaAisErrorT.whatis(rc))
+        sys.exit(1)
+else:
+    rc = subscriber.subscribe(args.subscriptionId)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        print("ERROR: saNtfNotificationSubscribe FAILED, rc = %s\n" %
+              eSaAisErrorT.whatis(rc))
+        sys.exit(1)
+
+# Obtain the subscriber selection object to poll for incoming notifications
+select_obj = subscriber.get_selection_object().value
+
+# Poll forever for notifications unless a timeout is specified
+while True:
+    try:
+        read_evt, _, _ = select.select([select_obj], [], [], args.timeout)
+        if select_obj in read_evt:
+            rc = subscriber.dispatch(eSaDispatchFlagsT.SA_DISPATCH_ALL)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                print("ERROR: saNtfDispatch FAILED, rc = %s\n" %
+                      eSaAisErrorT.whatis(rc))
+                sys.exit(1)
+        elif not read_evt:
+            print("Poll timeout!")
+            sys.exit(1)
+    except KeyboardInterrupt:
+        sys.exit(0)
-- 
2.7.4



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Opensaf-devel mailing list
Opensaf-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/opensaf-devel

Reply via email to