Add methods to trigger action on LED

Implements the ON, OFF and BLINK operations on a given LED.

Change-Id: I74b5ac01d8e76961999a2673d52d73296f5603d7
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
diff --git a/argument.cpp b/argument.cpp
index 7b70fe0..b70a0dd 100644
--- a/argument.cpp
+++ b/argument.cpp
@@ -73,7 +73,8 @@
     std::cerr << "Usage: " << argv[0] << " [options]" << std::endl;
     std::cerr << "Options:" << std::endl;
     std::cerr << "    --help               Print this menu" << std::endl;
-    std::cerr << "    --path=<path>        sysfs path like /sys/class/leds"
+    std::cerr << "    --path=<path>        absolute path of LED in sysfs; like";
+    std::cerr <<                           " /sys/class/leds/<name>"
               << std::endl;
 }
 } // namespace led
diff --git a/physical.cpp b/physical.cpp
index 31fc1ee..38c69b0 100644
--- a/physical.cpp
+++ b/physical.cpp
@@ -25,32 +25,129 @@
 /** @brief Populates key parameters */
 void Physical::setInitialState()
 {
+    // Control files in /sys/class/leds/<led-name>
+    brightCtrl = path + BRIGHTNESS;
+    blinkCtrl =  path + BLINKCTRL;
+
+    delayOnCtrl = path + DELAYON;
+    delayOffCtrl = path + DELAYOFF;
+
     // 1. read /sys/class/leds/name/trigger
     // 2. If its 'timer', then its blinking.
     //    2.1: On blink, use delay_on and delay_off into dutyOn
     // 3. If its 'none', then read brightness. 255 means, its ON, else OFF.
-    // Implementation in the next patchset.
+
+    auto trigger = read<std::string>(blinkCtrl);
+    if (trigger == "timer")
+    {
+        // LED is blinking. Get the delay_on and delay_off and compute
+        // DutyCycle. sfsfs values are in strings. Need to convert 'em over to
+        // integer.
+        auto delayOn = std::stoi(read<std::string>(delayOnCtrl));
+        auto delayOff = std::stoi(read<std::string>(delayOffCtrl));
+
+        // Calculate frequency and then percentage ON
+        frequency = delayOn + delayOff;
+        auto factor = frequency / 100;
+        auto dutyOn = delayOn / factor;
+
+        // Update.
+        this->dutyOn(dutyOn);
+    }
+    else
+    {
+        // This is hardcoded for now. This will be changed to this->frequency()
+        // when frequency is implemented.
+        // TODO
+        frequency = 1000;
+
+        // LED is either ON or OFF
+        auto brightness = read<std::string>(brightCtrl);
+        if (brightness == std::string(ASSERT))
+        {
+            // LED is in Solid ON
+            sdbusplus::xyz::openbmc_project::Led::server
+                          ::Physical::state(Action::On);
+        }
+        else
+        {
+            // LED is in OFF state
+            sdbusplus::xyz::openbmc_project::Led::server
+                          ::Physical::state(Action::Off);
+        }
+    }
+    return;
 }
 
 /** @brief Overloaded State Property Setter function */
 auto Physical::state(Action value) -> Action
 {
-    // Set the base class's state to actuals since the getter
-    // operation is handled there.
-    auto action = sdbusplus::xyz::openbmc_project::Led::server
+    // Obtain current operation
+    auto current = sdbusplus::xyz::openbmc_project::Led::server
+                                   ::Physical::state();
+
+    // Update requested operation into base class
+    auto requested = sdbusplus::xyz::openbmc_project::Led::server
                                    ::Physical::state(value);
 
     // Apply the action.
-    driveLED();
+    driveLED(current, requested);
 
-    return action;
+    return value;
 }
 
 /** @brief apply action on the LED */
-void Physical::driveLED()
+void Physical::driveLED(Action current, Action request)
 {
-    // Replace with actual code.
-    std::cout << " Drive LED  STUB :: \n";
+    if (current == request)
+    {
+        // Best we can do here is ignore.
+        return;
+    }
+
+    // Transition TO Blinking state
+    if (request == Action::Blink)
+    {
+        return blinkOperation();
+    }
+
+    // Transition TO Stable states.
+    if(request == Action::On || request == Action::Off)
+    {
+        return stableStateOperation(request);
+    }
+    return;
+}
+
+/** @brief Either TurnON -or- TurnOFF */
+void Physical::stableStateOperation(Action action)
+{
+    auto value = (action == Action::On) ? ASSERT : DEASSERT;
+
+    // Write "none" to trigger to clear any previous action
+    write(blinkCtrl, "none");
+
+    // And write the current command
+    write(brightCtrl, value);
+    return;
+}
+
+/** @brief BLINK the LED */
+void Physical::blinkOperation()
+{
+    // Get the latest dutyOn that the user requested
+    auto dutyOn = this->dutyOn();
+
+    // Write "timer" to "trigger" file
+    write(blinkCtrl, "timer");
+
+    // Write DutyON. Value in percentage 1_millisecond.
+    // so 50% input becomes 500. Driver wants string input
+    auto percentage = frequency / 100;
+    write(delayOnCtrl, std::to_string(dutyOn * percentage));
+
+    // Write DutyOFF. Value in milli seconds so 50% input becomes 500.
+    write(delayOffCtrl, std::to_string((100 - dutyOn) * percentage));
     return;
 }
 
diff --git a/physical.hpp b/physical.hpp
index 0eac97b..c24d3f2 100644
--- a/physical.hpp
+++ b/physical.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <string>
+#include <fstream>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
 #include "xyz/openbmc_project/Led/Physical/server.hpp"
@@ -8,6 +9,42 @@
 {
 namespace led
 {
+/** @brief Acts as input and output file for the current LED power state.
+ *   Write "0" to trigger a LED OFF operation.
+ *   Write "255" to trigger a LED ON operation.
+ *   To know the current power state of the LED, a read on this
+ *   would give either 0 or 255 indicating if the LED is currently
+ *   Off / On AT THAT moment.
+ *   Example: /sys/class/leds/myled/brightness
+ */
+constexpr auto BRIGHTNESS = "/brightness";
+
+/** @brief Assert LED by writing 255 */
+constexpr auto ASSERT = "255";
+
+/** @brief De-assert by writing "0" */
+constexpr auto DEASSERT = "0";
+
+/** @brief Write "timer" to this file telling the driver that
+ *   the intended operation is BLINK. When the value "timer" is written to
+ *   the file, 2 more files get auto generated and are named "delay_on" and
+ *   "delay_off"
+ *   To move away from blinking, write "none"
+ *   Example:  /sys/class/leds/myled/trigger
+ */
+constexpr auto BLINKCTRL = "/trigger";
+
+/** @brief write number of milliseconds that the LED needs to be ON
+ *   while blinking. Default is 500 by the driver.
+ *   Example:  /sys/class/leds/myled/delay_on
+ */
+constexpr auto DELAYON = "/delay_on";
+
+/** @brief Write number of milliseconds that the LED needs to be OFF
+ *   while blinking. Default is 500 by the driver.
+ *   Example:  /sys/class/leds/myled/delay_off
+ */
+constexpr auto DELAYOFF = "/delay_off";
 
 /** @class Physical
  *  @brief Responsible for applying actions on a particular physical LED
@@ -62,18 +99,91 @@
          */
         std::string path;
 
-        /** @brief Applies the user triggered action on the LED
-         *   by writing to sysfs
-         *
-         *  @return None
+        /** @brief Frequency range that the LED can operate on.
+         *  Will be removed when frequency is put into interface
          */
-        void driveLED(void);
+        uint32_t frequency;
+
+        /** @brief Brightness described above */
+        std::string brightCtrl;
+
+        /** @brief BlinkCtrl described above */
+        std::string blinkCtrl;
+
+        /** @brief delay_on described above */
+        std::string delayOnCtrl;
+
+        /** @brief delay_ff described above */
+        std::string delayOffCtrl;
 
         /** @brief reads sysfs and then setsup the parameteres accordingly
          *
-         *  @return Status or exception thrown
+         *  @return None
          */
-        void setInitialState(void);
+        void setInitialState();
+
+        /** @brief Applies the user triggered action on the LED
+         *   by writing to sysfs
+         *
+         *  @param [in] current - Current state of LED
+         *  @param [in] request - Requested state
+         *
+         *  @return None
+         */
+        void driveLED(Action current, Action request);
+
+        /** @brief Sets the LED to either ON or OFF state
+         *
+         *  @param [in] action - Requested action. Could be OFF or ON
+         *  @return None
+         */
+        void stableStateOperation(Action action);
+
+        /** @brief Sets the LED to BLINKING
+         *
+         *  @return None
+         */
+        void blinkOperation();
+
+        /** @brief Generic file writer.
+         *   There are files like "brightness", "trigger" , "delay_on" and
+         *   "delay_off" that will tell what the LED driver needs to do.
+         *
+         *  @param[in] filename - Name of file to be written
+         *  @param[in] data     - Data to be written to
+         *  @return             - None
+         */
+        template <typename T>
+        auto write(const std::string& fileName, T&& data)
+        {
+            if(std::ifstream(fileName))
+            {
+                std::ofstream file(fileName, std::ios::out);
+                file << data;
+                file.close();
+            }
+            return;
+        }
+
+        /** @brief Generic file reader.
+         *   There are files like "brightness", "trigger" , "delay_on" and
+         *   "delay_off" that will tell what the LED driver needs to do.
+         *
+         *  @param[in] filename - Name of file to be read
+         *  @return             - File content
+         */
+        template <typename T>
+        T read(const std::string& fileName)
+        {
+            T data = T();
+            if(std::ifstream(fileName))
+            {
+                std::ifstream file(fileName, std::ios::in);
+                file >> data;
+                file.close();
+            }
+            return data;
+        }
 };
 
 } // namespace led