Implement PSU Event

Expose PSU Event AC Lost, Fan Fault, Failure, Predictive D-Bus interface.
Read alarm and max_alarm, min_alarm, crit_alarm, lcrit_alarm and
assert or deassert the related properties on PSU Event.

Tested By:
After run /usr/sbin/psusensor below D-Bus interface are exposed
/xyz/openbmc_project/State/Decorator/PSU1_OperationalStatus
/xyz/openbmc_project/State/Decorator/PSU2_OperationalStatus
After plugging out AC cable from PSU1, the functional property in
PSU1_OperationalStatus changes to "false" from "true".
After plugging in AC cable again, the property changes back to "true".

Change-Id: Ic21513471c4632835c39148ea313808fdcc816fa
Signed-off-by: Cheng C Yang <cheng.c.yang@linux.intel.com>
diff --git a/src/PSUEvent.cpp b/src/PSUEvent.cpp
new file mode 100644
index 0000000..5bfa1e4
--- /dev/null
+++ b/src/PSUEvent.cpp
@@ -0,0 +1,200 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// 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 <PSUEvent.hpp>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+PSUCombineEvent::PSUCombineEvent(
+    sdbusplus::asio::object_server& objectServer, boost::asio::io_service& io,
+    const std::string& psuName,
+    boost::container::flat_map<std::string, std::vector<std::string>>&
+        eventPathList,
+    const std::string& combineEventName) :
+    objServer(objectServer)
+{
+    eventInterface = objServer.add_interface(
+        "/xyz/openbmc_project/State/Decorator/" + psuName + "_" +
+            combineEventName,
+        "xyz.openbmc_project.State.Decorator.OperationalStatus");
+    eventInterface->register_property("functional", bool(true));
+
+    if (!eventInterface->initialize())
+    {
+        std::cerr << "error initializing event interface\n";
+    }
+
+    std::shared_ptr<std::set<std::string>> combineEvent =
+        std::make_shared<std::set<std::string>>();
+    for (const auto& pathList : eventPathList)
+    {
+        const std::string& eventName = pathList.first;
+        std::string eventPSUName = eventName + psuName;
+        for (const auto& path : pathList.second)
+        {
+            std::shared_ptr<std::set<std::string>> assert =
+                std::make_shared<std::set<std::string>>();
+            std::shared_ptr<bool> state = std::make_shared<bool>(false);
+            events[eventPSUName].emplace_back(std::make_unique<PSUSubEvent>(
+                eventInterface, path, io, eventName, assert, combineEvent,
+                state));
+            asserts.emplace_back(assert);
+            states.emplace_back(state);
+        }
+    }
+}
+
+PSUCombineEvent::~PSUCombineEvent()
+{
+    events.clear();
+    objServer.remove_interface(eventInterface);
+}
+
+PSUSubEvent::PSUSubEvent(
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
+    const std::string& path, boost::asio::io_service& io,
+    const std::string& eventName,
+    std::shared_ptr<std::set<std::string>> asserts,
+    std::shared_ptr<std::set<std::string>> combineEvent,
+    std::shared_ptr<bool> state) :
+    eventInterface(eventInterface),
+    inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), errCount(0),
+    path(path), eventName(eventName), assertState(state), asserts(asserts),
+    combineEvent(combineEvent)
+{
+    setupRead();
+}
+
+void PSUSubEvent::setupRead(void)
+{
+    boost::asio::async_read_until(
+        inputDev, readBuf, '\n',
+        [&](const boost::system::error_code& ec,
+            std::size_t /*bytes_transfered*/) { handleResponse(ec); });
+}
+
+PSUSubEvent::~PSUSubEvent()
+{
+    inputDev.close();
+    waitTimer.cancel();
+}
+
+void PSUSubEvent::handleResponse(const boost::system::error_code& err)
+{
+    if (err == boost::system::errc::bad_file_descriptor)
+    {
+        return;
+    }
+    std::istream responseStream(&readBuf);
+    if (!err)
+    {
+        std::string response;
+        try
+        {
+            std::getline(responseStream, response);
+            int nvalue = std::stof(response);
+            responseStream.clear();
+            if (nvalue != value)
+            {
+                updateValue(nvalue);
+            }
+            errCount = 0;
+        }
+        catch (const std::invalid_argument&)
+        {
+            errCount++;
+        }
+    }
+    else
+    {
+        errCount++;
+    }
+    if (errCount >= warnAfterErrorCount)
+    {
+        if (errCount == warnAfterErrorCount)
+        {
+            std::cerr << "Failure to read event at " << path << "\n";
+        }
+        updateValue(0);
+        errCount++;
+    }
+    responseStream.clear();
+    inputDev.close();
+    int fd = open(path.c_str(), O_RDONLY);
+    if (fd <= 0)
+    {
+        return;
+    }
+    inputDev.assign(fd);
+    waitTimer.expires_from_now(boost::posix_time::milliseconds(eventPollMs));
+    waitTimer.async_wait([&](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return;
+        }
+        setupRead();
+    });
+}
+
+// Any of the sub events of one event is asserted, then the event will be
+// asserted. Only if none of the sub events are asserted, the event will be
+// deasserted.
+void PSUSubEvent::updateValue(const int& newValue)
+{
+    if (newValue == 0)
+    {
+        auto found = (*asserts).find(path);
+        if (found == (*asserts).end())
+        {
+            return;
+        }
+        (*asserts).erase(found);
+
+        if (!(*asserts).empty())
+        {
+            return;
+        }
+        if (*assertState == true)
+        {
+            *assertState = false;
+            auto foundCombine = (*combineEvent).find(eventName);
+            if (foundCombine != (*combineEvent).end())
+            {
+                return;
+            }
+            (*combineEvent).erase(eventName);
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", true);
+            }
+        }
+    }
+    else
+    {
+        (*asserts).emplace(path);
+        if (*assertState == false)
+        {
+            *assertState = true;
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", false);
+            }
+            (*combineEvent).emplace(eventName);
+        }
+    }
+    value = newValue;
+}