1 #pragma once
2 
3 #include <deque>
4 #include <tuple>
5 #include <vector>
6 
7 namespace witherspoon
8 {
9 namespace power
10 {
11 namespace history
12 {
13 
14 static constexpr auto recIDPos = 0;
15 static constexpr auto recTimePos = 1;
16 static constexpr auto recAvgPos = 2;
17 static constexpr auto recMaxPos = 3;
18 using Record = std::tuple<size_t, int64_t, int64_t, int64_t>;
19 
20 /**
21  * @class InvalidRecordException
22  *
23  * The exception that is thrown when a raw history record
24  * cannot be parsed.
25  */
26 class InvalidRecordException : public std::runtime_error
27 {
28     public:
29 
30         InvalidRecordException() :
31             std::runtime_error("Invalid history record")
32         {
33         }
34 };
35 
36 /**
37  * @class RecordManager
38  *
39  * This class manages the records for the input power history of
40  * a power supply.
41  *
42  * The history is the average and maximum power values across 30s
43  * intervals.  Every 30s, a new record will be available from the
44  * PS.  This class takes that raw PS data and converts it into
45  * something useable by D-Bus.  It ensures the readings are always
46  * sorted newest to oldest, and prunes out the oldest entries when
47  * necessary.  If there is a problem with the ordering IDs coming
48  * from the PS, it will clear out the old records and start over.
49  */
50 class RecordManager
51 {
52     public:
53 
54         static constexpr auto RAW_RECORD_SIZE = 5;
55         static constexpr auto RAW_RECORD_ID_OFFSET = 0;
56         static constexpr auto FIRST_SEQUENCE_ID = 0;
57         static constexpr auto LAST_SEQUENCE_ID = 0xFF;
58 
59         using DBusRecord = std::tuple<uint64_t, int64_t>;
60         using DBusRecordList = std::vector<DBusRecord>;
61 
62         RecordManager() = delete;
63         ~RecordManager() = default;
64         RecordManager(const RecordManager&) = default;
65         RecordManager& operator=(const RecordManager&) = default;
66         RecordManager(RecordManager&&) = default;
67         RecordManager& operator=(RecordManager&&) = default;
68 
69         /**
70          * @brief Constructor
71          *
72          * @param[in] maxRec - the maximum number of history
73          *                     records to keep at a time
74          */
75         RecordManager(size_t maxRec) :
76                 RecordManager(maxRec, LAST_SEQUENCE_ID)
77         {
78         }
79 
80         /**
81          * @brief Constructor
82          *
83          * @param[in] maxRec - the maximum number of history
84          *                     records to keep at a time
85          * @param[in] lastSequenceID - the last sequence ID the power supply
86          *                             will use before starting over
87          */
88         RecordManager(size_t maxRec, size_t lastSequenceID) :
89                 maxRecords(maxRec),
90                 lastSequenceID(lastSequenceID)
91         {
92         }
93 
94         /**
95          * @brief Adds a new entry to the history
96          *
97          * Also checks to see if the old history should be
98          * cleared, such as when there is an invalid record
99          * sequence ID or if there was no data from the PS.
100          *
101          * @param[in] rawRecord - the record data straight
102          *                    from the power supply
103          *
104          * @return bool - If there has been a change to the
105          *                history records that needs to be
106          *                reflected in D-Bus.
107          */
108         bool add(const std::vector<uint8_t>& rawRecord);
109 
110         /**
111          * @brief Returns the history of average input power
112          *        in a representation used by D-Bus.
113          *
114          * @return DBusRecordList - A list of averages with
115          *         a timestamp for each entry.
116          */
117         DBusRecordList getAverageRecords();
118 
119         /**
120          * @brief Returns the history of maximum input power
121          *        in a representation used by D-Bus.
122          *
123          * @return DBusRecordList - A list of maximums with
124          *         a timestamp for each entry.
125          */
126         DBusRecordList getMaximumRecords();
127 
128         /**
129          * @brief Converts a Linear Format power number to an integer
130          *
131          * The PMBus spec describes a 2 byte Linear Format
132          * number that is composed of an exponent and mantissa
133          * in two's complement notation.
134          *
135          * Value = Mantissa * 2**Exponent
136          *
137          * @return int64_t the converted value
138          */
139         static int64_t linearToInteger(uint16_t data);
140 
141         /**
142          * @brief Returns the number of records
143          *
144          * @return size_t - the number of records
145          *
146          */
147         inline size_t getNumRecords() const
148         {
149             return records.size();
150         }
151 
152         /**
153          * @brief Deletes all records
154          */
155         inline void clear()
156         {
157             records.clear();
158         }
159 
160     private:
161 
162         /**
163          * @brief returns the sequence ID from a raw history record
164          *
165          * Throws InvalidRecordException if the data is the wrong length.
166          *
167          * @param[in] data - the raw record data as the PS returns it
168          *
169          * @return size_t - the ID from byte 0
170          */
171         size_t getRawRecordID(const std::vector<uint8_t>& data) const;
172 
173         /**
174          * @brief Creates an instance of a Record from the raw PS data
175          *
176          * @param[in] data - the raw record data as the PS returns it
177          *
178          * @return Record - A filled in Record instance
179          */
180         Record createRecord(const std::vector<uint8_t>& data);
181 
182         /**
183          * @brief The maximum number of entries to keep in the history.
184          *
185          * When a new record is added, the oldest one will be removed.
186          */
187         const size_t maxRecords;
188 
189         /**
190          * @brief The last ID the power supply returns before rolling over
191          *        back to the first ID of 0.
192          */
193         const size_t lastSequenceID;
194 
195         /**
196          * @brief The list of timestamp/average/maximum records.
197          *        Newer records are added to the front, and older ones
198          *        removed from the back.
199          */
200         std::deque<Record> records;
201 };
202 
203 }
204 }
205 }
206