Add ability to set LED colors in DBus

phosphor-led-sysfs parses sysfs LED node names
in the form of "devicename:colour:function" as
described in

https://github.com/torvalds/linux/blob/master/Documentation/leds/leds-class.txt#L46

and then converts the "colour" part into the proper
value of the DBus 'Color' property using the
xyz.openbmc_project.Led.Physical.Palette interface.

In order for the led nodes to have their Color set,
the corresponding led nodes in devicetree must have
correct 'label' properties, e.g.:

leds {
    compatible = "gpio-leds";

    heartbeat {
        label = "bmc:green:hearbeat";
        gpios = <&gpio ASPEED_GPIO(R, 4) GPIO_ACTIVE_LOW>;
    };
    power_red {
        label = "system:red:power";
        gpios = <&gpio ASPEED_GPIO(N, 1) GPIO_ACTIVE_LOW>;
    };
}

Change-Id: I094f0e434bdd262995752576a3c792ccd5dbb3e2
Signed-off-by: Alexander Soldatov <a.soldatov@yadro.com>
Signed-off-by: Alexander Amelkin <a.amelkin@yadro.com>
diff --git a/controller.cpp b/controller.cpp
index 82430a7..8b9a22d 100644
--- a/controller.cpp
+++ b/controller.cpp
@@ -21,6 +21,7 @@
 #include "sysfs.hpp"
 
 #include <algorithm>
+#include <boost/algorithm/string.hpp>
 #include <iostream>
 #include <string>
 
@@ -32,6 +33,53 @@
     exit(-1);
 }
 
+struct LedDescr
+{
+    std::string devicename;
+    std::string color;
+    std::string function;
+};
+
+/** @brief parse LED name in sysfs
+ *  Parse sysfs LED name in format "devicename:colour:function"
+ *  or "devicename:colour" or "devicename" and sets corresponding
+ *  fields in LedDescr struct.
+ *
+ *  @param[in] name      - LED name in sysfs
+ *  @param[out] ledDescr - LED description
+ */
+void getLedDescr(const std::string& name, LedDescr& ledDescr)
+{
+    std::vector<std::string> words;
+    boost::split(words, name, boost::is_any_of(":"));
+    try
+    {
+        ledDescr.devicename = words.at(0);
+        ledDescr.color = words.at(1);
+        ledDescr.function = words.at(2);
+    }
+    catch (const std::out_of_range&)
+    {
+        return;
+    }
+}
+
+/** @brief generates LED DBus name from LED description
+ *
+ *  @param[in] name      - LED description
+ *  @return              - DBus LED name
+ */
+std::string getDbusName(const LedDescr& ledDescr)
+{
+    std::vector<std::string> words;
+    words.emplace_back(ledDescr.devicename);
+    if (!ledDescr.function.empty())
+        words.emplace_back(ledDescr.function);
+    if (!ledDescr.color.empty())
+        words.emplace_back(ledDescr.color);
+    return boost::join(words, "_");
+}
+
 int main(int argc, char** argv)
 {
     namespace fs = std::experimental::filesystem;
@@ -53,7 +101,7 @@
 
     // Since this application always gets invoked as part of a udev rule,
     // it is always guaranteed to get /sys/class/leds/one/two
-    // and we can go beyond leds/ to get the actual led name.
+    // and we can go beyond leds/ to get the actual LED name.
     // Refer: systemd/systemd#5072
 
     // On an error, this throws an exception and terminates.
@@ -72,6 +120,11 @@
     // dbus paths and hence need to convert them to underscores.
     std::replace(name.begin(), name.end(), '-', '_');
 
+    // Convert LED name in sysfs into DBus name
+    LedDescr ledDescr;
+    getLedDescr(name, ledDescr);
+    name = getDbusName(ledDescr);
+
     // Unique bus name representing a single LED.
     auto busName = std::string(BUSNAME) + '.' + name;
     auto objPath = std::string(OBJPATH) + '/' + name;
@@ -85,7 +138,7 @@
     // Create the Physical LED objects for directing actions.
     // Need to save this else sdbusplus destructor will wipe this off.
     phosphor::led::SysfsLed sled{fs::path(path)};
-    phosphor::led::Physical led(bus, objPath, sled);
+    phosphor::led::Physical led(bus, objPath, sled, ledDescr.color);
 
     /** @brief Claim the bus */
     bus.request_name(busName.c_str());
diff --git a/physical.cpp b/physical.cpp
index e614b99..ac8bb25 100644
--- a/physical.cpp
+++ b/physical.cpp
@@ -17,6 +17,7 @@
 #include "physical.hpp"
 
 #include <cassert>
+#include <cstdlib>
 #include <iostream>
 #include <string>
 namespace phosphor
@@ -110,5 +111,26 @@
     return;
 }
 
+/** @brief set led color property in DBus*/
+void Physical::setLedColor(const std::string& color)
+{
+    static const std::string prefix =
+        "xyz.openbmc_project.Led.Physical.Palette.";
+    if (!color.length())
+        return;
+    std::string tmp = color;
+    tmp[0] = toupper(tmp[0]);
+    try
+    {
+        auto palette = convertPaletteFromString(prefix + tmp);
+        setPropertyByName("Color", palette);
+    }
+    catch (const sdbusplus::exception::InvalidEnumString&)
+    {
+        // if color var contains invalid color,
+        // Color property will have default value
+    }
+}
+
 } // namespace led
 } // namespace phosphor
diff --git a/physical.hpp b/physical.hpp
index 67a900b..3801c3a 100644
--- a/physical.hpp
+++ b/physical.hpp
@@ -37,9 +37,10 @@
      * @param[in] bus       - system dbus handler
      * @param[in] objPath   - The Dbus path that hosts physical LED
      * @param[in] ledPath   - sysfs path where this LED is exported
+     * @param[in] color     - led color name
      */
     Physical(sdbusplus::bus::bus& bus, const std::string& objPath,
-             SysfsLed& led) :
+             SysfsLed& led, const std::string& color = "") :
 
         sdbusplus::server::object::object<
             sdbusplus::xyz::openbmc_project::Led::server::Physical>(
@@ -50,6 +51,9 @@
         // need to save what the micro-controller currently has.
         setInitialState();
 
+        // Read led color from enviroment and set it in DBus.
+        setLedColor(color);
+
         // We are now ready.
         emit_object_added();
     }
@@ -102,6 +106,12 @@
      *  @return None
      */
     void blinkOperation();
+
+    /** @brief set led color property in DBus
+     *
+     *  @param[in] color - led color name
+     */
+    void setLedColor(const std::string& color);
 };
 
 } // namespace led