Unigraf: add UCD event capture API

Implement the start and stop capture APIs in passport.

BUG=b:466062899
TEST=Start event capture & run compliance & view capture

Change-Id: Ia320b212b9e0b096252b8b83759f7266dd06d4fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/passport/+/7224052
Tested-by: George-Daniel Matei <danielgeorgem@google.com>
Auto-Submit: George-Daniel Matei <danielgeorgem@google.com>
Commit-Queue: George-Daniel Matei <danielgeorgem@google.com>
Reviewed-by: Mark Yacoub <markyacoub@google.com>
Reviewed-by: Jason Stanko <jstanko@google.com>
diff --git a/go/src/server/video_tester_service.go b/go/src/server/video_tester_service.go
index 369e22a..e81b286 100644
--- a/go/src/server/video_tester_service.go
+++ b/go/src/server/video_tester_service.go
@@ -372,3 +372,39 @@
 	// Call the AttachVideoTester method of the found plugin.
 	return tester.RunComplianceTest(ctx, req)
 }
+
+// Runs StartEventCapture.
+func (s *videoTesterServiceServer) StartEventCapture(
+	ctx context.Context,
+	req *passport.StartEventCaptureRequest,
+) (*passport.StartEventCaptureResponse, error) {
+
+	if tester, ok := s.testerMap[req.Id]; ok {
+		// Call the StartEventCapture method of the found plugin.
+		return tester.StartEventCapture(ctx, req)
+	}
+
+	// If no plugin is found for the given ID, return a NotFound error.
+	return nil, status.Errorf(
+		codes.NotFound,
+		fmt.Sprintf("there is no tester with id: %s", req.Id),
+	)
+}
+
+// Runs StopEventCapture.
+func (s *videoTesterServiceServer) StopEventCapture(
+	ctx context.Context,
+	req *passport.StopEventCaptureRequest,
+) (*passport.StopEventCaptureResponse, error) {
+
+	if tester, ok := s.testerMap[req.Id]; ok {
+		// Call the StartEventCapture method of the found plugin.
+		return tester.StopEventCapture(ctx, req)
+	}
+
+	// If no plugin is found for the given ID, return a NotFound error.
+	return nil, status.Errorf(
+		codes.NotFound,
+		fmt.Sprintf("there is no tester with id: %s", req.Id),
+	)
+}
diff --git a/go/src/unigraf/ucd422/video_tester_plugin.go b/go/src/unigraf/ucd422/video_tester_plugin.go
index 7930595..a083e2f 100644
--- a/go/src/unigraf/ucd422/video_tester_plugin.go
+++ b/go/src/unigraf/ucd422/video_tester_plugin.go
@@ -219,3 +219,21 @@
 	// Call the AttachVideoTester method of the found plugin.
 	return s.unigraf_control_client.RunComplianceTest(ctx, req)
 }
+
+// Runs compliance test(s).
+func (s *videoTesterPlugin) StartEventCapture(
+	ctx context.Context,
+	req *passport.StartEventCaptureRequest,
+) (*passport.StartEventCaptureResponse, error) {
+	// Call the AttachVideoTester method of the found plugin.
+	return s.unigraf_control_client.StartEventCapture(ctx, req)
+}
+
+// Runs compliance test(s).
+func (s *videoTesterPlugin) StopEventCapture(
+	ctx context.Context,
+	req *passport.StopEventCaptureRequest,
+) (*passport.StopEventCaptureResponse, error) {
+	// Call the AttachVideoTester method of the found plugin.
+	return s.unigraf_control_client.StopEventCapture(ctx, req)
+}
diff --git a/go/src/unigraf/ucd500/video_tester_plugin.go b/go/src/unigraf/ucd500/video_tester_plugin.go
index 649b096..0074701 100644
--- a/go/src/unigraf/ucd500/video_tester_plugin.go
+++ b/go/src/unigraf/ucd500/video_tester_plugin.go
@@ -219,3 +219,21 @@
 	// Call the AttachVideoTester method of the found plugin.
 	return s.unigraf_control_client.RunComplianceTest(ctx, req)
 }
+
+// Runs StartEventCapture.
+func (s *videoTesterPlugin) StartEventCapture(
+	ctx context.Context,
+	req *passport.StartEventCaptureRequest,
+) (*passport.StartEventCaptureResponse, error) {
+	// Call the StartEventCapture method of the found plugin.
+	return s.unigraf_control_client.StartEventCapture(ctx, req)
+}
+
+// Runs StopEventCapture.
+func (s *videoTesterPlugin) StopEventCapture(
+	ctx context.Context,
+	req *passport.StopEventCaptureRequest,
+) (*passport.StopEventCaptureResponse, error) {
+	// Call the StopEventCapture method of the found plugin.
+	return s.unigraf_control_client.StopEventCapture(ctx, req)
+}
diff --git a/python/unigraf/ucd/server.py b/python/unigraf/ucd/server.py
index 039f01d..7442065 100644
--- a/python/unigraf/ucd/server.py
+++ b/python/unigraf/ucd/server.py
@@ -92,6 +92,14 @@
         """Simulates attaching/detaching."""
         raise NotImplementedError("Method not implemented!")
 
+    def StartEventCapture(self, request, context):
+        """Start the event capture with the specified filters"""
+        raise NotImplementedError("Method not implemented!")
+
+    def StopEventCapture(self, request, context):
+        """Stop the event capture and optionally get the capture files."""
+        raise NotImplementedError("Method not implemented!")
+
     def _get_number_of_video_streams(self):
         raise RuntimeError("Method is not implemented!")
 
diff --git a/python/unigraf/ucd/ucd422.py b/python/unigraf/ucd/ucd422.py
index 731ef1f..fe03b5d 100644
--- a/python/unigraf/ucd/ucd422.py
+++ b/python/unigraf/ucd/ucd422.py
@@ -141,6 +141,21 @@
         return video_pb2.HpdPulseVideoTesterResponse()
 
     @log_functionality.logger
+    def StartEventCapture(self, request, context):
+        """Start the event capture with the specified filters"""
+
+        logging.info("NoOp StartEventCapture")
+
+        return video_pb2.StartEventCaptureResponse()
+
+    @log_functionality.logger
+    def StopEventCapture(self, request, context):
+        """Stop the event capture and optionally get the capture files."""
+
+        logging.info("NoOp StopEventCapture")
+        return video_pb2.StopEventCaptureResponse()
+
+    @log_functionality.logger
     def _get_number_of_video_streams(self):
         if self._port_rx.link.status.hpd_status:
             return 1
diff --git a/python/unigraf/ucd/ucd500.py b/python/unigraf/ucd/ucd500.py
index 7c31aa9..96b8a29 100644
--- a/python/unigraf/ucd/ucd500.py
+++ b/python/unigraf/ucd/ucd500.py
@@ -10,6 +10,7 @@
 """
 
 import logging
+import tempfile
 import time
 
 # pylint: disable=import-error
@@ -169,3 +170,81 @@
         caps = self._port_rx.link.capabilities.link_caps_status()
 
         return caps.mst_sink_count if caps.mst else 1
+
+    @log_functionality.logger
+    def StartEventCapture(self, request, context):
+        """Start the event capture with the specified filters"""
+
+        event_config_tx = self._port_tx.event_capturer.event_filter(
+            UniTAP.EventFilterDpTx
+        )
+        event_config_rx = self._port_rx.event_capturer.event_filter(
+            UniTAP.EventFilterDpRx
+        )
+
+        event_config_rx.config_hpd_events(request.dprx_config.hpd_events)
+        event_config_rx.config_aux_events(request.dprx_config.aux_events)
+        event_config_rx.config_sdp_events(request.dprx_config.sdp_events)
+        event_config_rx.config_link_pattern_events(
+            request.dprx_config.link_pattern_events
+        )
+        event_config_rx.config_vb_id_events(request.dprx_config.vb_id_events)
+        event_config_rx.config_msa_events(request.dprx_config.msa_events)
+        event_config_rx.config_aux_bw_events(request.dprx_config.aux_bw_events)
+
+        event_config_tx.config_hpd_events(request.dptx_config.hpd_events)
+        event_config_tx.config_aux_events(request.dptx_config.aux_events)
+
+        # Stop any capture in case they are still running.
+        self._port_tx.event_capturer.stop()
+        self._port_rx.event_capturer.stop()
+
+        self._port_tx.event_capturer.configure_capturer(event_config_tx)
+        self._port_rx.event_capturer.configure_capturer(event_config_rx)
+
+        self._port_tx.event_capturer.start()
+        self._port_rx.event_capturer.start()
+
+        return video_pb2.StartEventCaptureResponse()
+
+    def StopEventCapture(self, request, context):
+        """Stop the event capture and optionally get the capture files."""
+
+        logging.info("Running StopEventCapture")
+
+        self._port_tx.event_capturer.stop()
+        self._port_rx.event_capturer.stop()
+
+        capture_result_tx = (
+            self._port_tx.event_capturer.pop_all_elements_as_result_object()
+        )
+        capture_result_rx = (
+            self._port_rx.event_capturer.pop_all_elements_as_result_object()
+        )
+
+        logging.info(
+            f"StopEventCapture results are {len(capture_result_tx.buffer)} {len(capture_result_rx.buffer)}"
+        )
+
+        # Generate reports only if requested.
+        if not request.generate_reports:
+            return video_pb2.StopEventCaptureResponse()
+
+        # pylint: disable=R1732
+        tmp_tx = tempfile.NamedTemporaryFile(suffix=".html")
+        tmp_rx = tempfile.NamedTemporaryFile(suffix=".html")
+        # pylint: enable=R1732
+
+        capture_result_tx.save_to_file_all_events(
+            file_format=UniTAP.EventFileFormat.HTML, path=tmp_tx.name
+        )
+        capture_result_rx.save_to_file_all_events(
+            file_format=UniTAP.EventFileFormat.HTML, path=tmp_rx.name
+        )
+
+        with open(tmp_tx.name, "rb") as f_tx, open(tmp_rx.name, "rb") as f_rx:
+            logging.info("Files were found")
+            return video_pb2.StopEventCaptureResponse(
+                dptx_capture_html=f_tx.read(),
+                dprx_capture_html=f_rx.read(),
+            )