Adding new feature of core PID loop logging

This differs from the normal logging,
as this focuses on the core of the PID loop computations.

All variables within the core pid/ec/pid.cpp pid()
function are now logged, so math can be debugged.

Output is throttled to only log a new line
when it changes, or when 60 seconds have elapsed.

Creates 2 files for each PID loop,
one showing coefficients that were configured for it,
and one showing the variables changing over time.

Enable by --corelogging command line option,
or by creating /etc/thermal.d/corelogging file.

Tested:
Log files appear as expected, when enabled.
No changes noticed, when this feature is disabled.

Signed-off-by: Josh Lehan <krellan@google.com>
Change-Id: I3f37fe918e7cbc6fb885ffa2f268600d5a317d32
diff --git a/pid/ec/logging.hpp b/pid/ec/logging.hpp
new file mode 100644
index 0000000..3cbd714
--- /dev/null
+++ b/pid/ec/logging.hpp
@@ -0,0 +1,110 @@
+#pragma once
+
+#include "pid.hpp"
+
+#include <chrono>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+namespace pid_control
+{
+namespace ec
+{
+
+// Trivial class for information exported from core PID loop function
+struct PidCoreContext
+{
+    double input;
+    double setpoint;
+    double error;
+
+    double proportionalTerm;
+    double integralTerm1;
+    double integralTerm2;
+
+    double derivativeTerm;
+
+    double feedFwdTerm;
+    double output1;
+    double output2;
+
+    double minOut;
+    double maxOut;
+
+    double integralTerm3;
+    double output3;
+
+    double integralTerm;
+    double output;
+
+    bool operator!=(const PidCoreContext& rhs) const = default;
+    bool operator==(const PidCoreContext& rhs) const = default;
+};
+
+// Optional decorator class for each PID loop, to support logging
+// Although this is a trivial class, it ended up needing the Six Horsemen
+struct PidCoreLog
+{
+    std::string nameOriginal;
+    std::string nameClean;
+    std::ofstream fileContext;
+    std::ofstream fileCoeffs;
+    std::chrono::milliseconds lastLog;
+    PidCoreContext lastContext;
+    bool moved;
+
+    PidCoreLog() :
+        nameOriginal(), nameClean(), fileContext(), fileCoeffs(), lastLog(),
+        lastContext(), moved(false)
+    {}
+
+    PidCoreLog(const PidCoreLog& copy) = delete;
+
+    PidCoreLog& operator=(const PidCoreLog& copy) = delete;
+
+    PidCoreLog(PidCoreLog&& move)
+    {
+        // Reuse assignment operator below
+        *this = std::move(move);
+    }
+
+    PidCoreLog& operator=(PidCoreLog&& move)
+    {
+        if (this != &move)
+        {
+            *this = std::move(move);
+
+            // Mark the moved object, so destructor knows it was moved
+            move.moved = true;
+        }
+        return *this;
+    }
+
+    ~PidCoreLog()
+    {
+        // Do not close files if ownership was moved to another object
+        if (!moved)
+        {
+            fileContext.close();
+            fileCoeffs.close();
+        }
+    }
+};
+
+// Initializes logging files, call once per PID loop initialization
+void LogInit(const std::string& name, pid_info_t* pidinfoptr);
+
+// Returns PidCoreLog pointer, or nullptr if this PID loop not being logged
+PidCoreLog* LogPeek(const std::string& name);
+
+// Logs a line of logging, if different, or it has been long enough
+void LogContext(PidCoreLog& pidLog, const std::chrono::milliseconds& msNow,
+                const PidCoreContext& coreLog);
+
+// Takes a timestamp, suitable for column 1 of logging file output
+std::chrono::milliseconds LogTimestamp(void);
+
+} // namespace ec
+} // namespace pid_control