VOL-1138: Support for polled ONU KPI metrics and improved README files

Change-Id: Iba0343f3b8e1a949cb8ee7916fcc31e3e9ad58a2
diff --git a/voltha/extensions/kpi/README.md b/voltha/extensions/kpi/README.md
index 0cc9330..a55c7d2 100644
--- a/voltha/extensions/kpi/README.md
+++ b/voltha/extensions/kpi/README.md
@@ -5,7 +5,10 @@
 the same format.
 
 The original KpiEvent protobuf message is still supported for adapters that wish to use theprevious format but device adapter developers are encouraged to support the new format and
-make use of this shared library.
+make use of this shared library. 
+
+**Also**, please read the **Remaining Work Item** sections of each README.md file. Some additional
+work items as well as existing/related JIRA items are highlighted in this section. 
 
 ## KPI Manager Creation
 
@@ -267,6 +270,17 @@
 
 - Support calling a 'get-data' method before collect the metrics.  Currently metrics are collected
   in a device adapter independent way and the PM just updates what the attributes happen to have.
+  This would provide an asynchronous request and upon successful completion, the KPI metric/group
+  would be published on the Kafka bus.
+
+- [VOL-931](https://jira.opencord.org/browse/VOL-931) Support for retrieval of PM measurements
+  on-demaind. Would be best implemented after the previous async (get-data) work item.
 
 - For statistics groups that have more than one instance, do we need to be able to
   enable/disable specific instances? Major refactor of code if so (database work, ...)
+
+- [VOL-930](https://jira.opencord.org/browse/VOL-930) PM Collection Format. This format may
+  fit better with the time-series KPI collection as it requests ability for start/stop times.
+  It could possibly be done at a higher layer but the intent may be to have a greater number
+  of samples on a specific metric instance for a defined period of time. Need clarification
+  from the JIRA author.
diff --git a/voltha/extensions/kpi/adapter_pm_metrics.py b/voltha/extensions/kpi/adapter_pm_metrics.py
index 54983a3..59dbe12 100644
--- a/voltha/extensions/kpi/adapter_pm_metrics.py
+++ b/voltha/extensions/kpi/adapter_pm_metrics.py
@@ -15,6 +15,8 @@
 import structlog
 import arrow
 from twisted.internet.task import LoopingCall
+from voltha.protos.events_pb2 import KpiEvent2, KpiEventType, MetricInformation, MetricMetaData
+from voltha.protos.device_pb2 import PmConfig
 
 
 class AdapterPmMetrics(object):
@@ -25,6 +27,16 @@
     and this base class is primarily used to provide a consistent interface to configure,
     start, and stop statistics collection.
     """
+    DEFAULT_FREQUENCY_KEY = 'default-collection-frequency'
+    DEFAULT_COLLECTION_FREQUENCY = 15 * 10      # 1/10ths of a second
+
+    # If the collection object has a property of the following name, it will be used
+    # to retrieve the UTC Collection Timestamp (UTC seconds since epoch). If the collection
+    # object does not support this attribute, the current time will be used. If the attribute
+    # is supported, but returns None, this signals that no metrics are currently available
+    # for collection.
+    TIMESTAMP_ATTRIBUTE = 'timestamp'
+
     def __init__(self, adapter_agent, device_id, logical_device_id,
                  grouped=False, freq_override=False, **kwargs):
         """
@@ -47,7 +59,8 @@
         device = self.adapter_agent.get_device(self.device_id)
         self.serial_number = device.serial_number
 
-        self.default_freq = 150
+        self.default_freq = kwargs.get(AdapterPmMetrics.DEFAULT_FREQUENCY_KEY,
+                                       AdapterPmMetrics.DEFAULT_COLLECTION_FREQUENCY)
         self.grouped = grouped
         self.freq_override = grouped and freq_override
         self.lc = None
@@ -98,8 +111,6 @@
 
         :return: (MetricInformation) collected metrics
         """
-        from voltha.protos.device_pb2 import PmConfig
-        from voltha.protos.events_pb2 import MetricInformation, MetricMetaData
         assert ' ' not in group_name,  'Spaces are not allowed in metric titles, use an underscore'
 
         if group is None:
@@ -107,7 +118,13 @@
 
         metrics = dict()
         context = dict()
-        now = arrow.utcnow().float_timestamp
+
+        now = getattr(group, AdapterPmMetrics.TIMESTAMP_ATTRIBUTE) \
+            if hasattr(group, AdapterPmMetrics.TIMESTAMP_ATTRIBUTE) \
+            else arrow.utcnow().float_timestamp
+
+        if now is None:
+            return None     # No metrics available at this time for collection
 
         for (metric, t) in names:
             if config[metric].type == PmConfig.CONTEXT and hasattr(group, metric):
@@ -177,8 +194,6 @@
         :param data: (list) Existing list of collected metrics (MetricInformation)
                             to convert to a KPIEvent and publish
         """
-        from voltha.protos.events_pb2 import KpiEvent2, KpiEventType
-
         self.log.debug('publish-metrics', data=data)
 
         if len(data):
diff --git a/voltha/extensions/kpi/olt/README.md b/voltha/extensions/kpi/olt/README.md
new file mode 100644
index 0000000..e0012b8
--- /dev/null
+++ b/voltha/extensions/kpi/olt/README.md
@@ -0,0 +1,179 @@
+# OLT PM Metrics
+
+
+**THESE ARE PRELIMINARY METRIC GROUPS**, Work is needed by the VOLTHA community to reach a consensus on the
+actual metrics that will be provided. **Also**, please read the **Remaining Work Item** sections of each
+README file.
+
+
+
+This document outlines the metrics reported by VOLTHA OLTs.  These are currently collected
+from OLT Device Adapter which is responsible for polling the hardware for information. A future
+version of the Performance Monitoring Library will allow for collection on-demand.
+
+## Format on the Kafka bus
+
+The format of the OLT KPI Events is detailed in the [Basic KPI Format (**KpiEvent2**)](../README.md)
+section of this documents parent directory for wire format on the bus. This document primarily provides
+the group metric information for OLT PKIs and associated metadata context information.
+
+**All** metric values reported by the library are reported as *float*s. The context and metric tables
+listed in the sections below report the type as initially collected by the OLT Device Adapters.
+
+#OLT PM Metric Groups
+
+The following sections outline the KPI metrics gathered by most OLT Device adapters. If an OLT does not
+support a specific metric in a group, it will not report that metric. This is preferred to reporting a
+metric and it always having a value of 0.0 (which could be misleading).
+
+## Admin and Oper State/status
+
+Various interfaces will provide a numeric (integer) value for the current Admin State and Operation
+Status of the interface.  These map to the following states:
+
+**Admin State**
+
+| State             | Value | Notes |
+| ----------------: | :---: | :---- |
+| UNKNOWN           |   0   | The administrative state of the device is unknown |
+| DISABLED          |   2   | The device is disabled and shall not perform its intended forwarding functions other than being available for re-activation. |
+| PREPROVISIONED    |   1   | The device is pre-provisioned into Voltha, but not contacted by it |
+| ENABLED           |   3   | The device is enabled for activation and operation |
+| DOWNLOADING_IMAGE |   4   | The device is in the state of image download |
+
+**Operational Status**
+
+| State      | Value | Notes |
+| ---------: | :---: | :---- |
+| UNKNOWN    |   0   | The status of the device is unknown at this point |
+| DISCOVERED |   1   | The device has been discovered, but not yet activated |
+| ACTIVATING |   2   | The device is being activated (booted, rebooted, upgraded, etc.) |
+| TESTING    |   3   | Service impacting tests are being conducted |
+| ACTIVE     |   4   | The device is up and active |
+| FAILED     |   5   | The device has failed and cannot fulfill its intended role |
+
+## NNI KPI Metrics
+
+This metric provides metrics for a specific NNI Port of an OLT
+
+**Metadata Context items**
+
+| key         | value   | Notes |
+| :---------: | :------ | :---- |
+| intf_id     | integer | Physical device interface port number for this NNI port |
+
+**Metrics**
+
+| key              | type / size  | Notes |
+| :--------------: | :----------- | :---- |
+| admin_state      | state        | See _Admin State_ section above |
+| oper_status      | state        | See _Operational Status_ section above |
+| rx_bytes         | int, 64-bits | TODO: add definition here... |
+| rx_packets       | int, 64-bits | TODO: add definition here... |
+| rx_ucast_packets | int, 64-bits | TODO: add definition here... |
+| rx_mcast_packets | int, 64-bits | TODO: add definition here... |
+| rx_bcast_packets | int, 64-bits | TODO: add definition here... |
+| rx_error_packets | int, 64-bits | TODO: add definition here... | 
+| tx_bytes         | int, 64-bits | TODO: add definition here... |
+| tx_packets       | int, 64-bits | TODO: add definition here... |
+| tx_ucast_packets | int, 64-bits | TODO: add definition here... |
+| tx_mcast_packets | int, 64-bits | TODO: add definition here... |
+| tx_bcast_packets | int, 64-bits | TODO: add definition here... |
+| tx_error_packets | int, 64-bits | TODO: add definition here... |
+| rx_crc_errors    | int, 64-bits | TODO: add definition here... |
+| bip_errors       | int, 64-bits | TODO: add definition here... |
+
+## PON KPI Metrics
+
+The OLT PON Port metrics
+
+**Metadata Context items**
+
+| key         | value   | Notes |
+| :---------: | :------ | :---- |
+| intf_id     | integer | Physical device interface port number for this NNI port |
+| pon_id      | integer | PON ID (0..n) |
+
+**Metrics**
+
+| key                  | type / size  | Notes |
+| :------------------: | :----------- | :---- |
+| admin_state          | state        | See _Admin State_ section above |
+| oper_status          | state        | See _Operational Status_ section above |
+| rx_packets           | int, 64-bits | Sum of all the RX Packets of GEM ports that are not base TCONT's |
+| rx_bytes             | int, 64-bits | Sum of all the RX Octets of GEM ports that are not base TCONT's |
+| tx_packets           | int, 64-bits | Sum of all the TX Packets of GEM ports that are not base TCONT's |
+| tx_bytes             | int, 64-bits | Sum of all the TX Octets of GEM ports that are not base TCONT's |
+| tx_bip_errors        | int, 32-bits | Sum of all the TX ONU bip errors to get TX BIP's per PON |
+| in_service_onus      | int          | The number of activated ONUs on this pon |
+| closest_onu_distance | float        | Distance to the closest ONU, units=kM w/granularity in the thousandths |
+
+## ONU KPI Metrics
+
+The OLT metrics for each activated ONUs
+
+**Metadata Context items**
+
+| key         | value   | Notes |
+| :---------: | :------ | :---- |
+| intf_id     | integer | Physical device interface port number for this NNI port |
+| pon_id      | integer | PON ID (0..n) |
+| onu_id      | integer | ONU ID |
+
+**Metrics**
+
+| key                | type / size  | Notes |
+| :----------------: | :----------- | :---- |
+| fiber_length       | float        | Distance to ONU, units=kM w/granularity in the thousandths |
+| equalization_delay | int, 32-bits | Equalization delay |
+| rssi               | int, 32-bits | The received signal strength indication of the ONU. |
+
+**TODO**: How about the following as well?
+ - rx_packets - int, 32-bits - Rx packets received on all GEM ports
+ - rx_bytes   - int, 64-bits - Rx octets received on all GEM ports
+ - tx_packets - int, 32-bits - Tx packets transmitted on all GEM ports
+ - tx_bytes   - int, 64-bits - Rx packets transmitted on all GEM ports
+ - tx_bip_errors - int, 32-bits - Sum of all the TX ONU bip errors to get TX BIP's on all GEM ports
+
+## GEM Port KPI Metrics
+
+The GEM Port metrics for each activated ONUs
+
+**Metadata Context items**
+
+| key         | value   | Notes |
+| :---------: | :------ | :---- |
+| intf_id     | integer | Physical device interface port number for this NNI port |
+| pon_id      | integer | PON ID (0..n) |
+| onu_id      | integer | ONU ID |
+| gem_id      | integer | GEM Port ID |
+
+**Metrics**
+
+| key         | type / size  | Notes |
+| :---------: | :----------- | :---- |
+| alloc_id    | int, 16-bits | TODO: add definition here... |
+| rx_packets  | int, 32-bits | Rx packets received |
+| rx_bytes    | int, 64-bits | Rx octets received |
+| tx_packets  | int, 32-bits | Tx packets transmitted |
+| tx_bytes    | int, 64-bits | Rx packets transmitted |
+
+# Remaining Work Items
+
+This initial code is only a preliminary work. See the [Remaining Work Items](../README.md)
+section of this document's parent directory for a list of remaining tasks. 
+  
+- [VOL-932](https://jira.opencord.org/browse/VOL-932) PM Interval collection on the OLT. Need
+  to consult OLT device adapter vendors and operators for which KPIs would best fit in the
+  interval groups. Intervals differ from other metric groups as they are defined to collect on
+  a specific interval (15-minutes most common) and at the start of the interval, the counters
+  should be set to zero so that the accumulation during the interval is what is reported. See
+  also [VOL-933](https://jira.opencord.org/browse/VOL-932),
+       [VOL-934](https://jira.opencord.org/browse/VOL-934),
+       [VOL-935](https://jira.opencord.org/browse/VOL-935),
+       [VOL-938](https://jira.opencord.org/browse/VOL-938),
+       [VOL-939](https://jira.opencord.org/browse/VOL-939),
+       [VOL-940](https://jira.opencord.org/browse/VOL-940).
+       **NOTE**: A couple of the ones above are for the ONU
+
+TODO: For each group, list if the default is enabled/disabled
\ No newline at end of file
diff --git a/voltha/extensions/kpi/olt/olt_pm_metrics.py b/voltha/extensions/kpi/olt/olt_pm_metrics.py
index 8965fcd..ea2e0c8 100644
--- a/voltha/extensions/kpi/olt/olt_pm_metrics.py
+++ b/voltha/extensions/kpi/olt/olt_pm_metrics.py
@@ -131,11 +131,8 @@
                     if group_config is not None:
                         group_config.enabled = group.enabled
             else:
-                for m in pm_config.metrics:
-                    self.nni_metrics_config[m.name].enabled = m.enabled
-                    self.pon_metrics_config[m.name].enabled = m.enabled
-                    self.onu_metrics_config[m.name].enabled = m.enabled
-                    self.gem_metrics_config[m.name].enabled = m.enabled
+                msg = 'There are no independent OLT metrics, only group metrics at this time'
+                raise NotImplemented(msg)
 
         except Exception as e:
             self.log.exception('update-failure', e=e)
diff --git a/voltha/extensions/kpi/onu/IntervalMetrics.md b/voltha/extensions/kpi/onu/IntervalMetrics.md
index 85d673c..31169ae 100644
--- a/voltha/extensions/kpi/onu/IntervalMetrics.md
+++ b/voltha/extensions/kpi/onu/IntervalMetrics.md
@@ -52,6 +52,11 @@
 | interval_end_time   | int, 8-bits  | Identifies the most recently finished 15 minute. This attribute is set to zero when a synchronize time request is performed by OpenOMCI.  This counter rolls over from 255 to 0 upon saturation. | 
 | interval_start_time | int, 64-bits | The UTC timestamp (seconds since epoch) rounded down to the start time of the specific interval |
 
+# Supported 15-Minute Historical Performance Monitoring MEs
+
+The following 15-minute historical performance monitoring MEs currently supported are detailed
+in the sections below
+
 ## Ethernet Frame Performance Monitoring MEs
 
 The OMCI Ethernet PM supported by OpenOMCI includes 4 possible MEs.  These MEs are attached to
@@ -76,6 +81,10 @@
 | Ethernet Frame Upstream Performance MonitoringHistoryData   |   322    |  32-bit |
 | Ethernet Frame Downstream Performance MonitoringHistoryData |   321    |  32-bit |
 
+**Metric Group Name**: Ethernet_Bridge_Port_History  
+**Default Collection**: True  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Counter Information
 
 Each of the Ethernet Frame PM MEs contain the following counters
@@ -102,6 +111,10 @@
 This managed entity collects some of the performance monitoring data for a physical
 Ethernet interface. Instances of this managed entity are created and deleted by the OLT.
 
+**Metric Group Name**: Ethernet_UNI_History  
+**Default Collection**: True  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Application
 
 For performance monitoring of Ethernet UNI.
@@ -136,6 +149,10 @@
 This managed entity collects performance monitoring data associated with PON downstream FEC
 counters. Instances of this managed entity are created and deleted by the OLT.
 
+**Metric Group Name**: FEC_History  
+**Default Collection**: True  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Application
 This managed entity collects performance monitoring data associated with PON downstream FEC
 counters.
@@ -167,6 +184,10 @@
 Note 2: This managed entity replaces the GEM port performance history data managed entity and
 is preferred for new implementations.
 
+**Metric Group Name**: GEM_Port_History  
+**Default Collection**: False  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Relationships
 
 An instance of this managed entity is associated with an instance of the GEM port network CTP
@@ -192,6 +213,10 @@
 This managed entity collects performance monitoring data associated with the XG-PON
 transmission convergence layer, as defined in ITU-T G.987.3.
 
+**Metric Group Name**: xgPON_TC_History  
+**Default Collection**: False  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Relationships
 An instance of this managed entity is associated with an ANI-G.
 
@@ -216,6 +241,10 @@
 transmission convergence layer, as defined in ITU-T G.987.3. It collects counters associated with
 downstream PLOAM and OMCI messages.
 
+**Metric Group Name**: xgPON_Downstream_History  
+**Default Collection**: False  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ### Relationships
 
 An instance of this managed entity is associated with an ANI-G.           
@@ -247,6 +276,10 @@
 transmission convergence layer, as defined in ITU-T G.987.3. It counts upstream PLOAM
 messages transmitted by the ONU.
 
+**Metric Group Name**: xgPON_Upstream_History  
+**Default Collection**: False  
+**Default Interval**:  15 minutes & aligned to wall-clock. Read-Only
+
 ###Relationships
 
 An instance of this managed entity is associated with an ANI-G.          
diff --git a/voltha/extensions/kpi/onu/README.md b/voltha/extensions/kpi/onu/README.md
new file mode 100644
index 0000000..4b9798e
--- /dev/null
+++ b/voltha/extensions/kpi/onu/README.md
@@ -0,0 +1,123 @@
+# ONU PM Metrics
+
+
+**THESE ARE PRELIMINARY METRIC GROUPS**, Work is needed by the VOLTHA community to reach a consensus on the
+actual metrics that will be provided.  **Also**, please read the **Remaining Work Item** sections of each
+README file.
+
+
+This document outlines the non-interval metrics collected for the ONU by the OpenOMCI code.  These
+are primarily collected from one of the many OMCI Managed Entities
+
+## Format on the Kafka bus
+
+The format of the ONU KPI Events is detailed in the [Basic KPI Format (**KpiEvent2**)](../README.md)
+section of this documents parent directory for wire format on the bus. This document primarily provides
+the group metric information for OLT PKIs and associated metadata context information.
+
+**All** metric values reported by the library are reported as *float*s. The context and metric tables
+listed in the sections below report the type as initially collected by the OLT Device Adapters.
+
+#ONU PM Metric Groups
+
+The following sections outline the KPI metrics gathered by OpenOMCI on behalf of the ONU. If an ONU
+does not support a specific metric in a group, it will not report that metric. This is preferred to
+reporting a metric and it always having a value of 0.0 (which could be misleading).
+
+**Note**: Currently all metric groups are collected and reported at one time (only one collection timer)
+and this value is controlled by the VOLTHA shared kpi library's PM_Config default_freq value and will
+be set to _60 seconds_. This single-collection deficiency will be corrected in the near future. 
+
+## ANI Optical KPI Metrics
+
+This group reports the ONU's Optical Power metrics for each PON physical port as reported by the
+OMCI Managed Entity ANI-G (_Class ID #263_).
+
+**Metric Group Name**: PON_Optical 
+**Default Collection**: True  
+**Default Interval**:  15 minutes
+
+**Metadata Context items**
+
+| key     | value   | Notes |
+| :-----: | :------ | :---- |
+| intf_id | integer | Physical device interface port ID for this PON/ANI port |
+
+The port ID is extracted from the lower 8-bits of the ANI-G Managed Entity ID and indicates
+the physical position of the PON interface.
+
+**Metrics**
+
+| key                     | type / size  | Notes |
+| :---------------------: | :----------- | :---- |
+| transmit_power          | int, 16-bits | This attribute reports the current measurement of mean optical launch power. Its value is a 2s complement integer referred to 1 mW (i.e., dBm), with 0.002 dB granularity |
+| receive_power           | int, 16-bits | This attribute reports the current measurement of the total downstream optical signal level. Its value is a 2s complement integer referred to 1 mW (i.e., dBm), with 0.002 dB granularity. |
+
+**NOTE**: The following metrics were also requested for the PON interface in
+[VOL-935](https://jira.opencord.org/browse/VOL-935) but they are not available through
+the OpenOMCI set of Managed Entities. However there are alarms available that relate to
+these items available through the ANI-G ME:
+
+ - ONT Optical module/transceiver temperature
+ - ONT Optical module/transceiver voltage
+ - ONT Laser bias current
+ 
+TR-287 does reference mechanisms to perform OLT and ONU Optical Link monitoring to cover these
+three items but interfaces are not yet available in VOLTHA and retrieval of these values from
+an ONU may be difficult as the only defined interface to retrieve data is OMCI.
+
+## UNI KPI Metrics
+
+This group reports metrics associated with the customer facing UNI port of the ONU
+and is collected from OMCI Physical Path Termination Point Ethernet UNI (_Class ID #11_)
+and the UNI-G (_Class iD #264_).
+
+**Metric Group Name**: UNI_Status
+**Default Collection**: True  
+**Default Interval**:  15 minutes
+
+**Metadata Context items**
+
+| key     | value   | Notes |
+| :-----: | :------ | :---- |
+| intf_id | integer | Physical device interface port ID for this UNI port |
+
+The port ID is extracted from the UNI-G Managed Entity ID and indicates the 
+physical position of the UNI interface.  This ID is implicitly linked to the
+associated PPTP Ethernet UNI ME.
+
+**Metrics**
+
+| key              | type / size | From  | Notes |
+| :--------------: | :---------- | :---- | :---- |
+| ethernet_type    | int, gauge  | PPTP  | This attribute represents the sensed interface type as defined in the table below |
+| oper_status      | boolean     | PPTP  | Link status/Operational Status: Link up (1), Link down (0) |
+| pptp_admin_state | boolean     | PPTP  | Administrative state: Locked/disabled (1), Unlocked/enabled (0) |
+| uni_admin_state  | boolean     | UNI-G | Administrative state: Locked/disabled (1), Unlocked/enabled (0) |
+
+**Sensed Ethernet Type Table**
+
+| value | Rate             | Duplex |
+| ----: | :--------------: | :--- |
+|  0x00 | Unknown          | n/a  |
+|  0x01 | 10BASE-T         | full |
+|  0x02 | 100BASE-T        | full |
+|  0x03 | Gigabit Ethernet | full |
+|  0x04 | 10Gb/s Ethernet  | full |
+|  0x05 | 2.5Gb/s Ethernet | full |
+|  0x06 | 5Gb/s Ethernet   | full |
+|  0x07 | 25Gb/s Ethernet  | full |
+|  0x08 | 40Gb/s  Ethernet | full |
+|  0x11 | 10BASE-T         | half |
+|  0x12 | 100BASE-T        | half |
+|  0x13 | Gigabit Ethernet | half |
+
+# Remaining Work Items
+
+This initial code is only a preliminary work. See the [Remaining Work Items](../README.md)
+section of this document's parent directory for a list of remaining tasks. In addition to these
+work items, the interval statistics [README](./IntervalMetrics.md) may have additional work
+items remaining.
+
+
+TODO: For each group, list if the default is enabled/disabled
\ No newline at end of file
diff --git a/voltha/extensions/kpi/onu/onu_omci_pm.py b/voltha/extensions/kpi/onu/onu_omci_pm.py
index c2e6682..fc2316e 100644
--- a/voltha/extensions/kpi/onu/onu_omci_pm.py
+++ b/voltha/extensions/kpi/onu/onu_omci_pm.py
@@ -12,19 +12,34 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import arrow
 from voltha.protos.device_pb2 import PmConfig, PmGroupConfig
+from voltha.protos.events_pb2 import MetricInformation, MetricMetaData
 from voltha.extensions.kpi.adapter_pm_metrics import AdapterPmMetrics
 from voltha.extensions.kpi.onu.onu_pm_interval_metrics import OnuPmIntervalMetrics
+from voltha.extensions.omci.omci_entities import UniG
+from voltha.extensions.omci.omci_entities import PptpEthernetUni
 
 
 class OnuOmciPmMetrics(AdapterPmMetrics):
     """ ONU OMCI related metrics """
 
     # Metric default settings
-    DEFAULT_OMCI_CC_ENABLED = False
-    DEFAULT_OMCI_CC_FREQUENCY = 1200        # 1/10ths of a second
-
+    #
+    #  Frequency values are in 1/10ths of a second
+    #
+    OMCI_DEV_KEY = 'omci-onu-dev'
     OMCI_CC_GROUP_NAME = 'OMCI_CC'
+    DEFAULT_OMCI_CC_ENABLED = False
+    DEFAULT_OMCI_CC_FREQUENCY = (2 * 60) * 10
+
+    OPTICAL_GROUP_NAME = 'PON_Optical'
+    DEFAULT_OPTICAL_ENABLED = True
+    DEFAULT_OPTICAL_FREQUENCY = (15 * 60 * 10)
+
+    UNI_STATUS_GROUP_NAME = 'UNI_Status'
+    DEFAULT_UNI_STATUS_ENABLED = True
+    DEFAULT_UNI_STATUS_FREQUENCY = (15 * 60 * 10)
 
     def __init__(self, adapter_agent, device_id, logical_device_id,
                  grouped=False, freq_override=False, **kwargs):
@@ -41,16 +56,18 @@
                               expected key-value pairs are listed below. If not provided, the
                               associated PM statistics are not gathered:
 
-                              'omci-cc': Reference to the OMCI_CC object for retrieval of OpenOMCI
-                                         Communications channel statistics. Available from the ONU's
-                                         OpenOMCI OnuDeviceEntry object.
+                              'omci-onu-dev': Reference to the OMCI OnuDeviceEtnry object for
+                                         retrieval of OpenOMCI Communications channel statistics
+                                         and retrieval of polled statistics.
         """
         super(OnuOmciPmMetrics, self).__init__(adapter_agent, device_id, logical_device_id,
                                                grouped=grouped, freq_override=freq_override,
                                                **kwargs)
-        self._omci_cc = kwargs.pop('omci-cc', None)
 
-        self.omci_pm_names = {
+        self._omci_onu_device = kwargs.pop(OnuOmciPmMetrics.OMCI_DEV_KEY, None)
+        self._omci_cc = self._omci_onu_device.omci_cc if self._omci_onu_device is not None else None
+
+        self.omci_cc_pm_names = {
             ('tx_frames', PmConfig.COUNTER),
             ('tx_errors', PmConfig.COUNTER),
             ('rx_frames', PmConfig.COUNTER),
@@ -63,8 +80,28 @@
             ('reply_max', PmConfig.GAUGE),      # Milliseconds
             ('reply_average', PmConfig.GAUGE),  # Milliseconds
         }
-        self.omci_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
-                                    for (m, t) in self.omci_pm_names}
+        self.omci_cc_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
+                                       for (m, t) in self.omci_cc_pm_names}
+
+        self.omci_optical_pm_names = {
+            ('intf_id', PmConfig.CONTEXT),
+
+            ('transmit_power', PmConfig.GAUGE),
+            ('receive_power', PmConfig.GAUGE),
+        }
+        self.omci_optical_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
+                                            for (m, t) in self.omci_optical_pm_names}
+
+        self.omci_uni_pm_names = {
+            ('intf_id', PmConfig.CONTEXT),
+
+            ('ethernet_type', PmConfig.GAUGE),     # PPTP Ethernet ME
+            ('oper_status', PmConfig.GAUGE),       # PPTP Ethernet ME
+            ('pptp_admin_state', PmConfig.GAUGE),  # PPTP Ethernet ME
+            ('uni_admin_state', PmConfig.GAUGE),   # UNI-G ME
+        }
+        self.omci_uni_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
+                                        for (m, t) in self.omci_uni_pm_names}
 
         self.openomci_interval_pm = OnuPmIntervalMetrics(adapter_agent, device_id, logical_device_id)
 
@@ -82,37 +119,46 @@
                 if group_config is not None:
                     group_config.enabled = group.enabled
         else:
-            for m in pm_config.metrics:
-                self.omci_metrics_config[m.name].enabled = m.enabled
+            msg = 'There are on independent OMCI metrics, only group metrics at this time'
+            raise NotImplemented(msg)
 
         self.openomci_interval_pm.update(pm_config)
 
     def make_proto(self, pm_config=None):
         assert pm_config is not None
 
-        if self._omci_cc is not None:
-            if self.grouped:
-                pm_omci_stats = PmGroupConfig(group_name=OnuOmciPmMetrics.OMCI_CC_GROUP_NAME,
-                                              group_freq=OnuOmciPmMetrics.DEFAULT_OMCI_CC_FREQUENCY,
-                                              enabled=OnuOmciPmMetrics.DEFAULT_OMCI_CC_ENABLED)
-                self.pm_group_metrics[pm_omci_stats.group_name] = pm_omci_stats
-            else:
-                pm_omci_stats = pm_config
+        # OMCI only supports grouped metrics
+        if self._omci_onu_device is None or not self.grouped:
+            return pm_config
 
-            metrics = set()
-            for m in sorted(self.omci_metrics_config):
-                pm = self.omci_metrics_config[m]
-                if not self.grouped:
-                    if pm.name in metrics:
-                        continue
-                    metrics.add(pm.name)
+        pm_omci_cc_stats = PmGroupConfig(group_name=OnuOmciPmMetrics.OMCI_CC_GROUP_NAME,
+                                         group_freq=OnuOmciPmMetrics.DEFAULT_OMCI_CC_FREQUENCY,
+                                         enabled=OnuOmciPmMetrics.DEFAULT_OMCI_CC_ENABLED)
+        self.pm_group_metrics[pm_omci_cc_stats.group_name] = pm_omci_cc_stats
 
-                pm_omci_stats.metrics.extend([PmConfig(name=pm.name,
-                                                       type=pm.type,
-                                                       enabled=pm.enabled)])
-            if self.grouped:
-                pm_config.groups.extend([pm_omci_stats])
+        pm_omci_optical_stats = PmGroupConfig(group_name=OnuOmciPmMetrics.OPTICAL_GROUP_NAME,
+                                              group_freq=OnuOmciPmMetrics.DEFAULT_OPTICAL_FREQUENCY,
+                                              enabled=OnuOmciPmMetrics.DEFAULT_OPTICAL_ENABLED)
+        self.pm_group_metrics[pm_omci_optical_stats.group_name] = pm_omci_optical_stats
 
+        pm_omci_uni_stats = PmGroupConfig(group_name=OnuOmciPmMetrics.UNI_STATUS_GROUP_NAME,
+                                          group_freq=OnuOmciPmMetrics.DEFAULT_UNI_STATUS_FREQUENCY,
+                                          enabled=OnuOmciPmMetrics.DEFAULT_UNI_STATUS_ENABLED)
+        self.pm_group_metrics[pm_omci_uni_stats.group_name] = pm_omci_uni_stats
+
+        stats_and_config = [(pm_omci_cc_stats, self.omci_cc_metrics_config),
+                            (pm_omci_optical_stats, self.omci_optical_metrics_config),
+                            (pm_omci_uni_stats, self.omci_cc_metrics_config)]
+
+        for stats, config in stats_and_config:
+            for m in sorted(config):
+                pm = config[m]
+                stats.metrics.extend([PmConfig(name=pm.name,
+                                               type=pm.type,
+                                               enabled=pm.enabled)])
+            pm_config.groups.extend([stats])
+
+        # Also create OMCI Interval PM configs
         return self.openomci_interval_pm.make_proto(pm_config)
 
     def collect_metrics(self, data=None):
@@ -145,8 +191,122 @@
             if self.pm_group_metrics[group_name].enabled:
                 group_data = self.collect_group_metrics(group_name,
                                                         self._omci_cc,
-                                                        self.omci_pm_names,
-                                                        self.omci_metrics_config)
+                                                        self.omci_cc_pm_names,
+                                                        self.omci_cc_metrics_config)
                 if group_data is not None:
                     data.append(group_data)
+
+            # Optical and UNI data is collected on a per-port basis
+            data.extend(self.collect_optical_metrics())
+            data.extend(self.collect_uni_status_metrics())
+
         return data
+
+    def collect_optical_metrics(self):
+        """
+        Collect the metrics for optical information from all ANI/PONs
+
+        :return: (list) collected metrics (MetricInformation)
+        """
+        now = self._omci_onu_device.timestamp
+
+        group_name = OnuOmciPmMetrics.OPTICAL_GROUP_NAME
+        if now is None or not self.pm_group_metrics[group_name].enabled:
+            return []
+
+        # Scan all ANI-G ports
+        ani_g_entities = self._omci_onu_device.configuration.ani_g_entities
+        ani_g_entities_ids = ani_g_entities.keys() if ani_g_entities is not None else None
+        metrics_info = []
+
+        if ani_g_entities_ids is not None and len(ani_g_entities_ids):
+            from voltha.extensions.omci.omci_entities import AniG
+            ani_g_items = ['optical_signal_level', 'transmit_optical_level']
+
+            for entity_id in ani_g_entities_ids:
+                metrics = dict()
+                data = self._omci_onu_device.query_mib(class_id=AniG.class_id,
+                                                       instance_id=entity_id,
+                                                       attributes=ani_g_items)
+                if len(data):
+                    if 'optical_signal_level' in data:
+                        metrics['receive_power'] = data['optical_signal_level']
+
+                    if 'transmit_optical_level' in data:
+                        metrics['transmit_power'] = data['transmit_optical_level']
+
+                if len(metrics):
+                    metric_data = MetricInformation(metadata=MetricMetaData(title=group_name,
+                                                                            ts=now,
+                                                                            logical_device_id=self.logical_device_id,
+                                                                            serial_no=self.serial_number,
+                                                                            device_id=self.device_id,
+                                                                            context={
+                                                                                'intf_id': str(entity_id)
+                                                                            }),
+                                                    metrics=metrics)
+                    metrics_info.append(metric_data)
+
+        return metrics_info
+
+    def collect_uni_status_metrics(self):
+        """
+        Collect the metrics for optical information from all ANI/PONs
+
+        :return: (list) collected metrics (MetricInformation)
+        """
+        now = self._omci_onu_device.timestamp
+
+        group_name = OnuOmciPmMetrics.UNI_STATUS_GROUP_NAME
+        if now is None or not self.pm_group_metrics[group_name].enabled:
+            return []
+
+        # Scan all UNI-G and PPTP ports
+        uni_g_entities = self._omci_onu_device.configuration.uni_g_entities
+        uni_g_entities_ids = uni_g_entities.keys() if uni_g_entities is not None else None
+        pptp_entities = self._omci_onu_device.configuration.pptp_entities
+        pptp_entities_ids = pptp_entities.keys() if pptp_entities is not None else None
+
+        metrics_info = []
+
+        if uni_g_entities_ids and pptp_entities_ids and len(uni_g_entities_ids) and \
+                len(uni_g_entities_ids) <= len(pptp_entities_ids):
+
+            uni_g_items = ['administrative_state']
+            pptp_items = ['administrative_state', 'operational_state', 'sensed_type']
+
+            for entity_id in pptp_entities_ids:
+                metrics = dict()
+                data = self._omci_onu_device.query_mib(class_id=UniG.class_id,
+                                                       instance_id=entity_id,
+                                                       attributes=uni_g_items)
+                if len(data):
+                    if 'administrative_state' in data:
+                        metrics['uni_admin_state'] = data['administrative_state']
+
+                data = self._omci_onu_device.query_mib(class_id=PptpEthernetUni.class_id,
+                                                       instance_id=entity_id,
+                                                       attributes=pptp_items)
+                if len(data):
+                    if 'administrative_state' in data:
+                        metrics['pptp_admin_state'] = data['administrative_state']
+
+                    if 'operational_state' in data:
+                        metrics['oper_status'] = data['operational_state']
+
+                    if 'sensed_type' in data:
+                        metrics['ethernet_type'] = data['sensed_type']
+
+                if len(metrics):
+                    metric_data = MetricInformation(metadata=MetricMetaData(title=group_name,
+                                                                            ts=now,
+                                                                            logical_device_id=self.logical_device_id,
+                                                                            serial_no=self.serial_number,
+                                                                            device_id=self.device_id,
+                                                                            context={
+                                                                                'intf_id': str(entity_id & 0xFF)
+                                                                            }),
+                                                    metrics=metrics)
+                    metrics_info.append(metric_data)
+
+        return metrics_info
diff --git a/voltha/extensions/kpi/onu/onu_pm_interval_metrics.py b/voltha/extensions/kpi/onu/onu_pm_interval_metrics.py
index 9e44393..46d0e2a 100644
--- a/voltha/extensions/kpi/onu/onu_pm_interval_metrics.py
+++ b/voltha/extensions/kpi/onu/onu_pm_interval_metrics.py
@@ -47,6 +47,13 @@
         XgPonDownstreamPerformanceMonitoringHistoryData.class_id: 'xgPON_Downstream_History',
         XgPonUpstreamPerformanceMonitoringHistoryData.class_id: 'xgPON_Upstream_History'
     }
+    ETHERNET_BRIDGE_HISTORY_ENABLED = True
+    ETHERNET_UNI_HISTORY_ENABLED = True
+    FEC_HISTORY_ENABLED = True
+    GEM_PORT_HISTORY_ENABLED = False
+    TRANS_CONV_HISTORY_ENABLED = False
+    XGPON_DOWNSTREAM_HISTORY = False
+    XGPON_UPSTREAM_HISTORY = False
 
     def __init__(self, adapter_agent, device_id, logical_device_id, **kwargs):
         super(OnuPmIntervalMetrics, self).__init__(adapter_agent, device_id, logical_device_id,
@@ -205,11 +212,15 @@
         self.log.debug('update')
 
         try:
-            for group in pm_config.groups:
-                group_config = self.pm_group_metrics.get(group.group_name)
-                if group_config is not None and group_config.enabled != group.enabled:
-                    group_config.enabled = group.enabled
-                    # TODO: For OMCI PM Metrics, tie this into add/remove of the PM Interval ME itself
+            if pm_config.grouped:
+                for group in pm_config.groups:
+                    group_config = self.pm_group_metrics.get(group.group_name)
+                    if group_config is not None and group_config.enabled != group.enabled:
+                        group_config.enabled = group.enabled
+                        # TODO: For OMCI PM Metrics, tie this into add/remove of the PM Interval ME itself
+            else:
+                msg = 'There are on independent OMCI Interval metrics, only group metrics at this time'
+                raise NotImplemented(msg)
 
         except Exception as e:
             self.log.exception('update-failure', e=e)
@@ -218,11 +229,11 @@
     def make_proto(self, pm_config=None):
         """
         From the PM Configurations defined in this class's initializer, create
-        the PMConfigs protobuf message that defines our PM configuation and
+        the PMConfigs protobuf message that defines our PM configuration and
         data.
 
         All ONU PM Interval metrics are grouped metrics that are generated autonmouslly
-        from the OpenOMCI Performace Intervals state machine.
+        from the OpenOMCI Performance Intervals state machine.
 
         :param pm_config (PMConfigs) PM Configuration message to add OpenOMCI config items too
         :return: (PmConfigs) PM Configuration Protobuf message
@@ -231,7 +242,7 @@
 
         pm_ethernet_bridge_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id],
                                                    group_freq=0,
-                                                   enabled=True)
+                                                   enabled=OnuPmIntervalMetrics.ETHERNET_BRIDGE_HISTORY_ENABLED)
         self.pm_group_metrics[pm_ethernet_bridge_history.group_name] = pm_ethernet_bridge_history
 
         for m in sorted(self._ethernet_bridge_history_config):
@@ -242,7 +253,7 @@
 
         pm_ethernet_uni_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[EthernetPMMonitoringHistoryData.class_id],
                                                 group_freq=0,
-                                                enabled=True)
+                                                enabled=OnuPmIntervalMetrics.ETHERNET_UNI_HISTORY_ENABLED)
         self.pm_group_metrics[pm_ethernet_uni_history.group_name] = pm_ethernet_uni_history
 
         for m in sorted(self._ethernet_uni_history_config):
@@ -253,7 +264,7 @@
 
         pm_fec_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[FecPerformanceMonitoringHistoryData.class_id],
                                        group_freq=0,
-                                       enabled=True)
+                                       enabled=OnuPmIntervalMetrics.FEC_HISTORY_ENABLED)
         self.pm_group_metrics[pm_fec_history.group_name] = pm_fec_history
 
         for m in sorted(self._fec_history_config):
@@ -264,7 +275,7 @@
 
         pm_gem_port_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[GemPortNetworkCtpMonitoringHistoryData.class_id],
                                             group_freq=0,
-                                            enabled=True)
+                                            enabled=OnuPmIntervalMetrics.GEM_PORT_HISTORY_ENABLED)
         self.pm_group_metrics[pm_gem_port_history.group_name] = pm_gem_port_history
 
         for m in sorted(self._gem_port_history_config):
@@ -275,7 +286,7 @@
 
         pm_xgpon_tc_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonTcPerformanceMonitoringHistoryData.class_id],
                                             group_freq=0,
-                                            enabled=True)
+                                            enabled=OnuPmIntervalMetrics.TRANS_CONV_HISTORY_ENABLED)
         self.pm_group_metrics[pm_xgpon_tc_history.group_name] = pm_xgpon_tc_history
 
         for m in sorted(self._xgpon_tc_history_config):
@@ -286,7 +297,7 @@
 
         pm_xgpon_downstream_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonDownstreamPerformanceMonitoringHistoryData.class_id],
                                                     group_freq=0,
-                                                    enabled=True)
+                                                    enabled=OnuPmIntervalMetrics.XGPON_DOWNSTREAM_HISTORY)
         self.pm_group_metrics[pm_xgpon_downstream_history.group_name] = pm_xgpon_downstream_history
 
         for m in sorted(self._xgpon_downstream_history_config):
@@ -297,7 +308,7 @@
 
         pm_xgpon_upstream_history = PmGroupConfig(group_name=OnuPmIntervalMetrics.ME_ID_INFO[XgPonUpstreamPerformanceMonitoringHistoryData.class_id],
                                                   group_freq=0,
-                                                  enabled=True)
+                                                  enabled=OnuPmIntervalMetrics.XGPON_UPSTREAM_HISTORY)
         self.pm_group_metrics[pm_xgpon_upstream_history.group_name] = pm_xgpon_upstream_history
 
         for m in sorted(self._xgpon_upstream_history_config):
diff --git a/voltha/extensions/kpi/onu/onu_pm_metrics.py b/voltha/extensions/kpi/onu/onu_pm_metrics.py
index 2155d19..c94136a 100644
--- a/voltha/extensions/kpi/onu/onu_pm_metrics.py
+++ b/voltha/extensions/kpi/onu/onu_pm_metrics.py
@@ -29,6 +29,11 @@
     # Metric default settings
     DEFAULT_HEARTBEAT_ENABLED = False
     DEFAULT_HEARTBEAT_FREQUENCY = 1200  # 1/10ths of a second
+    #
+    # Currently only a single KPI metrics collection occurs (individual group
+    # frequency not supported). The next value defines this single frequency until
+    # the KPI shared library supports individual collection.
+    DEFAULT_ONU_COLLECTION_FREQUENCY = 60 * 10      # 1 minute
 
     def __init__(self, adapter_agent, device_id, logical_device_id,
                  grouped=False, freq_override=False, **kwargs):
@@ -91,8 +96,8 @@
                     if group_config is not None:
                         group_config.enabled = group.enabled
             else:
-                for m in pm_config.metrics:
-                    self.health_metrics_config[m.name].enabled = m.enabled
+                msg = 'There are no independent ONU metrics, only group metrics at this time'
+                raise NotImplemented(msg)
 
         except Exception as e:
             self.log.exception('update-failure', e=e)
@@ -163,7 +168,4 @@
         # if self._heartbeat is not None:
         #     data.extend(self.collect_metrics(self._heartbeat, self.health_pm_names,
         #                                      self.health_metrics_config))
-        data.extend(self.omci_pm.collect_metrics(data=data))
-        # TODO Add PON Port PM
-        # TODO Add UNI Port PM
-        return data
+        return self.omci_pm.collect_metrics(data=data)
diff --git a/voltha/extensions/omci/omci_entities.py b/voltha/extensions/omci/omci_entities.py
index 64ebab9..e1f1054 100644
--- a/voltha/extensions/omci/omci_entities.py
+++ b/voltha/extensions/omci/omci_entities.py
@@ -316,7 +316,7 @@
         ECA(ByteField("expected_type", 0), {AA.R, AA.W},
             range_check=lambda x: 0 <= x <= 254),
         ECA(ByteField("sensed_type", 0), {AA.R}, optional=True, avc=True),
-        # TODO: For sensed_type AVC, see not in AT&T OMCI Specification, V3.0, page 123
+        # TODO: For sensed_type AVC, see note in AT&T OMCI Specification, V3.0, page 123
         ECA(ByteField("autodetection_config", 0), {AA.R, AA.W},
             range_check=lambda x: x in [0, 1, 2, 3, 4, 5,
                                         0x10, 0x11, 0x12, 0x13, 0x14,
@@ -1329,7 +1329,7 @@
         ECA(IntField("ranging_time_messages_received", None), {AA.R}, counter=True),
         ECA(IntField("deactivate_onu_id_messages_received", None), {AA.R}, counter=True),
         ECA(IntField("disable_serial_number_messages_received", None), {AA.R}, counter=True),
-        ECA(IntField("request_registration_messages_receeved", None), {AA.R}, counter=True),
+        ECA(IntField("request_registration_messages_received", None), {AA.R}, counter=True),
         ECA(IntField("assign_alloc_id_messages_received", None), {AA.R}, counter=True),
         ECA(IntField("key_control_messages_received", None), {AA.R}, counter=True),
         ECA(IntField("sleep_allow_messages_received", None), {AA.R}, counter=True),
diff --git a/voltha/extensions/omci/onu_device_entry.py b/voltha/extensions/omci/onu_device_entry.py
index ca44c88..43f55b6 100644
--- a/voltha/extensions/omci/onu_device_entry.py
+++ b/voltha/extensions/omci/onu_device_entry.py
@@ -75,6 +75,7 @@
         self._deferred = None
         self._first_in_sync = False
         self._first_capabilities = False
+        self._timestamp = None
 
         # OMCI related databases are on a per-agent basis. State machines and tasks
         # are per ONU Vendor
@@ -206,6 +207,15 @@
         self._pm_intervals_sm.set_pm_config(pm_config)
 
     @property
+    def timestamp(self):
+        """Pollable Metrics last collected timestamp"""
+        return self._timestamp
+
+    @timestamp.setter
+    def timestamp(self, value):
+        self._timestamp = value
+
+    @property
     def alarm_synchronizer(self):
         """
         Reference to the OpenOMCI Alarm Synchronization state machine for this ONU
@@ -497,7 +507,8 @@
 
     def get_imagefile(self, local_name, local_dir, remote_url=None):
         """
-        Return a Deferred that will be triggered if the file is locally availabe or downloaded sucessfully
+        Return a Deferred that will be triggered if the file is locally available
+        or downloaded successfully
         """
         self.log.info('start download from {}'.format(remote_url))
 
@@ -505,4 +516,3 @@
         # self._runner.start()
 
         return self._image_agent.get_image(local_name, local_dir, remote_url)
-                                                             
diff --git a/voltha/extensions/omci/openomci_agent.py b/voltha/extensions/omci/openomci_agent.py
index 8dfd4de..d337475 100644
--- a/voltha/extensions/omci/openomci_agent.py
+++ b/voltha/extensions/omci/openomci_agent.py
@@ -68,15 +68,15 @@
         },
     },
     'alarm-synchronizer': {
-         'state-machine': AlarmSynchronizer,    # Implements the Alarm sync state machine
-         'database': AlarmDbExternal,           # For any State storage needs
-         'advertise-events': True,             # Advertise events on OpenOMCI event bus
-         'tasks': {
-             'alarm-sync': AlarmSyncDataTask,
-             'alarm-check': AlarmDataTask,
-             'alarm-resync': AlarmResyncTask,
-             'alarm-audit': AlarmDataTask
-         }
+        'state-machine': AlarmSynchronizer,    # Implements the Alarm sync state machine
+        'database': AlarmDbExternal,           # For any State storage needs
+        'advertise-events': True,              # Advertise events on OpenOMCI event bus
+        'tasks': {
+            'alarm-sync': AlarmSyncDataTask,
+            'alarm-check': AlarmDataTask,
+            'alarm-resync': AlarmResyncTask,
+            'alarm-audit': AlarmDataTask
+        }
      },
     'image_downloader': {
         'state-machine': ImageDownloadeSTM,
diff --git a/voltha/extensions/omci/state_machines/performance_intervals.py b/voltha/extensions/omci/state_machines/performance_intervals.py
index 417a706..1f58dc8 100644
--- a/voltha/extensions/omci/state_machines/performance_intervals.py
+++ b/voltha/extensions/omci/state_machines/performance_intervals.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 #
 import structlog
+import arrow
 from transitions import Machine
 from datetime import datetime, timedelta
 from random import uniform, shuffle
@@ -24,6 +25,7 @@
 from voltha.extensions.omci.omci_cc import OmciCCRxEvents, OMCI_CC, TX_REQUEST_KEY, \
     RX_RESPONSE_KEY
 from voltha.extensions.omci.database.mib_db_api import ATTRIBUTES_KEY
+from voltha.extensions.omci.tasks.omci_get_request import OmciGetRequest
 from voltha.extensions.omci.omci_entities import MacBridgePortConfigurationData
 from voltha.extensions.omci.omci_entities import EthernetPMMonitoringHistoryData, \
     FecPerformanceMonitoringHistoryData, \
@@ -33,7 +35,7 @@
     EthernetFrameUpstreamPerformanceMonitoringHistoryData, \
     EthernetFrameDownstreamPerformanceMonitoringHistoryData, \
     EthernetFrameExtendedPerformanceMonitoring, \
-    EthernetFrameExtendedPerformanceMonitoring64Bit
+    EthernetFrameExtendedPerformanceMonitoring64Bit, AniG
 
 
 RxEvent = OmciCCRxEvents
@@ -79,7 +81,7 @@
         {'trigger': 'reboot', 'source': '*', 'dest': 'rebooted'},
     ]
     DEFAULT_RETRY = 10               # Seconds to delay after task failure/timeout/poll
-    DEFAULT_TICK_DELAY = 5           # Seconds between checks for collection tick
+    DEFAULT_TICK_DELAY = 15          # Seconds between checks for collection tick
     DEFAULT_INTERVAL_SKEW = 10 * 60  # Seconds to skew past interval boundary
     DEFAULT_COLLECT_ATTEMPTS = 3     # Maximum number of collection fetch attempts
 
@@ -154,6 +156,15 @@
         self._add_pm_me = dict()        # (pm cid, pm eid) -> (me cid, me eid, upstream)
         self._del_pm_me = set()
 
+        # Pollable PM items
+        # Note that some items the KPI extracts are not listed below. These are the
+        # administrative states, operational states, and sensed ethernet type. The values
+        # in the MIB database should be accurate for these items.
+
+        self._ani_g_items = ["optical_signal_level", "transmit_optical_level"]
+        self._next_poll_time = datetime.utcnow()
+        self._poll_interval = 60                    # TODO: Fixed at once a minute
+
         # Statistics and attributes
         # TODO: add any others if it will support problem diagnosis
 
@@ -439,10 +450,37 @@
         elif len(self._add_pm_me) and self._add_me_deferred is None:
             self._add_me_deferred = reactor.callLater(0, self.add_me)
 
-        else:
-            # TODO: Compute a better mechanism than just polling here, perhaps based on
-            #       the next time to fetch data for 'any' interval
-            self._deferred = reactor.callLater(self._tick_delay, self.tick)
+        elif datetime.utcnow() >= self._next_poll_time:
+            def success(results):
+                self._device.timestamp = arrow.utcnow().float_timestamp
+                self._device.mib_synchronizer.mib_set(results.me_class.class_id,
+                                                      results.entity_id,
+                                                      results.attributes)
+                self._next_poll_time = datetime.utcnow() + timedelta(seconds=self._poll_interval)
+
+            def failure(reason):
+                self.log.info('poll-failure', reason=reason)
+                self._device.timestamp = None
+                return None
+
+            # Scan all ANI-G ports
+            ani_g_entities = self._device.configuration.ani_g_entities
+            ani_g_entities_ids = ani_g_entities.keys() if ani_g_entities is not None else None
+
+            if ani_g_entities_ids is not None and len(ani_g_entities_ids):
+                for entity_id in ani_g_entities_ids:
+                    task = OmciGetRequest(self._agent, self.device_id,
+                                          AniG, entity_id,
+                                          self._ani_g_items, allow_failure=True)
+                    self._task_deferred = self._device.task_runner.queue_task(task)
+                    self._task_deferred.addCallbacks(success, failure)
+            else:
+                self.log.warn('poll-pm-no-anis')
+                self._next_poll_time = datetime.utcnow() + timedelta(seconds=self._poll_interval)
+
+        # TODO: Compute a better mechanism than just polling here, perhaps based on
+        #       the next time to fetch data for 'any' interval
+        self._deferred = reactor.callLater(self._tick_delay, self.tick)
 
     def on_enter_create_pm_me(self):
         """
@@ -583,8 +621,8 @@
 
         # NOTE: For debugging, uncomment next section to perform collection
         #       right after initial code startup/mib-sync
-        # if self._next_interval is None:
-        #     return now     # Do it now  (just for debugging purposes)
+        if self._next_interval is None:
+            return now     # Do it now  (just for debugging purposes)
 
         # Skew the next time up to the maximum specified
         # TODO: May want to skew in a shorter range and select the minute
diff --git a/voltha/extensions/omci/tasks/omci_get_request.py b/voltha/extensions/omci/tasks/omci_get_request.py
index 137f50c..3bfdf06 100644
--- a/voltha/extensions/omci/tasks/omci_get_request.py
+++ b/voltha/extensions/omci/tasks/omci_get_request.py
@@ -91,6 +91,16 @@
             raise GetException('Get Request Task was cancelled')
 
     @property
+    def me_class(self):
+        """The OMCI Managed Entity Class associated with this request"""
+        return self._entity_class
+
+    @property
+    def entity_id(self):
+        """The ME Entity ID associated with this request"""
+        return self._entity_id
+
+    @property
     def attributes(self):
         """
         Return a dictionary of attributes for the request if the Get was