1 /**
2  * Copyright © 2017 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "record_manager.hpp"
17 
18 #include <math.h>
19 
20 #include <chrono>
21 #include <phosphor-logging/log.hpp>
22 
23 namespace witherspoon
24 {
25 namespace power
26 {
27 namespace history
28 {
29 
30 using namespace phosphor::logging;
31 
32 bool RecordManager::add(const std::vector<uint8_t>& rawRecord)
33 {
34     if (rawRecord.size() == 0)
35     {
36         // The PS has no data - either the power supply just started up,
37         // or it just got a SYNC.  Clear the history.
38         records.clear();
39         return true;
40     }
41 
42     try
43     {
44         // Peek at the ID to see if more processing is needed.
45         auto id = getRawRecordID(rawRecord);
46 
47         if (!records.empty())
48         {
49             auto previousID = std::get<recIDPos>(records.front());
50 
51             // Already have this record.  Done.
52             if (previousID == id)
53             {
54                 return false;
55             }
56 
57             // Check that the sequence ID is in order.
58             // If not, clear out current list.
59             if ((previousID + 1) != id)
60             {
61                 // If it just rolled over from 0xFF to 0x00, then no
62                 // need to clear.  If we see a 0 seemingly out of nowhere,
63                 // then it was a sync so clear the old records.
64                 auto rolledOver =
65                     (previousID == lastSequenceID) && (id == FIRST_SEQUENCE_ID);
66 
67                 if (!rolledOver)
68                 {
69                     if (id != FIRST_SEQUENCE_ID)
70                     {
71                         log<level::INFO>(
72                             "Noncontiguous INPUT_HISTORY sequence ID "
73                             "found. Clearing old entries",
74                             entry("OLD_ID=%ld", previousID),
75                             entry("NEW_ID=%ld", id));
76                     }
77                     records.clear();
78                 }
79             }
80         }
81 
82         records.push_front(std::move(createRecord(rawRecord)));
83 
84         // If no more should be stored, prune the oldest
85         if (records.size() > maxRecords)
86         {
87             records.pop_back();
88         }
89     }
90     catch (InvalidRecordException& e)
91     {
92         return false;
93     }
94 
95     return true;
96 }
97 
98 auto RecordManager::getAverageRecords() -> DBusRecordList
99 {
100     DBusRecordList list;
101 
102     for (const auto& r : records)
103     {
104         list.emplace_back(std::get<recTimePos>(r), std::get<recAvgPos>(r));
105     }
106 
107     return list;
108 }
109 
110 auto RecordManager::getMaximumRecords() -> DBusRecordList
111 {
112     DBusRecordList list;
113 
114     for (const auto& r : records)
115     {
116         list.emplace_back(std::get<recTimePos>(r), std::get<recMaxPos>(r));
117     }
118 
119     return list;
120 }
121 
122 size_t RecordManager::getRawRecordID(const std::vector<uint8_t>& data) const
123 {
124     if (data.size() != RAW_RECORD_SIZE)
125     {
126         log<level::ERR>("Invalid INPUT_HISTORY size",
127                         entry("SIZE=%d", data.size()));
128         throw InvalidRecordException{};
129     }
130 
131     return data[RAW_RECORD_ID_OFFSET];
132 }
133 
134 Record RecordManager::createRecord(const std::vector<uint8_t>& data)
135 {
136     // The raw record format is:
137     //  0xAABBCCDDEE
138     //
139     //  where:
140     //    0xAA = sequence ID
141     //    0xBBCC = average power in linear format (0xCC = MSB)
142     //    0xDDEE = maximum power in linear format (0xEE = MSB)
143     auto id = getRawRecordID(data);
144 
145     auto time = std::chrono::duration_cast<std::chrono::milliseconds>(
146                     std::chrono::system_clock::now().time_since_epoch())
147                     .count();
148 
149     auto val = static_cast<uint16_t>(data[2]) << 8 | data[1];
150     auto averagePower = linearToInteger(val);
151 
152     val = static_cast<uint16_t>(data[4]) << 8 | data[3];
153     auto maxPower = linearToInteger(val);
154 
155     return Record{id, time, averagePower, maxPower};
156 }
157 
158 int64_t RecordManager::linearToInteger(uint16_t data)
159 {
160     // The exponent is the first 5 bits, followed by 11 bits of mantissa.
161     int8_t exponent = (data & 0xF800) >> 11;
162     int16_t mantissa = (data & 0x07FF);
163 
164     // If exponent's MSB on, then it's negative.
165     // Convert from two's complement.
166     if (exponent & 0x10)
167     {
168         exponent = (~exponent) & 0x1F;
169         exponent = (exponent + 1) * -1;
170     }
171 
172     // If mantissa's MSB on, then it's negative.
173     // Convert from two's complement.
174     if (mantissa & 0x400)
175     {
176         mantissa = (~mantissa) & 0x07FF;
177         mantissa = (mantissa + 1) * -1;
178     }
179 
180     auto value = static_cast<float>(mantissa) * pow(2, exponent);
181     return value;
182 }
183 
184 } // namespace history
185 } // namespace power
186 } // namespace witherspoon
187