[VOL-4069] Measure and read Rx optical power for an ONU

Reading Rx optical power requires triggering an RSSI measurement and tracking the RSSI Measurement Completed indication.
The raw value is converted to mW, and a dBm value is returned back.
A small test application (~40K) is bundled within the Debian package to help with debugging the optical power levels.

Change-Id: I20f304a9de0c47b94dfd7b1d8fdd52c56d6a2983
diff --git a/.gitignore b/.gitignore
index f510ac4..56dc8a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,8 @@
 
 # for unit tests
 keystore/
+
+# for Rx optical power test app and test data
+agent/src/read_rxtx_power
+agent/read_rxtx_power
+eeprom.bin
diff --git a/BUILDING.md b/BUILDING.md
index 0f11167..106335d 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -183,7 +183,7 @@
 make OPENOLTDEVICE=asfvolt16 OPENOLT_PROTO_VER=master
 ```
 
-By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.0.15* of the
+By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.1.5* of the
 [voltha-protos](https://gerrit.opencord.org/gitweb?p=voltha-protos.git;a=summary)
 repo.
 
@@ -305,7 +305,7 @@
 make OPENOLTDEVICE=phoenix OPENOLT_PROTO_VER=master
 ```
 
-By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.0.15* of the
+By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.1.5* of the
 [voltha-protos](https://gerrit.opencord.org/gitweb?p=voltha-protos.git;a=summary)
 repo.
 
diff --git a/Makefile b/Makefile
index ecd0b52..14e68f2 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@
 
 ## Variables
 OPENOLTDEVICE     ?= asfvolt16
-OPENOLT_PROTO_VER ?= v4.0.15
+OPENOLT_PROTO_VER ?= v4.1.5
 
 DOCKER                     ?= docker
 DOCKER_REGISTRY            ?=
diff --git a/VERSION b/VERSION
index 4d9d11c..6cb9d3d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.4.2
+3.4.3
diff --git a/agent/Makefile.in b/agent/Makefile.in
index 1813112..dba3131 100644
--- a/agent/Makefile.in
+++ b/agent/Makefile.in
@@ -43,7 +43,7 @@
 # This specifies the GIT tag in https://github.com/opencord/voltha-protos
 # repo that we need to refer to, to pick the right version of
 # openolt.proto and tech_profile.proto
-OPENOLT_PROTO_VER ?= v4.0.15
+OPENOLT_PROTO_VER ?= v4.1.5
 
 # Variables used for Inband build
 INBAND = "n"
@@ -58,6 +58,9 @@
 # Build directory
 BUILD_DIR = build
 
+# Rx optical power test app
+RXTX_POWER_EXE = read_rxtx_power
+
 # initialiaze path of protoc binary
 protoc-bin = $(shell which protoc)
 
@@ -296,13 +299,14 @@
 src/%.o: src/%.cc
 	$(CXX) $(CXXFLAGS) $(CXXFLAGSDEVICE) $(CPPFLAGS) -I./common -c $< -o $@
 
-deb:
+deb: rxtx-power
 	cp $(BUILD_DIR)/release_$(OPENOLTDEVICE)_V$(BAL_VER).$(DEV_VER).tar.gz device/$(OPENOLTDEVICE)/mkdebian/debian
 	cp $(BUILD_DIR)/openolt device/$(OPENOLTDEVICE)/mkdebian/debian
 	cp $(BUILD_DIR)/libz.so.1 device/$(OPENOLTDEVICE)/mkdebian/debian
 	cp $(BUILD_DIR)/libstdc++.so.6 device/$(OPENOLTDEVICE)/mkdebian/debian
 	cp $(BUILD_DIR)/libbal_host_api.so device/$(OPENOLTDEVICE)/mkdebian/debian
 	cp -a scripts/init.d device/$(OPENOLTDEVICE)/mkdebian/debian
+	cp -a $(RXTX_POWER_EXE) device/$(OPENOLTDEVICE)/mkdebian/debian
 ifeq ("$(strip $(OPENOLTDEVICE))","phoenix")
 	sed -i '/\/opt\/bcm68650\/svk_init.sh/c\    \/opt\/bcm68650\/svk_init.sh -qsfp_speed=$(PORT_40G_SPEED) -sfp_speed=$(PORT_10G_SPEED)' device/$(OPENOLTDEVICE)/mkdebian/debian/init.d/dev_mgmt_daemon
 endif
@@ -358,7 +362,10 @@
 src/%.o: %.cpp
 	$(CXX) -MMD -c $< -o $@
 
-deb-cleanup:
+rxtx-power: clean-rxtx-power
+	$(CXX) -std=c++11 -DRXTX_POWER_EXE_MODE src/trx_eeprom_reader.cc -o $(RXTX_POWER_EXE) && ls -l $(RXTX_POWER_EXE)
+
+deb-cleanup: clean-rxtx-power
 	@rm -f device/$(OPENOLTDEVICE)/mkdebian/debian/$(OPENOLTDEVICE).debhelper.log
 	@rm -f device/$(OPENOLTDEVICE)/mkdebian/debian/$(OPENOLTDEVICE).postinst.debhelper
 	@rm -f device/$(OPENOLTDEVICE)/mkdebian/debian/$(OPENOLTDEVICE).postrm.debhelper
@@ -373,6 +380,7 @@
 	@rm -f device/$(OPENOLTDEVICE)/mkdebian/debian/release_$(OPENOLTDEVICE)_V$(BAL_VER).$(DEV_VER).tar.gz
 	@rm -rf device/$(OPENOLTDEVICE)/mkdebian/debian/tmp/
 	@rm -f device/$(OPENOLTDEVICE)/$(OPENOLTDEVICE)_$(BAL_VER)+edgecore-V$(DEV_VER)_amd64.changes
+	@rm -f device/$(OPENOLTDEVICE)/mkdebian/debian/$(RXTX_POWER_EXE)
 
 inband-onl-cleanup:
 	@rm -f $(ONL_DIR)/OpenNetworkLinux/*.patch
@@ -391,6 +399,10 @@
 clean-src: protos-clean
 	@rm -f $(OBJS) $(DEPS)
 
+clean-rxtx-power:
+	@rm -f src/$(RXTX_POWER_EXE)
+	@rm -f $(RXTX_POWER_EXE)
+
 distclean: clean-src clean
 	@rm -rf $(BUILD_DIR)
 
diff --git a/agent/common/Queue.h b/agent/common/Queue.h
index 2e366ce..8e90a75 100644
--- a/agent/common/Queue.h
+++ b/agent/common/Queue.h
@@ -17,6 +17,44 @@
 class Queue
 {
  public:
+  /**
+   * @brief      pop with timeout
+   * @details    This pop() checks the queue within the given timeout duration at each given interval.
+   *             Use of the out parameter is for exception safety reasons.
+   *             The timeout duration is decreased at each check interval, so that it may be <= 0.
+   * <a href="https://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html">Implementing a Thread-Safe Queue using Condition Variables (Updated)</a>
+   * @param[out] value              pop queue.front()
+   * @param[in]  timeout_duration   time out after this duration
+   * @param[in]  check_interval     check at each this interval
+   * @return     [true] if pop happens within the timeout duration, [false] otherwise
+   */
+  bool pop(T& value,
+    std::chrono::milliseconds timeout_duration,
+    const std::chrono::milliseconds& check_interval=std::chrono::milliseconds(10)) {
+    std::unique_lock<std::mutex> lock(mutex_);
+
+    while (queue_.empty()) {
+      if (cond_.wait_for(lock, check_interval) == std::cv_status::timeout) {
+        timeout_duration -= check_interval;
+        if (timeout_duration <= std::chrono::milliseconds::zero() ) {
+          return false;
+        }
+      }
+    }
+
+    value = queue_.front();
+    queue_.pop();
+    return true;
+  }
+
+  /**
+   * @brief      returns the number of elements
+   * @return     Returns the number of elements in the underlying container.
+   */
+  std::size_t size() {
+    std::unique_lock<std::mutex> lock(mutex_);
+    return queue_.size();
+  }
 
   // timeout is in milliseconds, wait_granularity in milliseconds
   std::pair<T, bool> pop(int timeout, int wait_granularity=10)
@@ -66,7 +104,7 @@
   Queue()=default;
   Queue(const Queue&) = delete;            // disable copying
   Queue& operator=(const Queue&) = delete; // disable assignment
-  
+
  private:
   std::queue<T> queue_;
   std::mutex mutex_;
diff --git a/agent/common/core.h b/agent/common/core.h
index b3a5408..09ff890 100644
--- a/agent/common/core.h
+++ b/agent/common/core.h
@@ -243,6 +243,7 @@
 Status GetLogicalOnuDistance_(uint32_t intf_id, uint32_t onu_id, openolt::OnuLogicalDistance* response);
 Status GetOnuStatistics_(uint32_t intf_id, uint32_t onu_id, openolt::OnuStatistics *onu_stats);
 Status GetGemPortStatistics_(uint32_t intf_id, uint32_t gemport_id, openolt::GemPortStatistics* gemport_stats);
+Status GetPonRxPower_(uint32_t intf_id, uint32_t onu_id, openolt::PonRxPowerData* response);
 int get_status_bcm_cli_quit(void);
 uint16_t get_dev_id(void);
 Status pushOltOperInd(uint32_t intf_id, const char *type, const char *state);
diff --git a/agent/common/server.cc b/agent/common/server.cc
index 68cce10..168df68 100644
--- a/agent/common/server.cc
+++ b/agent/common/server.cc
@@ -347,6 +347,16 @@
             request->gemport_id(),
             response);
     }
+
+    Status GetPonRxPower(
+            ServerContext* context,
+            const openolt::Onu* request,
+            openolt::PonRxPowerData* response) override {
+        return GetPonRxPower_(
+            request->intf_id(),
+            request->onu_id(),
+            response);
+    }
 };
 
 bool RunServer(int argc, char** argv) {
diff --git a/agent/device/asfvolt16/mkdebian/debian/asfvolt16.postinst b/agent/device/asfvolt16/mkdebian/debian/asfvolt16.postinst
index ff4b68a..59175c0 100644
--- a/agent/device/asfvolt16/mkdebian/debian/asfvolt16.postinst
+++ b/agent/device/asfvolt16/mkdebian/debian/asfvolt16.postinst
@@ -39,3 +39,6 @@
 ldconfig /broadcom
 # create directory for certificates
 mkdir -p /broadcom/keystore/
+# Rx optical power test app
+cp -r /tmp/read_rxtx_power /broadcom/
+chmod +x /broadcom/read_rxtx_power
diff --git a/agent/device/asfvolt16/mkdebian/debian/rules b/agent/device/asfvolt16/mkdebian/debian/rules
index c952f58..d3dedf0 100755
--- a/agent/device/asfvolt16/mkdebian/debian/rules
+++ b/agent/device/asfvolt16/mkdebian/debian/rules
@@ -24,7 +24,7 @@
 
 DEB_DH_INSTALL_SOURCEDIR = $(CURDIR)/debian/tmp
 
-override_dh_auto_install: 
+override_dh_auto_install:
 	mkdir -p $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/release_asfvolt16_V3.4.9.6.202012040101.tar.gz $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/libz.so.1 $(DEB_DH_INSTALL_SOURCEDIR)/tmp
@@ -34,6 +34,7 @@
 	cp -a $(CURDIR)/debian/init.d $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/logrotate.d $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/watchdog $(DEB_DH_INSTALL_SOURCEDIR)/tmp
+	cp -a $(CURDIR)/debian/read_rxtx_power $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 
 #override_dh_shlibdeps:
 #	dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info -l$(ONLP_LIB_PATH):$(OFDPA_LIB_PATH)
diff --git a/agent/device/asgvolt64/mkdebian/debian/asgvolt64.postinst b/agent/device/asgvolt64/mkdebian/debian/asgvolt64.postinst
index 46e2356..2c97375 100644
--- a/agent/device/asgvolt64/mkdebian/debian/asgvolt64.postinst
+++ b/agent/device/asgvolt64/mkdebian/debian/asgvolt64.postinst
@@ -39,3 +39,6 @@
 ldconfig /broadcom
 # create directory for certificates
 mkdir -p /broadcom/keystore/
+# Rx optical power test app
+cp -r /tmp/read_rxtx_power /broadcom/
+chmod +x /broadcom/read_rxtx_power
diff --git a/agent/device/asgvolt64/mkdebian/debian/rules b/agent/device/asgvolt64/mkdebian/debian/rules
index 772857a..b047c30 100755
--- a/agent/device/asgvolt64/mkdebian/debian/rules
+++ b/agent/device/asgvolt64/mkdebian/debian/rules
@@ -24,7 +24,7 @@
 
 DEB_DH_INSTALL_SOURCEDIR = $(CURDIR)/debian/tmp
 
-override_dh_auto_install: 
+override_dh_auto_install:
 	mkdir -p $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/release_asgvolt64_V3.4.9.6.202012040101.tar.gz $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/libz.so.1 $(DEB_DH_INSTALL_SOURCEDIR)/tmp
@@ -34,6 +34,7 @@
 	cp -a $(CURDIR)/debian/init.d $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/logrotate.d $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 	cp -a $(CURDIR)/debian/watchdog $(DEB_DH_INSTALL_SOURCEDIR)/tmp
+	cp -a $(CURDIR)/debian/read_rxtx_power $(DEB_DH_INSTALL_SOURCEDIR)/tmp
 
 #override_dh_shlibdeps:
 #	dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info -l$(ONLP_LIB_PATH):$(OFDPA_LIB_PATH)
diff --git a/agent/src/core_api_handler.cc b/agent/src/core_api_handler.cc
index 6252355..babd9ce 100644
--- a/agent/src/core_api_handler.cc
+++ b/agent/src/core_api_handler.cc
@@ -75,6 +75,7 @@
                                bcmolt_egress_qos_type qos_type, uint32_t priority, uint32_t gemport_id, uint32_t tech_profile_id);
 static bcmos_errno CreateDefaultSched(uint32_t intf_id, const std::string direction);
 static bcmos_errno CreateDefaultQueue(uint32_t intf_id, const std::string direction);
+static const std::chrono::milliseconds ONU_RSSI_COMPLETE_WAIT_TIMEOUT = std::chrono::seconds(10);
 
 inline const char *get_flow_acton_command(uint32_t command) {
     char actions[200] = { };
@@ -3298,3 +3299,69 @@
     OPENOLT_LOG(INFO, openolt_log_id, "retrieved GEMPORT statistics for PON ID = %d, GEMPORT ID = %d\n", (int)intf_id, (int)gemport_id);
     return Status::OK;
 }
+
+Status GetPonRxPower_(uint32_t intf_id, uint32_t onu_id, openolt::PonRxPowerData* response) {
+    bcmos_errno err = BCM_ERR_OK;
+
+    // check the PON intf id
+    if (intf_id >= MAX_SUPPORTED_PON) {
+        err = BCM_ERR_PARM;
+        OPENOLT_LOG(ERROR, openolt_log_id, "invalid pon intf_id - intf_id: %d, onu_id: %d\n",
+            intf_id, onu_id);
+        return bcm_to_grpc_err(err, "invalid pon intf_id");
+    }
+
+    bcmolt_onu_rssi_measurement onu_oper; /* declare main API struct */
+    bcmolt_onu_key onu_key; /**< Object key. */
+    onu_rssi_compltd_key key(intf_id, onu_id);
+    Queue<onu_rssi_complete_result> queue;
+
+    OPENOLT_LOG(INFO, openolt_log_id, "GetPonRxPower - intf_id %d, onu_id %d\n", intf_id, onu_id);
+
+    onu_key.onu_id = onu_id;
+    onu_key.pon_ni = intf_id;
+    /* Initialize the API struct. */
+    BCMOLT_OPER_INIT(&onu_oper, onu, rssi_measurement, onu_key);
+    err = bcmolt_oper_submit(dev_id, &onu_oper.hdr);
+    if (err == BCM_ERR_OK) {
+        // initialize map
+        bcmos_fastlock_lock(&onu_rssi_wait_lock);
+        onu_rssi_compltd_map.insert({key, &queue});
+        bcmos_fastlock_unlock(&onu_rssi_wait_lock, 0);
+    } else {
+        OPENOLT_LOG(ERROR, openolt_log_id, "failed to measure rssi rx power - intf_id: %d, onu_id: %d, err = %s (%d): %s\n",
+            intf_id, onu_id, bcmos_strerror(err), err, onu_oper.hdr.hdr.err_text);
+        return bcm_to_grpc_err(err, "failed to measure rssi rx power");
+    }
+
+    onu_rssi_complete_result completed{};
+    if (!queue.pop(completed, ONU_RSSI_COMPLETE_WAIT_TIMEOUT)) {
+        // invalidate the queue pointer
+        bcmos_fastlock_lock(&onu_rssi_wait_lock);
+        onu_rssi_compltd_map[key] = NULL;
+        bcmos_fastlock_unlock(&onu_rssi_wait_lock, 0);
+        err = BCM_ERR_TIMEOUT;
+        OPENOLT_LOG(ERROR, openolt_log_id, "timeout waiting for RSSI Measurement Completed indication intf_id %d, onu_id %d\n",
+                    intf_id, onu_id);
+    } else {
+        OPENOLT_LOG(INFO, openolt_log_id, "RSSI Rx power - intf_id: %d, onu_id: %d, status: %s, fail_reason: %d, rx_power_mean_dbm: %f\n",
+            completed.pon_intf_id, completed.onu_id, completed.status.c_str(), completed.reason, completed.rx_power_mean_dbm);
+
+        response->set_intf_id(completed.pon_intf_id);
+        response->set_onu_id(completed.onu_id);
+        response->set_status(completed.status);
+        response->set_fail_reason(static_cast<::openolt::PonRxPowerData_RssiMeasurementFailReason>(completed.reason));
+        response->set_rx_power_mean_dbm(completed.rx_power_mean_dbm);
+    }
+
+    // Remove entry from map
+    bcmos_fastlock_lock(&onu_rssi_wait_lock);
+    onu_rssi_compltd_map.erase(key);
+    bcmos_fastlock_unlock(&onu_rssi_wait_lock, 0);
+
+    if (err == BCM_ERR_OK) {
+        return Status::OK;
+    } else {
+        return bcm_to_grpc_err(err, "timeout waiting for pon rssi measurement complete indication");
+    }
+}
diff --git a/agent/src/core_data.cc b/agent/src/core_data.cc
index ad66ae3..a53ca0e 100644
--- a/agent/src/core_data.cc
+++ b/agent/src/core_data.cc
@@ -206,3 +206,7 @@
 
 
 char* grpc_server_interface_name = NULL;
+
+// Read Rx optical power
+std::map<onu_rssi_compltd_key, Queue<onu_rssi_complete_result>*> onu_rssi_compltd_map;
+bcmos_fastlock onu_rssi_wait_lock;
diff --git a/agent/src/core_data.h b/agent/src/core_data.h
index 214b8fd..12edb9e 100644
--- a/agent/src/core_data.h
+++ b/agent/src/core_data.h
@@ -162,6 +162,17 @@
 
 } device_flow;
 
+// key for map used for tracking Onu RSSI Measurement Completed Indication
+typedef std::tuple<uint32_t, uint32_t> onu_rssi_compltd_key;
+
+typedef struct {
+    uint32_t pon_intf_id;
+    uint32_t onu_id;
+    std::string status;
+    bcmolt_rssi_measurement_fail_reason reason;
+    double rx_power_mean_dbm;
+} onu_rssi_complete_result;
+
 // *******************************************************//
 // Extern Variable/Constant declarations used by the core //
 // *******************************************************//
@@ -333,4 +344,7 @@
 // Interface name on which grpc server is running on
 // and this can be used to get the mac adress based on interface name.
 extern char* grpc_server_interface_name;
+
+extern std::map<onu_rssi_compltd_key, Queue<onu_rssi_complete_result>*> onu_rssi_compltd_map;
+extern bcmos_fastlock onu_rssi_wait_lock;
 #endif // OPENOLT_CORE_DATA_H_
diff --git a/agent/src/core_utils.cc b/agent/src/core_utils.cc
index 0153078..e8060aa 100644
--- a/agent/src/core_utils.cc
+++ b/agent/src/core_utils.cc
@@ -1723,3 +1723,25 @@
 
     return {buffer.str(), in_file.good()};
 }
+
+bool save_to_txt_file(const std::string& file_name, const std::string& content) {
+    std::ofstream out_file;
+    out_file.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+
+    try {
+        out_file.open(file_name, std::ios::out | std::ios::trunc);
+
+        if (!out_file.is_open()) {
+            std::cerr << "error while opening file '" << file_name << "'\n";
+            return false;
+        }
+
+        out_file << content;
+        out_file.close();
+
+        return true;
+    } catch (const std::ofstream::failure& e) {
+        std::cerr << "exception while writing to file '" << file_name << "' | err: " << e.what() << '\n';
+        return false;
+    }
+}
diff --git a/agent/src/core_utils.h b/agent/src/core_utils.h
index 3d4668c..c860644 100644
--- a/agent/src/core_utils.h
+++ b/agent/src/core_utils.h
@@ -122,4 +122,5 @@
 const std::string &get_grpc_tls_option();
 bool is_grpc_secure();
 std::pair<std::string, bool> read_from_txt_file(const std::string& file_name);
+bool save_to_txt_file(const std::string& file_name, const std::string& content);
 #endif // OPENOLT_CORE_UTILS_H_
diff --git a/agent/src/indications.cc b/agent/src/indications.cc
index 0583ddb..304cb45 100644
--- a/agent/src/indications.cc
+++ b/agent/src/indications.cc
@@ -21,6 +21,7 @@
 #include "stats_collection.h"
 #include "translation.h"
 #include "state.h"
+#include "trx_eeprom_reader.h"
 
 #include <string>
 
@@ -1055,6 +1056,66 @@
     bcmolt_msg_free(msg);
 }
 
+static void OnuRssiMeasurementCompletedIndication(bcmolt_devid olt, bcmolt_msg *msg) {
+    switch (msg->obj_type) {
+        case BCMOLT_OBJ_ID_ONU:
+            switch (msg->subgroup) {
+                case BCMOLT_ONU_AUTO_SUBGROUP_RSSI_MEASUREMENT_COMPLETED:
+                {
+                    bcmolt_onu_key *key = &((bcmolt_onu_rssi_measurement_completed*)msg)->key;
+                    bcmolt_onu_rssi_measurement_completed_data *data = &((bcmolt_onu_rssi_measurement_completed*)msg)->data;
+                    double rx_power_mean_dbm = 0.0;
+
+                    OPENOLT_LOG(INFO, openolt_log_id, "ONU RSSI Measurement Completed indication - pon_id: %d, onu_id: %d, status: %d, fail_reason: %d\n",
+                                key->pon_ni, key->onu_id, data->status, data->fail_reason);
+
+                    if (data->status == BCMOLT_RESULT_SUCCESS) {
+                        auto trx_eeprom_reader =
+#ifdef ASGVOLT64
+                            TrxEepromReader{TrxEepromReader::DEVICE_GPON, TrxEepromReader::RX_POWER, key->pon_ni};
+#else
+                            TrxEepromReader{TrxEepromReader::DEVICE_XGSPON, TrxEepromReader::RX_POWER, key->pon_ni};
+#endif
+                        auto power = trx_eeprom_reader.read_power_mean_dbm();
+
+                        if (power.second) {
+                            rx_power_mean_dbm = power.first.first;
+                            OPENOLT_LOG(INFO, openolt_log_id, "ONU RSSI Measurement Completed indication - rx_power_mean_dbm: %f\n", rx_power_mean_dbm);
+                        } else {
+                            OPENOLT_LOG(ERROR, openolt_log_id, "ONU RSSI Measurement Completed indication - Rx power read failure\n");
+                        }
+                    } else {
+                        OPENOLT_LOG(ERROR, openolt_log_id, "ONU RSSI Measurement Completed indication - failure");
+                    }
+
+                    // for internal use insert into map
+                    onu_rssi_compltd_key onu_key((uint32_t)key->pon_ni, (uint32_t)key->onu_id);
+                    onu_rssi_complete_result res;
+                    res.pon_intf_id = (uint32_t)key->pon_ni;
+                    res.onu_id = (uint32_t)key->onu_id;
+                    res.status = bcmolt_result_to_string(data->status);
+                    res.reason = data->fail_reason;
+                    res.rx_power_mean_dbm = rx_power_mean_dbm;
+
+                    bcmos_fastlock_lock(&onu_rssi_wait_lock);
+                    auto it = onu_rssi_compltd_map.find(onu_key);
+                    if (it == onu_rssi_compltd_map.end()) {
+                        OPENOLT_LOG(ERROR, openolt_log_id, "ONU RSSI Measurement Completed key not found for pon intf %u, onu_id %u\n",
+                            key->pon_ni, key->onu_id);
+                    } else if (it->second) {
+                        it->second->push(res);
+                    } else {
+                        OPENOLT_LOG(WARNING, openolt_log_id, "ONU RSSI Measurement Completed queue not found for pon intf %u, onu_id %u\n",
+                            key->pon_ni, key->onu_id);
+                    }
+                    bcmos_fastlock_unlock(&onu_rssi_wait_lock, 0);
+                }
+            }
+    }
+
+    bcmolt_msg_free(msg);
+}
+
 /* removed by BAL v3.0
 bcmos_errno OnuProcessingErrorIndication(bcmbal_obj *obj) {
     openolt::Indication ind;
@@ -1320,6 +1381,14 @@
     if(rc != BCM_ERR_OK)
         return Status(grpc::StatusCode::INTERNAL, "Complete members update indication subscribe failed");
 
+    rx_cfg.obj_type = BCMOLT_OBJ_ID_ONU;
+    rx_cfg.rx_cb = OnuRssiMeasurementCompletedIndication;
+    rx_cfg.flags = BCMOLT_AUTO_FLAGS_NONE;
+    rx_cfg.subgroup = bcmolt_onu_auto_subgroup_rssi_measurement_completed;
+    rc = bcmolt_ind_subscribe(current_device, &rx_cfg);
+    if(rc != BCM_ERR_OK)
+        return Status(grpc::StatusCode::INTERNAL, "ONU RSSI Measurement indication subscription failed");
+
     subscribed = true;
 
     return Status::OK;
diff --git a/agent/src/trx_eeprom_reader.cc b/agent/src/trx_eeprom_reader.cc
new file mode 100644
index 0000000..0c17a3d
--- /dev/null
+++ b/agent/src/trx_eeprom_reader.cc
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <cstdio>
+#include <cstdlib>
+#include <cmath>
+#include <iomanip>
+#include <stdexcept>
+
+#include "trx_eeprom_reader.h"
+
+// g++ -std=c++11 -DRXTX_POWER_EXE_MODE trx_eeprom_reader.cc -o rssi
+
+TrxEepromReader::TrxEepromReader(device_type dev_type, const power_type read_type, const int port)
+    : _read_type{read_type},
+      _port{port} {
+
+    if (dev_type == DEVICE_EITHER) {
+        std::string device = TrxEepromReader::get_board_name();
+        if (device == "ASGvOLT64") {
+            dev_type = TrxEepromReader::DEVICE_GPON;
+        } else if (device == "ASXvOLT16") {
+            dev_type = TrxEepromReader::DEVICE_XGSPON;
+        } else { // improbable
+            std::cerr << "ERROR - unknown device: '" << device << "'\n";
+            dev_type = TrxEepromReader::DEVICE_XGSPON;
+        }
+    }
+
+    switch (dev_type) {
+        case DEVICE_GPON:
+            _dev_name = "ASGvOLT64";
+            _buf_size = 600;
+            _read_offset = 360,
+            _read_num_bytes = 2;
+            _port_addr = 50;
+            _name_eeprom = "eeprom";
+            _bus_index = _gpon_bus_index;
+            _max_ports = sizeof(_gpon_bus_index) / sizeof(_gpon_bus_index[0]);
+            break;
+
+        case DEVICE_XGSPON:
+            _dev_name = "ASXvOLT16";
+            _buf_size = 256;
+            _read_offset = 104,
+            _read_num_bytes = 2;
+            _port_addr = 50;
+            _name_eeprom = "sfp_eeprom";
+            _bus_index = _xgspon_bus_index;
+            _max_ports = sizeof(_xgspon_bus_index) / sizeof(_xgspon_bus_index[0]);
+            break;
+    }
+
+    // trick: convert the first string specifier to integer specifier
+    sprintf(_node_format, _master_format, "%d", _port_addr, _name_eeprom.c_str());
+}
+
+std::pair<std::string, bool>  TrxEepromReader::read_txt_file(const std::string& file_name) {
+    std::ifstream in_file(file_name);
+
+    if (!in_file.is_open()) {
+        std::cerr << "error while opening file '" << file_name << "'\n";
+        return {"", false};
+    }
+
+    std::stringstream buffer;
+    buffer << in_file.rdbuf();
+
+    return {buffer.str(), in_file.good()};
+}
+
+std::string TrxEepromReader::get_board_name() {
+    const std::string board_path = "/sys/devices/virtual/dmi/id/board_name";
+    auto res = TrxEepromReader::read_txt_file(board_path);
+
+    // read failure is improbable
+    if (!res.second) {
+        std::cerr << "ERROR - file " << board_path << "cannot be opened\n";
+    }
+
+    if (res.first.find_last_of("\n") != std::string::npos) {
+        res.first.pop_back();
+    }
+
+    return res.first;
+}
+
+int TrxEepromReader::read_binary_file(char* buffer) {
+    std::ifstream is;
+    int len;
+
+    if (buffer == NULL || _buf_size < 0) {
+        return -1;
+    }
+
+#ifdef TEST_MODE
+    is.open("./eeprom.bin", std::ios_base::in | std::ios_base::binary);
+#else
+    is.open(_node_path, std::ios_base::in | std::ios_base::binary);
+#endif
+
+    if (!is.is_open()) {
+        return -2;
+    }
+
+    is.read(buffer, _buf_size);
+    len = is.gcount();
+    is.close();
+
+    if (len > _buf_size || len < (_read_offset + _read_num_bytes)) {
+        return -4;
+    }
+
+    return len;
+}
+
+bool TrxEepromReader::is_valid_port() const {
+    return (_port >= 0 && (size_t)_port < _max_ports);
+}
+
+void TrxEepromReader::set_port_path() {
+    sprintf(_node_path, _node_format, _bus_index[_port]);
+}
+
+unsigned long TrxEepromReader::get_value_from_pointer_u(unsigned char *ptr, int size) {
+    unsigned long sum = 0;
+    unsigned char i;
+
+    if (size > 4) {
+        return (sum);
+    }
+
+    for (i = 0; i < size; ++i) {
+        sum = sum * 256 + (*(ptr + i));
+    }
+
+    return (sum);
+}
+
+double TrxEepromReader::raw_rx_to_mw(int raw) {
+    return raw * 0.0001;
+}
+
+double TrxEepromReader::raw_tx_to_mw(int raw) {
+    return raw * 0.0002;
+}
+
+double TrxEepromReader::mw_to_dbm(double mw) {
+    return 10 * log10(mw);
+}
+
+std::pair<std::pair<int, int>, bool> TrxEepromReader::read_power_raw() {
+    if (is_valid_port()) {
+        set_port_path();
+
+        char* eeprom_data = new (std::nothrow) char[_buf_size];
+
+        if (!eeprom_data) {
+            std::cerr << "ERROR - memory allocation for eeprom_data failed\n";
+            return {{0, 0}, false};
+        }
+
+        int ret_file = read_binary_file(eeprom_data);
+
+        if (ret_file < 0) {
+            std::cerr << "ERROR - eeprom_data file cannot be opened\n";
+            return {{0, 0}, false};
+        }
+
+        // le = Little Endian, be = Big Endian
+        unsigned char rx_power_le[_read_num_bytes] = {0};
+        unsigned char tx_power_le[_read_num_bytes] = {0};
+        unsigned long rx_power_be = 0;
+        unsigned long tx_power_be = 0;
+
+        if (_read_type == RX_POWER || _read_type == RX_AND_TX_POWER) {
+            memcpy(&rx_power_le, eeprom_data + _read_offset, _read_num_bytes);
+            rx_power_be = get_value_from_pointer_u(rx_power_le, _read_num_bytes);
+        }
+
+        if (_read_type == TX_POWER || _read_type == RX_AND_TX_POWER) {
+            memcpy(&tx_power_le, eeprom_data + _read_offset - _read_num_bytes, _read_num_bytes);
+            tx_power_be = get_value_from_pointer_u(tx_power_le, _read_num_bytes);
+        }
+
+        if (_read_type == RX_POWER) {
+            tx_power_be = 0;
+        }
+
+        if (_read_type == TX_POWER) {
+            rx_power_be = 0;
+        }
+
+        delete[] eeprom_data;
+
+        return {{(int)rx_power_be, (int)tx_power_be}, true};
+    } else {
+        std::cerr << "ERROR - invalid port: " << _port << '\n';
+        return {{0, 0}, false};
+    }
+}
+
+std::pair<std::pair<double, double>, bool> TrxEepromReader::read_power_mean_dbm() {
+    auto power_raw = read_power_raw();
+    if (power_raw.second) {
+        return {{mw_to_dbm(raw_rx_to_mw(power_raw.first.first)), mw_to_dbm(raw_tx_to_mw(power_raw.first.second))}, true};
+    } else {
+        return {{0.0, 0.0}, false};
+    }
+}
+
+std::string TrxEepromReader::dump_data() {
+    std::ostringstream dump;
+
+    dump << "\tdevice:       " << _dev_name << '\n'
+         << "\tbuffer size:  " << _buf_size << '\n'
+         << "\tread offset:  " << _read_offset << '\n'
+         << "\tnum bytes:    " << _read_num_bytes << '\n'
+         << "\tmax ports:    " << _max_ports << '\n'
+         << "\tport:         " << _port << '\n';
+
+    //dump << std::showpos;
+    dump << std::fixed;
+    dump << std::setprecision(3);
+
+    auto rxtx_power_raw = this->read_power_raw();
+
+    dump << "\tnode path:    " << _node_path << '\n';
+
+    if (rxtx_power_raw.second) {
+        if (_read_type == RX_POWER || _read_type == RX_AND_TX_POWER) {
+            dump << "\tRx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.first
+                 << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.first
+                 << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << raw_rx_to_mw(rxtx_power_raw.first.first) << " mW, "
+                 << std::dec << std::setw(9) << mw_to_dbm(raw_rx_to_mw(rxtx_power_raw.first.first)) << " dBm\n";
+        }
+        if (_read_type == TX_POWER || _read_type == RX_AND_TX_POWER) {
+            dump << "\tTx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.second
+                 << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.second
+                 << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << raw_tx_to_mw(rxtx_power_raw.first.second) << " mW, "
+                 << std::dec << std::setw(9) << mw_to_dbm(raw_tx_to_mw(rxtx_power_raw.first.second)) << " dBm\n";
+        }
+    }
+
+    return dump.str();
+}
+
+int TrxEepromReader::get_buf_size() const {
+    return _buf_size;
+}
+
+int TrxEepromReader::get_read_offset() const {
+    return _read_offset;
+}
+
+int TrxEepromReader::get_read_num_bytes() const {
+    return _read_num_bytes;
+}
+
+int TrxEepromReader::get_max_ports() const {
+    return _max_ports;
+}
+
+const char* TrxEepromReader::get_node_path() const {
+    return _node_path;
+}
+
+#ifdef RXTX_POWER_EXE_MODE
+int main(int argc, char *argv[]) {
+    int port = 0;
+
+    if (argc > 1) {
+        std::string help{argv[1]};
+
+        // trim leading '-'s
+        do {
+            if (help.find_first_of("-") == std::string::npos) {
+                break;
+            } else {
+                help.erase(0, 1);
+            }
+        } while (true);
+
+        if (help == "h" || help == "help") {
+            std::cout << "usage:\n\t" << argv[0] << " " << port << '\n';
+            std::cout << "\t#1 port no\n";
+            return 0;
+        }
+
+        try {
+            port = std::stoi(argv[1]);
+        } catch(std::invalid_argument eia) {
+            std::cerr << "ERROR - invalid argument exception: '" << argv[1] << "'\n";
+            return 1;
+        } catch(std::out_of_range eoor) {
+            std::cerr << "ERROR - out of range exception: '" << argv[1] << "'\n";
+            return 1;
+        }
+    }
+
+    TrxEepromReader trx_eeprom_reader{TrxEepromReader::DEVICE_EITHER, TrxEepromReader::RX_AND_TX_POWER, port};
+
+    std::cout << trx_eeprom_reader.dump_data() << '\n';
+
+    return 0;
+}
+#endif
diff --git a/agent/src/trx_eeprom_reader.h b/agent/src/trx_eeprom_reader.h
new file mode 100644
index 0000000..25bfe99
--- /dev/null
+++ b/agent/src/trx_eeprom_reader.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TRX_EEPROM_READER_H
+#define TRX_EEPROM_READER_H
+
+/**
+ * RSSI - Received Signal Strength Indication
+ * see:
+ *      https://en.wikipedia.org/wiki/Received_signal_strength_indication
+ *
+ * per EC Ticket#7825
+ * see:
+ *      https://support.edge-core.com/hc/en-us/requests/7825
+ */
+
+class TrxEepromReader {
+    public:
+        enum device_type {
+            DEVICE_EITHER,
+            DEVICE_GPON,
+            DEVICE_XGSPON
+        };
+
+        enum power_type {
+            RX_POWER,
+            TX_POWER,
+            RX_AND_TX_POWER
+        };
+
+        TrxEepromReader(device_type dev_type, const power_type read_type, const int port);
+        TrxEepromReader() = delete;
+
+        static std::pair<std::string, bool> read_txt_file(const std::string& file_name);
+        static std::string get_board_name();
+
+        int read_binary_file(char* buffer);
+        bool is_valid_port() const;
+        void set_port_path();
+        unsigned long get_value_from_pointer_u(unsigned char *ptr, int size);
+        double raw_rx_to_mw(int raw);
+        double raw_tx_to_mw(int raw);
+        double mw_to_dbm(double mw);
+        std::pair<std::pair<int, int>, bool> read_power_raw();
+        std::pair<std::pair<double, double>, bool> read_power_mean_dbm();
+        std::string dump_data();
+        int get_buf_size() const;
+        int get_read_offset() const;
+        int get_read_num_bytes() const;
+        int get_max_ports() const;
+        const char* get_node_path() const;
+
+    private:
+        int _port;
+        power_type _read_type;
+        std::string _dev_name;
+        int _buf_size;
+        int _read_offset;
+        int _read_num_bytes;
+        int _port_addr;
+        std::string _name_eeprom;
+        const int* _bus_index;
+        size_t _max_ports;
+
+        char _node_format[64] = {0};
+        char _node_path[64] = {0};
+
+        const char* _master_format = "/sys/bus/i2c/devices/%s-00%d/%s";
+        const int _gpon_bus_index[74] = {
+            41,  42,  56,  55,  43,  44,  54,  53,
+            45,  46,  52,  51,  47,  48,  50,  49,
+            57,  58,  72,  71,  59,  60,  70,  69,
+            61,  62,  68,  67,  63,  64,  66,  65,
+            73,  74,  88,  87,  75,  76,  86,  85,
+            77,  78,  84,  83,  79,  80,  82,  81,
+            89,  90, 104, 103,  91,  92, 102, 101,
+            93,  94, 100,  99,  95,  96,  98,  97,
+            20,  21,  25,  26,  27,  28,  29,  30,
+            31,  32
+        };
+        const int _xgspon_bus_index[20] = {
+            47,  48,  37,  38,  35,  36,  33,  34,
+            39,  40,  41,  42,  43,  44,  45,  46,
+            49,  50,  51,  52
+        };
+};
+
+#endif
diff --git a/agent/test/Makefile b/agent/test/Makefile
index cb70c69..d103d57 100644
--- a/agent/test/Makefile
+++ b/agent/test/Makefile
@@ -21,7 +21,7 @@
 TOP_DIR=`pwd`
 OPENOLTDEVICE ?= asfvolt16
 
-OPENOLT_PROTO_VER ?= v4.0.15
+OPENOLT_PROTO_VER ?= v4.1.5
 
 ########################################################################
 ##
diff --git a/agent/test/src/test_core.cc b/agent/test/src/test_core.cc
index 55297b9..650b497 100644
--- a/agent/test/src/test_core.cc
+++ b/agent/test/src/test_core.cc
@@ -20,6 +20,8 @@
 #include "core_data.h"
 #include "server.h"
 #include <future>
+#include <fstream>
+#include "trx_eeprom_reader.h"
 using namespace testing;
 using namespace std;
 
@@ -3324,3 +3326,137 @@
         FAIL();
     }
 }
+
+////////////////////////////////////////////////////////////////////////////
+// For testing RxTx Power Read functionality
+////////////////////////////////////////////////////////////////////////////
+
+class TestPowerRead : public Test {
+    protected:
+        virtual void SetUp() {
+            std::array<char, 600> content = {};
+            content.fill(0x00); // not required for 0x00
+
+            // for asfvolt16
+            content[102] = 0x5D;
+            content[103] = 0x38;
+            content[104] = 0x1A;
+            content[105] = 0xB5;
+
+            // for asgvolt64
+            content[358] = 0x5C;
+            content[359] = 0x82;
+            content[360] = 0x04;
+            content[361] = 0xBE;
+
+            std::ofstream test_file;
+            test_file.open(file_name, std::ios::binary | std::ios::out);
+            test_file.write(content.data(), content.size());
+            test_file.close();
+
+            const std::string cmd = "exec hexdump -C " + file_name + " > " + hex_dump;
+
+            int res = std::system(cmd.c_str());
+            if (res == 0) {
+                std::ifstream dump_file(hex_dump) ;
+                std::string hexdump = { std::istreambuf_iterator<char>(dump_file), std::istreambuf_iterator<char>() };
+                std::cout << cmd << '\n';
+                std::cout << hexdump << '\n';
+            } else {
+                std::cerr << "hexdump capture failed\n";
+            }
+        }
+        virtual void TearDown() {
+            std::remove(file_name.c_str());
+            std::remove(hex_dump.c_str());
+        }
+
+        const std::string file_name = "eeprom.bin";
+        const std::string hex_dump = file_name + ".hexdump";
+};
+
+TEST_F(TestPowerRead, TestAsfvolt16) {
+    std::cout << "Test Power Reads on XGS-PON OLT:\n";
+
+    int port = 20;
+    auto trx_eeprom_reader1 = TrxEepromReader{TrxEepromReader::DEVICE_XGSPON, TrxEepromReader::RX_AND_TX_POWER, port};
+
+    std::cout << "\tis port #" << port << " valid? " << std::boolalpha << trx_eeprom_reader1.is_valid_port() << '\n';
+
+    ASSERT_FALSE(trx_eeprom_reader1.is_valid_port());
+
+    port = 0;
+    auto trx_eeprom_reader2 = TrxEepromReader{TrxEepromReader::DEVICE_XGSPON, TrxEepromReader::RX_AND_TX_POWER, port};
+
+    std::cout << "\tis port #" << port << " valid? " << trx_eeprom_reader2.is_valid_port() << '\n';
+    std::cout << "\tbuffer size: " << trx_eeprom_reader2.get_buf_size() << '\n';
+    std::cout << "\tread offset: " << trx_eeprom_reader2.get_read_offset() << '\n';
+    std::cout << "\tread num bytes: " << trx_eeprom_reader2.get_read_num_bytes() << '\n';
+    std::cout << "\tmax ports: " << trx_eeprom_reader2.get_max_ports() << '\n';
+
+    auto rxtx_power_raw = trx_eeprom_reader2.read_power_raw();
+
+    ASSERT_TRUE(rxtx_power_raw.second);
+
+    std::cout << "\tRx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.first
+              << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.first
+              << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << trx_eeprom_reader2.raw_rx_to_mw(rxtx_power_raw.first.first) << " mW, "
+              << std::dec << std::setw(9) << trx_eeprom_reader2.mw_to_dbm(trx_eeprom_reader2.raw_rx_to_mw(rxtx_power_raw.first.first)) << " dBm\n";
+    std::cout << "\tTx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.second
+              << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.second
+              << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << trx_eeprom_reader2.raw_tx_to_mw(rxtx_power_raw.first.second) << " mW, "
+              << std::dec << std::setw(9) << trx_eeprom_reader2.mw_to_dbm(trx_eeprom_reader2.raw_tx_to_mw(rxtx_power_raw.first.second)) << " dBm\n";
+    std::cout << "\tnode path: " << trx_eeprom_reader2.get_node_path() << '\n';
+
+    ASSERT_TRUE(trx_eeprom_reader2.is_valid_port());
+    ASSERT_EQ(trx_eeprom_reader2.get_buf_size(), 256);
+    ASSERT_EQ(trx_eeprom_reader2.get_read_offset(), 104);
+    ASSERT_EQ(trx_eeprom_reader2.get_read_num_bytes(), 2);
+    ASSERT_EQ(trx_eeprom_reader2.get_max_ports(), 20);
+    ASSERT_EQ(rxtx_power_raw.first.first, 0x1AB5);   // 6837
+    ASSERT_EQ(rxtx_power_raw.first.second, 0x5D38);  // 23864
+    ASSERT_STREQ(trx_eeprom_reader2.get_node_path(), "/sys/bus/i2c/devices/47-0050/sfp_eeprom");
+}
+
+TEST_F(TestPowerRead, TestAsgvolt64) {
+    std::cout << "Test Power Reads on GPON OLT:\n";
+
+    int port = 80;
+    auto trx_eeprom_reader1 = TrxEepromReader{TrxEepromReader::DEVICE_GPON, TrxEepromReader::RX_AND_TX_POWER, port};
+
+    std::cout << "\tis port #" << port << " valid? " << std::boolalpha << trx_eeprom_reader1.is_valid_port() << '\n';
+
+    ASSERT_FALSE(trx_eeprom_reader1.is_valid_port());
+
+    port = 0;
+    auto trx_eeprom_reader2 = TrxEepromReader{TrxEepromReader::DEVICE_GPON, TrxEepromReader::RX_AND_TX_POWER, port};
+
+    std::cout << "\tis port #" << port << " valid? " << trx_eeprom_reader2.is_valid_port() << '\n';
+    std::cout << "\tbuffer size: " << trx_eeprom_reader2.get_buf_size() << '\n';
+    std::cout << "\tread offset: " << trx_eeprom_reader2.get_read_offset() << '\n';
+    std::cout << "\tread num bytes: " << trx_eeprom_reader2.get_read_num_bytes() << '\n';
+    std::cout << "\tmax ports: " << trx_eeprom_reader2.get_max_ports() << '\n';
+
+    auto rxtx_power_raw = trx_eeprom_reader2.read_power_raw();
+
+    ASSERT_TRUE(rxtx_power_raw.second);
+
+    std::cout << "\tRx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.first
+              << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.first
+              << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << trx_eeprom_reader2.raw_rx_to_mw(rxtx_power_raw.first.first) << " mW, "
+              << std::dec << std::setw(9) << trx_eeprom_reader2.mw_to_dbm(trx_eeprom_reader2.raw_rx_to_mw(rxtx_power_raw.first.first)) << " dBm\n";
+    std::cout << "\tTx power - raw: (hex) " << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << rxtx_power_raw.first.second
+              << ", (dec) " << std::dec << std::setfill(' ') << std::setw(5) << rxtx_power_raw.first.second
+              << "  | " << std::dec << std::fixed << std::setprecision(5) << std::setw(8) << trx_eeprom_reader2.raw_tx_to_mw(rxtx_power_raw.first.second) << " mW, "
+              << std::dec << std::setw(9) << trx_eeprom_reader2.mw_to_dbm(trx_eeprom_reader2.raw_tx_to_mw(rxtx_power_raw.first.second)) << " dBm\n";
+    std::cout << "\tnode path: " << trx_eeprom_reader2.get_node_path() << '\n';
+
+    ASSERT_TRUE(trx_eeprom_reader2.is_valid_port());
+    ASSERT_EQ(trx_eeprom_reader2.get_buf_size(), 600);
+    ASSERT_EQ(trx_eeprom_reader2.get_read_offset(), 360);
+    ASSERT_EQ(trx_eeprom_reader2.get_read_num_bytes(), 2);
+    ASSERT_EQ(trx_eeprom_reader2.get_max_ports(), 74);
+    ASSERT_EQ(rxtx_power_raw.first.first, 0x04BE);   // 1214
+    ASSERT_EQ(rxtx_power_raw.first.second, 0x5C82);  // 23682
+    ASSERT_STREQ(trx_eeprom_reader2.get_node_path(), "/sys/bus/i2c/devices/41-0050/eeprom");
+}
diff --git a/protos/Makefile b/protos/Makefile
index 84d0b0b..96300b6 100644
--- a/protos/Makefile
+++ b/protos/Makefile
@@ -19,7 +19,7 @@
 # This specifies the GIT tag in https://github.com/opencord/voltha-protos
 # repo that we need to refer to, to pick the right version of
 # openolt.proto, ext_config.proto and tech_profile.proto
-OPENOLT_PROTO_VER ?= v4.0.15
+OPENOLT_PROTO_VER ?= v4.1.5
 
 CXX ?= g++
 CPPFLAGS += `pkg-config --cflags protobuf grpc` -I googleapis/gens -I./