Add association interface

Make the virtual sensor inherit association interface, so that a sensor
could be associated with an inventory.
This is needed for Redfish to get the virtual sensor.

The json config could add optional "Association" config, that is an
array of tuples with three strings.
The virtual sensor will parse the config and create the association
accordingly.

Tested: Add below json config, and verify the virtual sensor has the
        association with the inventory DBus object, and the Redfish
        could show the sensor.

        "Associations":
        [
            [
                "chassis",
                "all_sensors",
                "/xyz/openbmc_project/inventory/system/board/xxxx"
            ]
        ],

Signed-off-by: Lei YU <yulei.sh@bytedance.com>
Change-Id: Ife4b6c38449ed0f29ab6d37f80b87c31df9ee24e
diff --git a/virtualSensor.cpp b/virtualSensor.cpp
index 0d76fcf..1e1d5bb 100644
--- a/virtualSensor.cpp
+++ b/virtualSensor.cpp
@@ -72,6 +72,24 @@
     }
 }
 
+using AssociationList =
+    std::vector<std::tuple<std::string, std::string, std::string>>;
+
+AssociationList getAssociationsFromJson(const Json& j)
+{
+    AssociationList assocs{};
+    try
+    {
+        j.get_to(assocs);
+    }
+    catch (const std::exception& ex)
+    {
+        log<level::ERR>("Failed to parse association",
+                        entry("EX=%s", ex.what()));
+    }
+    return assocs;
+}
+
 void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
                                       const std::string& objPath)
 {
@@ -161,6 +179,19 @@
         ValueIface::minValue(minConf->get<double>());
     }
 
+    /* Get optional association */
+    auto assocJson = sensorConfig.value("Associations", empty);
+    if (!assocJson.empty())
+    {
+        auto assocs = getAssociationsFromJson(assocJson);
+        if (!assocs.empty())
+        {
+            associationIface =
+                std::make_unique<AssociationObject>(bus, objPath.c_str());
+            associationIface->associations(assocs);
+        }
+    }
+
     /* Get expression string */
     exprStr = sensorConfig.value("Expression", "");
 
diff --git a/virtualSensor.hpp b/virtualSensor.hpp
index 1d72f1e..71a66ca 100644
--- a/virtualSensor.hpp
+++ b/virtualSensor.hpp
@@ -6,6 +6,7 @@
 
 #include <nlohmann/json.hpp>
 #include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
 #include <xyz/openbmc_project/Sensor/Value/server.hpp>
 
 #include <map>
@@ -24,6 +25,10 @@
 using ValueIface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
 using ValueObject = ServerObject<ValueIface>;
 
+using AssociationIface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+using AssociationObject = ServerObject<AssociationIface>;
+
 class SensorParam
 {
   public:
@@ -118,6 +123,9 @@
     /** @brief The performance loss threshold interface object */
     std::unique_ptr<Threshold<PerformanceLossObject>> perfLossIface;
 
+    /** @brief The association interface object */
+    std::unique_ptr<AssociationObject> associationIface;
+
     /** @brief Read config from json object and initialize sensor data
      * for each virtual sensor
      */
diff --git a/virtual_sensor_config.json b/virtual_sensor_config.json
index 6ccc403..f0d50ef 100644
--- a/virtual_sensor_config.json
+++ b/virtual_sensor_config.json
@@ -14,6 +14,14 @@
 			"WarningHigh": 70,
 			"WarningLow": 30
 		},
+		"Associations":
+		[
+			[
+				"chassis",
+				"all_sensors",
+				"/xyz/openbmc_project/inventory/system/board/my_board"
+			]
+		],
 		"Params":
 		{
 			"ConstParam" :