1 #pragma once
2 
3 #include "pid.hpp"
4 
5 #include <chrono>
6 #include <cstring>
7 #include <fstream>
8 #include <iostream>
9 #include <string>
10 
11 namespace pid_control
12 {
13 namespace ec
14 {
15 
16 // Trivial class for information exported from core PID loop function
17 struct PidCoreContext
18 {
19     double input;
20     double setpoint;
21     double error;
22 
23     double proportionalTerm;
24     double integralTerm1;
25     double integralTerm2;
26 
27     double derivativeTerm;
28 
29     double feedFwdTerm;
30     double output1;
31     double output2;
32 
33     double minOut;
34     double maxOut;
35 
36     double integralTerm3;
37     double output3;
38 
39     double integralTerm;
40     double output;
41 
42     bool operator!=(const PidCoreContext& rhs) const = default;
43     bool operator==(const PidCoreContext& rhs) const = default;
44 };
45 
46 // Optional decorator class for each PID loop, to support logging
47 // Although this is a trivial class, it ended up needing the Six Horsemen
48 struct PidCoreLog
49 {
50     std::string nameOriginal;
51     std::string nameClean;
52     std::ofstream fileContext;
53     std::ofstream fileCoeffs;
54     std::chrono::milliseconds lastLog;
55     PidCoreContext lastContext;
56     bool moved;
57 
PidCoreLogpid_control::ec::PidCoreLog58     PidCoreLog() :
59         nameOriginal(), nameClean(), fileContext(), fileCoeffs(), lastLog(),
60         lastContext(), moved(false)
61     {}
62 
63     PidCoreLog(const PidCoreLog& copy) = delete;
64 
65     PidCoreLog& operator=(const PidCoreLog& copy) = delete;
66 
PidCoreLogpid_control::ec::PidCoreLog67     PidCoreLog(PidCoreLog&& move)
68     {
69         // Reuse assignment operator below
70         *this = std::move(move);
71     }
72 
operator =pid_control::ec::PidCoreLog73     PidCoreLog& operator=(PidCoreLog&& move)
74     {
75         if (this != &move)
76         {
77             // Move each field individually
78             nameOriginal = std::move(move.nameOriginal);
79             nameClean = std::move(move.nameClean);
80             fileContext = std::move(move.fileContext);
81             fileCoeffs = std::move(move.fileCoeffs);
82             lastLog = std::move(move.lastLog);
83             lastContext = std::move(move.lastContext);
84 
85             // Mark the moved object, so destructor knows it was moved
86             move.moved = true;
87         }
88         return *this;
89     }
90 
~PidCoreLogpid_control::ec::PidCoreLog91     ~PidCoreLog()
92     {
93         // Do not close files if ownership was moved to another object
94         if (!moved)
95         {
96             fileContext.close();
97             fileCoeffs.close();
98         }
99     }
100 };
101 
102 // Initializes logging files, call once per PID loop initialization
103 void LogInit(const std::string& name, pid_info_t* pidinfoptr);
104 
105 // Returns PidCoreLog pointer, or nullptr if this PID loop not being logged
106 PidCoreLog* LogPeek(const std::string& name);
107 
108 // Logs a line of logging, if different, or it has been long enough
109 void LogContext(PidCoreLog& pidLog, const std::chrono::milliseconds& msNow,
110                 const PidCoreContext& coreLog);
111 
112 // Takes a timestamp, suitable for column 1 of logging file output
113 std::chrono::milliseconds LogTimestamp(void);
114 
115 } // namespace ec
116 } // namespace pid_control
117