Send ambient and altitude to OCC

After the OCCs go active or anytime the ambient temperature changes the
ambient temperature and alititude will get sent to each OCC.
The altitude is only read once. If the altitude was not valid,
another attempt to read it will be done when the ambient temperature
is going to be re-sent.

Tested on Everest and Rainier systems

Change-Id: Icd64c50c05469bc985cfcaa3fcc74c3db5b41429
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/occ_command.hpp b/occ_command.hpp
index 3ea9b19..ff8347b 100644
--- a/occ_command.hpp
+++ b/occ_command.hpp
@@ -25,6 +25,7 @@
     SET_CONFIG_DATA = 0x21,
     SET_USER_PCAP = 0x22,
     RESET_PREP = 0x25,
+    SEND_AMBIENT = 0x30,
     DEBUG_PASS_THROUGH = 0x40,
     AME_PASS_THROUGH = 0x41,
     GET_FIELD_DEBUG_DATA = 0x42,
diff --git a/occ_manager.cpp b/occ_manager.cpp
index eadbca1..c1ecf5b 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -779,5 +779,135 @@
     return;
 }
 #endif
+
+// Read the altitude from DBus
+void Manager::readAltitude()
+{
+    static bool traceAltitudeErr = true;
+
+    utils::PropertyValue altitudeProperty{};
+    try
+    {
+        altitudeProperty = utils::getProperty(ALTITUDE_PATH, ALTITUDE_INTERFACE,
+                                              ALTITUDE_PROP);
+        auto sensorVal = std::get<double>(altitudeProperty);
+        if (sensorVal < 0xFFFF)
+        {
+            if (sensorVal < 0)
+            {
+                altitude = 0;
+            }
+            else
+            {
+                // Round to nearest meter
+                altitude = uint16_t(sensorVal + 0.5);
+            }
+            log<level::DEBUG>(fmt::format("readAltitude: sensor={} ({}m)",
+                                          sensorVal, altitude)
+                                  .c_str());
+            traceAltitudeErr = true;
+        }
+        else
+        {
+            if (traceAltitudeErr)
+            {
+                traceAltitudeErr = false;
+                log<level::DEBUG>(
+                    fmt::format("Invalid altitude value: {}", sensorVal)
+                        .c_str());
+            }
+        }
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        if (traceAltitudeErr)
+        {
+            traceAltitudeErr = false;
+            log<level::INFO>(
+                fmt::format("Unable to read Altitude: {}", e.what()).c_str());
+        }
+        altitude = 0xFFFF; // not available
+    }
+}
+
+// Callback function when ambient temperature changes
+void Manager::ambientCallback(sdbusplus::message::message& msg)
+{
+    double currentTemp = 0;
+    uint8_t truncatedTemp = 0xFF;
+    std::string msgSensor;
+    std::map<std::string, std::variant<double>> msgData;
+    msg.read(msgSensor, msgData);
+
+    auto valPropMap = msgData.find(AMBIENT_PROP);
+    if (valPropMap == msgData.end())
+    {
+        log<level::DEBUG>("ambientCallback: Unknown ambient property changed");
+        return;
+    }
+    currentTemp = std::get<double>(valPropMap->second);
+    if (std::isnan(currentTemp))
+    {
+        truncatedTemp = 0xFF;
+    }
+    else
+    {
+        if (currentTemp < 0)
+        {
+            truncatedTemp = 0;
+        }
+        else
+        {
+            // Round to nearest degree C
+            truncatedTemp = uint8_t(currentTemp + 0.5);
+        }
+    }
+
+    // If ambient changes, notify OCCs
+    if (truncatedTemp != ambient)
+    {
+        log<level::DEBUG>(
+            fmt::format("ambientCallback: Ambient change from {} to {}C",
+                        ambient, currentTemp)
+                .c_str());
+
+        ambient = truncatedTemp;
+        if (altitude == 0xFFFF)
+        {
+            // No altitude yet, try reading again
+            readAltitude();
+        }
+
+        log<level::DEBUG>(
+            fmt::format("ambientCallback: Ambient: {}C, altitude: {}m", ambient,
+                        altitude)
+                .c_str());
+#ifdef POWER10
+        // Send ambient and altitude to all OCCs
+        for (auto& obj : statusObjects)
+        {
+            if (obj->occActive())
+            {
+                obj->sendAmbient(ambient, altitude);
+            }
+        }
+#endif // POWER10
+    }
+}
+
+// return the current ambient and altitude readings
+void Manager::getAmbientData(bool& ambientValid, uint8_t& ambientTemp,
+                             uint16_t& altitudeValue) const
+{
+    ambientValid = true;
+    ambientTemp = ambient;
+    altitudeValue = altitude;
+
+    if (ambient == 0xFF)
+    {
+        ambientValid = false;
+    }
+}
+
 } // namespace occ
 } // namespace open_power
diff --git a/occ_manager.hpp b/occ_manager.hpp
index 742c222..a137363 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -48,6 +48,14 @@
 constexpr unsigned int defaultPollingInterval = 5;
 #endif
 
+constexpr auto AMBIENT_PATH =
+    "/xyz/openbmc_project/sensors/temperature/Ambient_Virtual_Temp";
+constexpr auto AMBIENT_INTERFACE = "xyz.openbmc_project.Sensor.Value";
+constexpr auto AMBIENT_PROP = "Value";
+constexpr auto ALTITUDE_PATH = "/xyz/openbmc_project/sensors/altitude/Altitude";
+constexpr auto ALTITUDE_INTERFACE = "xyz.openbmc_project.Sensor.Value";
+constexpr auto ALTITUDE_PROP = "Value";
+
 /** @class Manager
  *  @brief Builds and manages OCC objects
  */
@@ -72,7 +80,14 @@
         _pollTimer(
             std::make_unique<
                 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
-                sdpEvent, std::bind(&Manager::pollerTimerExpired, this)))
+                sdpEvent, std::bind(&Manager::pollerTimerExpired, this))),
+        ambientPropChanged(
+            utils::getBus(),
+            sdbusRule::member("PropertiesChanged") +
+                sdbusRule::path(AMBIENT_PATH) +
+                sdbusRule::argN(0, AMBIENT_INTERFACE) +
+                sdbusRule::interface("org.freedesktop.DBus.Properties"),
+            std::bind(&Manager::ambientCallback, this, std::placeholders::_1))
 #ifdef PLDM
         ,
         pldmHandle(std::make_unique<pldm::Interface>(
@@ -95,6 +110,7 @@
 #else
         findAndCreateObjects();
 #endif
+        readAltitude();
     }
 
     /** @brief Return the number of bound OCCs */
@@ -112,6 +128,15 @@
     void sbeTimeout(unsigned int instance);
 #endif
 
+    /** @brief Return the latest ambient and altitude readings
+     *
+     *  @param[out] ambientValid - true if ambientTemp is valid
+     *  @param[out] ambient - ambient temperature in degrees C
+     *  @param[out] altitude - altitude in meters
+     */
+    void getAmbientData(bool& ambientValid, uint8_t& ambientTemp,
+                        uint16_t& altitude) const;
+
   private:
     /** @brief Creates the OCC D-Bus objects.
      */
@@ -175,6 +200,12 @@
     /** @brief Number of seconds between poll commands */
     uint8_t pollInterval;
 
+    /** @brief Ambient temperature of the system in degrees C */
+    uint8_t ambient = 0xFF; // default: not available
+
+    /** @brief Altitude of the system in meters */
+    uint16_t altitude = 0xFFFF; // default: not available
+
     /** @brief Poll timer event */
     sdeventplus::Event sdpEvent;
 
@@ -186,6 +217,9 @@
         sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>
         _pollTimer;
 
+    /** @brief Subscribe to ambient temperature changed events */
+    sdbusplus::bus::match_t ambientPropChanged;
+
 #ifdef I2C_OCC
     /** @brief Init Status objects for I2C OCC devices
      *
@@ -337,6 +371,15 @@
         {PMIC, "_pmic_temp"},
         {memCtlrExSensor, "_extmb_temp"}};
 #endif
+
+    /** @brief Read the altitude from DBus */
+    void readAltitude();
+
+    /** @brief Callback function when ambient temperature changes
+     *
+     *  @param[in]  msg - Data associated with subscribed signal
+     */
+    void ambientCallback(sdbusplus::message::message& msg);
 };
 
 } // namespace occ
diff --git a/occ_status.cpp b/occ_status.cpp
index eaa6080..ad0d0ab 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -1,5 +1,6 @@
 #include "occ_status.hpp"
 
+#include "occ_manager.hpp"
 #include "occ_sensor.hpp"
 #include "powermode.hpp"
 #include "utils.hpp"
@@ -188,6 +189,18 @@
                 // Kernel detected that the master OCC went to active state
                 occsWentActive();
             }
+            if (OccState(state) == OccState::ACTIVE)
+            {
+                CmdStatus status = sendAmbient();
+                if (status != CmdStatus::SUCCESS)
+                {
+                    log<level::ERR>(
+                        fmt::format(
+                            "readOccState: Sending Ambient failed with status {}",
+                            status)
+                            .c_str());
+                }
+            }
 #endif
         }
         file.close();
@@ -590,6 +603,79 @@
     return status;
 }
 
+// Send Ambient and Altitude to the OCC
+CmdStatus Status::sendAmbient(const uint8_t inTemp, const uint16_t inAltitude)
+{
+    CmdStatus status = CmdStatus::FAILURE;
+    bool ambientValid = true;
+    uint8_t ambientTemp = inTemp;
+    uint16_t altitude = inAltitude;
+
+    if (ambientTemp == 0xFF)
+    {
+        // Get latest readings from manager
+        manager.getAmbientData(ambientValid, ambientTemp, altitude);
+        log<level::DEBUG>(
+            fmt::format("sendAmbient: valid: {}, Ambient: {}C, altitude: {}m",
+                        ambientValid, ambientTemp, altitude)
+                .c_str());
+    }
+
+    std::vector<std::uint8_t> cmd, rsp;
+    cmd.reserve(11);
+    cmd.push_back(uint8_t(CmdType::SEND_AMBIENT));
+    cmd.push_back(0x00);                    // Data Length (2 bytes)
+    cmd.push_back(0x08);                    //
+    cmd.push_back(0x00);                    // Version
+    cmd.push_back(ambientValid ? 0 : 0xFF); // Ambient Status
+    cmd.push_back(ambientTemp);             // Ambient Temperature
+    cmd.push_back(altitude >> 8);           // Altitude in meters (2 bytes)
+    cmd.push_back(altitude & 0xFF);         //
+    cmd.push_back(0x00);                    // Reserved (3 bytes)
+    cmd.push_back(0x00);
+    cmd.push_back(0x00);
+    log<level::DEBUG>(fmt::format("sendAmbient: SEND_AMBIENT "
+                                  "command to OCC{} ({} bytes)",
+                                  instance, cmd.size())
+                          .c_str());
+    status = occCmd.send(cmd, rsp);
+    if (status == CmdStatus::SUCCESS)
+    {
+        if (rsp.size() == 5)
+        {
+            if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+            {
+                log<level::ERR>(
+                    fmt::format(
+                        "sendAmbient: SEND_AMBIENT failed with status 0x{:02X}",
+                        rsp[2])
+                        .c_str());
+                dump_hex(rsp);
+                status = CmdStatus::FAILURE;
+            }
+        }
+        else
+        {
+            log<level::ERR>("sendAmbient: INVALID SEND_AMBIENT response");
+            dump_hex(rsp);
+            status = CmdStatus::FAILURE;
+        }
+    }
+    else
+    {
+        if (status == CmdStatus::OPEN_FAILURE)
+        {
+            // OCC not active yet
+            status = CmdStatus::SUCCESS;
+        }
+        else
+        {
+            log<level::ERR>("sendAmbient: SEND_AMBIENT FAILED!");
+        }
+    }
+
+    return status;
+}
 #endif // POWER10
 
 } // namespace occ
diff --git a/occ_status.hpp b/occ_status.hpp
index 7356add..c449ec1 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -69,7 +69,7 @@
      *                             OCC if PLDM is the host communication
      *                             protocol
      */
-    Status(EventPtr& event, const char* path, Manager& manager,
+    Status(EventPtr& event, const char* path, Manager& managerRef,
            std::function<void(bool)> callBack = nullptr
 #ifdef PLDM
            ,
@@ -79,6 +79,7 @@
 
         Interface(utils::getBus(), getDbusPath(path).c_str(), true),
         path(path), callBack(callBack), instance(getInstance(path)),
+        manager(managerRef),
         device(event,
 #ifdef I2C_OCC
                fs::path(DEV_PATH) / i2c_occ::getI2cDeviceName(path),
@@ -86,7 +87,7 @@
                fs::path(DEV_PATH) /
                    fs::path(sysfsName + "." + std::to_string(instance + 1)),
 #endif
-               manager, *this, instance),
+               managerRef, *this, instance),
         hostControlSignal(
             utils::getBus(),
             sdbusRule::type::signal() + sdbusRule::member("CommandComplete") +
@@ -177,6 +178,17 @@
      *  @return SUCCESS on success
      */
     CmdStatus sendIpsData();
+
+    /** @brief Send Ambient & Altitude data to OCC
+     *
+     *  @param[in] ambient - temperature to send (0xFF will force read
+     *                       of current temperature and altitude)
+     *  @param[in] altitude - altitude to send (0xFFFF = unavailable)
+     *
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendAmbient(const uint8_t ambient = 0xFF,
+                          const uint16_t altitude = 0xFFFF);
 #endif // POWER10
 
   private:
@@ -197,6 +209,9 @@
     /** @brief OCC instance to Sensor definitions mapping */
     static const std::map<instanceID, sensorDefs> sensorMap;
 
+    /** @brief OCC manager object */
+    const Manager& manager;
+
     /** @brief OCC device object to do bind and unbind */
     Device device;
 
diff --git a/utils.hpp b/utils.hpp
index 67d7a31..9e6c19b 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -19,7 +19,7 @@
 constexpr auto DBUS_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
 
 // The value of the property(type: variant, contains some basic types)
-using PropertyValue = std::variant<uint32_t, bool>;
+using PropertyValue = std::variant<uint32_t, bool, double>;
 
 /** @brief Get the bus connection. */
 static auto& getBus()