nanotool: add support for running sensor self-test

Bug: 28906610
Change-Id: I2c728f884db2215e981e5308eb51d90f0c7f206d
diff --git a/util/nanotool/apptohostevent.cpp b/util/nanotool/apptohostevent.cpp
index 9ab4fb3..b801475 100644
--- a/util/nanotool/apptohostevent.cpp
+++ b/util/nanotool/apptohostevent.cpp
@@ -47,11 +47,7 @@
               + sizeof(struct HostHubRawPacket));
 }
 
-bool AppToHostEvent::IsCalibrationEventForSensor(SensorType sensor_type) const {
-    if (GetDataLen() < sizeof(struct SensorAppEventHeader)) {
-        return false;
-    }
-
+bool AppToHostEvent::CheckAppId(SensorType sensor_type) const {
     // Make sure the app ID matches what we expect for the sensor type, bail out
     // early if it doesn't
     switch (sensor_type) {
@@ -85,12 +81,43 @@
         return false;
     }
 
+    return true;
+}
+
+bool AppToHostEvent::CheckEventHeader(SensorType sensor_type) const {
+    if (GetDataLen() < sizeof(struct SensorAppEventHeader)) {
+        return false;
+    }
+
+    if (!CheckAppId(sensor_type)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool AppToHostEvent::IsCalibrationEventForSensor(SensorType sensor_type) const {
+    if (!CheckEventHeader(sensor_type)) {
+        return false;
+    }
+
     // If we made it this far, we only need to confirm the message ID
     auto header = reinterpret_cast<const struct SensorAppEventHeader *>(
         GetDataPtr());
     return (header->msgId == SENSOR_APP_MSG_CALIBRATION_RESULT);
 }
 
+bool AppToHostEvent::IsTestEventForSensor(SensorType sensor_type) const {
+    if (!CheckEventHeader(sensor_type)) {
+        return false;
+    }
+
+    // If we made it this far, we only need to confirm the message ID
+    auto header = reinterpret_cast<const struct SensorAppEventHeader *>(
+        GetDataPtr());
+    return (header->msgId == SENSOR_APP_MSG_TEST_RESULT);
+}
+
 bool AppToHostEvent::IsValid() const {
     const HostHubRawPacket *packet = GetTypedData();
     if (!packet) {
diff --git a/util/nanotool/apptohostevent.h b/util/nanotool/apptohostevent.h
index 0a8101b..f4c6b45 100644
--- a/util/nanotool/apptohostevent.h
+++ b/util/nanotool/apptohostevent.h
@@ -92,13 +92,17 @@
     const uint8_t *GetDataPtr() const;
 
     bool IsCalibrationEventForSensor(SensorType sensor_type) const;
+    bool IsTestEventForSensor(SensorType sensor_type) const;
     virtual bool IsValid() const;
 
   protected:
     const HostHubRawPacket *GetTypedData() const;
+    bool CheckAppId(SensorType sensor_type) const;
+    bool CheckEventHeader(SensorType sensor_type) const;
 };
 
 #define SENSOR_APP_MSG_CALIBRATION_RESULT (0)
+#define SENSOR_APP_MSG_TEST_RESULT        (1)
 
 struct SensorAppEventHeader {
     uint8_t msgId;
diff --git a/util/nanotool/contexthub.cpp b/util/nanotool/contexthub.cpp
index 5beed5e..b4fe1c2 100644
--- a/util/nanotool/contexthub.cpp
+++ b/util/nanotool/contexthub.cpp
@@ -31,6 +31,7 @@
 #define UNUSED_PARAM(param) (void) (param)
 
 constexpr int kCalibrationTimeoutMs(10000);
+constexpr int kTestTimeoutMs(10000);
 constexpr int kBridgeVersionTimeoutMs(500);
 
 struct SensorTypeNames {
@@ -174,6 +175,14 @@
     return success;
 }
 
+bool ContextHub::TestSensors(const std::vector<SensorSpec>& sensors) {
+    bool success = ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool {
+        return TestSingleSensor(spec);
+    });
+
+    return success;
+}
+
 bool ContextHub::EnableSensor(const SensorSpec& spec) {
     ConfigureSensorRequest req;
 
@@ -382,6 +391,40 @@
     return success;
 }
 
+bool ContextHub::TestSingleSensor(const SensorSpec& sensor) {
+    ConfigureSensorRequest req;
+
+    req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor);
+    req.config.sensor_type = static_cast<uint8_t>(sensor.sensor_type);
+    req.config.command = static_cast<uint8_t>(
+        ConfigureSensorRequest::CommandType::SelfTest);
+
+    LOGI("Issuing test request to sensor %d (%s)", sensor.sensor_type,
+         ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str());
+    auto result = WriteEvent(req);
+    if (result != TransportResult::Success) {
+        LOGE("Failed to test sensor %d", sensor.sensor_type);
+        return false;
+    }
+
+    bool success = false;
+    auto test_event_handler = [this, &sensor, &success](const AppToHostEvent &event) -> bool {
+        if (event.IsTestEventForSensor(sensor.sensor_type)) {
+            success = HandleTestResult(sensor, event);
+            return false;
+        }
+        return true;
+    };
+
+    result = ReadAppEvents(test_event_handler, kTestTimeoutMs);
+    if (result != TransportResult::Success) {
+      LOGE("Error reading test response %d", static_cast<int>(result));
+      return false;
+    }
+
+    return success;
+}
+
 bool ContextHub::ForEachSensor(const std::vector<SensorSpec>& sensors,
         std::function<bool(const SensorSpec&)> callback) {
     bool success = true;
@@ -451,6 +494,23 @@
     return success;
 }
 
+bool ContextHub::HandleTestResult(const SensorSpec& sensor,
+        const AppToHostEvent &event) {
+    auto hdr = reinterpret_cast<const SensorAppEventHeader *>(event.GetDataPtr());
+    if (!hdr->status) {
+        LOGI("Self-test of sensor %d (%s) succeeded",
+             sensor.sensor_type,
+             ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str());
+        return true;
+    } else {
+        LOGE("Self-test of sensor %d (%s) failed with status %u",
+             sensor.sensor_type,
+             ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str(),
+             hdr->status);
+        return false;
+    }
+}
+
 ContextHub::TransportResult ContextHub::ReadAppEvents(
         std::function<bool(const AppToHostEvent&)> callback, int timeout_ms) {
     using Milliseconds = std::chrono::milliseconds;
diff --git a/util/nanotool/contexthub.h b/util/nanotool/contexthub.h
index f8eb6e7..1ea0c8a 100644
--- a/util/nanotool/contexthub.h
+++ b/util/nanotool/contexthub.h
@@ -133,6 +133,11 @@
     bool CalibrateSensors(const std::vector<SensorSpec>& sensors);
 
     /*
+     * Performs the sensor self-test routine.
+     */
+    bool TestSensors(const std::vector<SensorSpec>& sensors);
+
+    /*
      * Sends a sensor enable request to the context hub.
      */
     bool EnableSensor(const SensorSpec& sensor);
@@ -197,6 +202,9 @@
     // Performs the calibration routine, but does not call SaveCalibration()
     bool CalibrateSingleSensor(const SensorSpec& sensor);
 
+    // Performs the self-test routine
+    bool TestSingleSensor(const SensorSpec& sensor);
+
     /*
      * Iterates over sensors, invoking the given callback on each element.
      * Returns true if all callbacks returned true. Exits early on failure.
@@ -212,6 +220,12 @@
         const AppToHostEvent &event);
 
     /*
+     * Parses a self-test result event
+     */
+    bool HandleTestResult(const SensorSpec& sensor,
+        const AppToHostEvent &event);
+
+    /*
      * Same as ReadSensorEvents, but filters on AppToHostEvent instead of
      * SensorEvent.
      */
diff --git a/util/nanotool/nanomessage.h b/util/nanotool/nanomessage.h
index 2276775..e11ec4b 100644
--- a/util/nanotool/nanomessage.h
+++ b/util/nanotool/nanomessage.h
@@ -165,7 +165,8 @@
         Enable,
         Flush,
         ConfigData,
-        Calibrate
+        Calibrate,
+        SelfTest
     };
 
     ConfigureSensorRequest();
diff --git a/util/nanotool/nanotool.cpp b/util/nanotool/nanotool.cpp
index cf37945..71251a5 100644
--- a/util/nanotool/nanotool.cpp
+++ b/util/nanotool/nanotool.cpp
@@ -42,6 +42,7 @@
     Disable,
     DisableAll,
     Calibrate,
+    Test,
     Read,
     Poll,
     LoadCalibration,
@@ -64,6 +65,7 @@
         std::make_tuple("disable_all", NanotoolCommand::DisableAll),
         std::make_tuple("calibrate",   NanotoolCommand::Calibrate),
         std::make_tuple("cal",         NanotoolCommand::Calibrate),
+        std::make_tuple("test",        NanotoolCommand::Test),
         std::make_tuple("read",        NanotoolCommand::Read),
         std::make_tuple("poll",        NanotoolCommand::Poll),
         std::make_tuple("load_cal",    NanotoolCommand::LoadCalibration),
@@ -98,6 +100,7 @@
         "                        disable_all: send a disable request for all sensors\n"
         "                        calibrate: disable the sensor, then perform the sensor\n"
         "                           calibration routine\n"
+        "                        test: run a sensor's self-test routine\n"
 #ifndef __ANDROID__
         "                        flash: load a new firmware image to the hub\n"
 #endif
@@ -159,6 +162,7 @@
     if (!args->sensors.size()
           && (args->command == NanotoolCommand::Disable
                 || args->command == NanotoolCommand::Calibrate
+                || args->command == NanotoolCommand::Test
                 || args->command == NanotoolCommand::Poll)) {
         fprintf(stderr, "%s: At least 1 sensor must be specified for this "
                         "command (use -s)\n",
@@ -460,6 +464,11 @@
         success = hub->CalibrateSensors(args->sensors);
         break;
       }
+      case NanotoolCommand::Test: {
+        hub->DisableSensors(args->sensors);
+        success = hub->TestSensors(args->sensors);
+        break;
+      }
       case NanotoolCommand::LoadCalibration: {
         success = hub->LoadCalibration();
         break;