blob: 9a2edc0ddafc366f6809f38a79016e62e819e4e2 [file] [log] [blame]
Tomáš Pecka339bc672020-11-11 15:59:03 +01001/*
2 * Copyright (C) 2016-2020 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
5 *
6 */
7
Václav Kubernát069d3a92021-11-14 12:37:46 +01008#include <chrono>
Tomáš Pecka339bc672020-11-11 15:59:03 +01009#include <utility>
10#include "IETFHardware.h"
11#include "utils/log.h"
12#include "utils/time.h"
13
14using namespace std::literals;
15
16namespace {
17
Tomáš Pecka83b62e12020-12-16 14:50:49 +010018static const std::string ietfHardwareStatePrefix = "/ietf-hardware:hardware";
Tomáš Pecka339bc672020-11-11 15:59:03 +010019
20/** @brief Constructs a full XPath for a specific component */
21std::string xpathForComponent(const std::string& componentName)
22{
23 return ietfHardwareStatePrefix + "/component[name='" + componentName + "']/";
24}
25
26/** @brief Prefix all properties from values DataTree with a component name (calculated from @p componentName) and push them into the DataTree */
27void addComponent(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::optional<std::string>& parent, const velia::ietf_hardware::DataTree& values)
28{
29 auto componentPrefix = xpathForComponent(componentName);
30
31 if (parent) {
32 res[componentPrefix + "parent"] = *parent;
33 }
34 for (const auto& [k, v] : values) {
35 res[componentPrefix + k] = v;
36 }
37}
38
39/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
40void addSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value)
41{
42 const auto componentPrefix = xpathForComponent(componentName);
43 res[componentPrefix + "sensor-data/value"] = value;
44}
45}
46
47namespace velia::ietf_hardware {
48
49IETFHardware::IETFHardware() = default;
50
51IETFHardware::~IETFHardware() = default;
52
53std::map<std::string, std::string> IETFHardware::process()
54{
55 std::map<std::string, std::string> res;
56
57 for (auto& dataReader : m_callbacks) {
58 res.merge(dataReader());
59 }
60
61 res[ietfHardwareStatePrefix + "/last-change"] = velia::utils::yangTimeFormat(std::chrono::system_clock::now());
62 return res;
63}
64
65void IETFHardware::registerDataReader(const DataReader& callable)
66{
67 m_callbacks.push_back(callable);
68}
69
70/** @brief A namespace containing predefined data readers for IETFHardware class.
71 * @see IETFHardware for more information
72 */
73namespace data_reader {
74
75DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
76 : m_componentName(std::move(componentName))
77 , m_parent(std::move(parent))
78{
79}
80
81/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
82 * @param componentName the name of the component in the resulting tree
83 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
84 * @param dataTree static data to insert into the resulting tree. The dataTree keys should only contain the YANG node name, not full XPath. The full XPath is constructed from @componentName and the map key.
85 */
86StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
87 : DataReader(std::move(componentName), std::move(parent))
88{
89 addComponent(m_staticData,
90 m_componentName,
91 m_parent,
92 dataTree);
93}
94
95DataTree StaticData::operator()() const { return m_staticData; }
96
97Fans::Fans(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount)
98 : DataReader(std::move(componentName), std::move(parent))
99 , m_hwmon(std::move(hwmon))
100 , m_fanChannelsCount(fanChannelsCount)
101{
102 // fans
103 addComponent(m_staticData,
104 m_componentName,
105 m_parent,
106 DataTree {
107 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
108 });
109
110 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
111 // fans -> fan_i
112 addComponent(m_staticData,
113 m_componentName + ":fan" + std::to_string(i),
114 m_componentName,
115 DataTree {
116 {"class", "iana-hardware:fan"},
117 });
118
119 // fans -> fan_i -> sensor-data
120 addComponent(m_staticData,
121 m_componentName + ":fan" + std::to_string(i) + ":rpm",
122 m_componentName + ":fan" + std::to_string(i),
123 DataTree {
124 {"class", "iana-hardware:sensor"},
125 {"sensor-data/value-type", "rpm"},
126 {"sensor-data/value-scale", "units"},
127 {"sensor-data/value-precision", "0"},
128 {"sensor-data/oper-status", "ok"},
129 });
130 }
131}
132
133DataTree Fans::operator()() const
134{
135 DataTree res(m_staticData);
136
Tomáš Pecka339bc672020-11-11 15:59:03 +0100137 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
138 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
139 const auto attribute = "fan"s + std::to_string(i) + "_input";
140
Václav Kubernátb0939dd2021-04-28 04:08:48 +0200141 addSensorValue(res, sensorComponentName, std::to_string(m_hwmon->attribute(attribute)));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100142 }
143
144 return res;
145}
146
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200147std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
148{
149 switch (type) {
150 case SensorType::Temperature:
151 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100152 case SensorType::Current:
153 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
154 case SensorType::Power:
155 return "power"s + std::to_string(sysfsChannelNr) + "_input";
156 case SensorType::VoltageAC:
157 case SensorType::VoltageDC:
158 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200159 }
160
161 __builtin_unreachable();
162}
163
164template <SensorType TYPE> const DataTree sysfsStaticData;
165template <> const DataTree sysfsStaticData<SensorType::Temperature> = {
166 {"class", "iana-hardware:sensor"},
167 {"sensor-data/value-type", "celsius"},
168 {"sensor-data/value-scale", "milli"},
169 {"sensor-data/value-precision", "0"},
170 {"sensor-data/oper-status", "ok"},
171};
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100172template <> const DataTree sysfsStaticData<SensorType::Current> = {
173 {"class", "iana-hardware:sensor"},
174 {"sensor-data/value-type", "amperes"},
175 {"sensor-data/value-scale", "milli"},
176 {"sensor-data/value-precision", "0"},
177 {"sensor-data/oper-status", "ok"},
178};
179template <> const DataTree sysfsStaticData<SensorType::Power> = {
180 {"class", "iana-hardware:sensor"},
181 {"sensor-data/value-type", "watts"},
182 {"sensor-data/value-scale", "micro"},
183 {"sensor-data/value-precision", "0"},
184 {"sensor-data/oper-status", "ok"},
185};
186template <> const DataTree sysfsStaticData<SensorType::VoltageAC> = {
187 {"class", "iana-hardware:sensor"},
188 {"sensor-data/value-type", "volts-AC"},
189 {"sensor-data/value-scale", "micro"},
190 {"sensor-data/value-precision", "0"},
191 {"sensor-data/oper-status", "ok"}
192};
193template <> const DataTree sysfsStaticData<SensorType::VoltageDC> = {
194 {"class", "iana-hardware:sensor"},
195 {"sensor-data/value-type", "volts-DC"},
196 {"sensor-data/value-scale", "micro"},
197 {"sensor-data/value-precision", "0"},
198 {"sensor-data/oper-status", "ok"}
199};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200200
201template <SensorType TYPE>
202SysfsValue<TYPE>::SysfsValue(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr)
Tomáš Pecka339bc672020-11-11 15:59:03 +0100203 : DataReader(std::move(componentName), std::move(parent))
204 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200205 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100206{
207 addComponent(m_staticData,
208 m_componentName,
209 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200210 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100211}
212
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200213template <SensorType TYPE>
214DataTree SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100215{
216 DataTree res(m_staticData);
217
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200218 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100219 addSensorValue(res, m_componentName, std::to_string(sensorValue));
220
221 return res;
222}
223
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100224template struct SysfsValue<SensorType::Current>;
225template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200226template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100227template struct SysfsValue<SensorType::VoltageAC>;
228template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200229
Tomáš Pecka339bc672020-11-11 15:59:03 +0100230EMMC::EMMC(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc)
231 : DataReader(std::move(componentName), std::move(parent))
232 , m_emmc(std::move(emmc))
233{
234 auto emmcAttrs = m_emmc->attributes();
235
236 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
237 std::string mfgDate = emmcAttrs.at("date");
Václav Kubernát069d3a92021-11-14 12:37:46 +0100238 // FIXME: replace this with C++20's <chrono> calendar features once Buildroot gets GCC 11+
239 struct tm date;
240 memset(&date, 0, sizeof(date));
241 date.tm_mday = 1;
242 date.tm_mon = std::stoul(mfgDate.substr(0, 2)) - 1;
243 date.tm_year = std::stoi(mfgDate.substr(3, 4)) - 1900;
244 date.tm_isdst = -1;
245 mfgDate = velia::utils::yangTimeFormat(std::chrono::system_clock::from_time_t(timegm(&date)));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100246
247 addComponent(m_staticData,
248 m_componentName,
249 m_parent,
250 DataTree {
251 {"class", "iana-hardware:module"},
252 {"mfg-date", mfgDate},
253 {"serial-num", emmcAttrs.at("serial")},
254 {"model-name", emmcAttrs.at("name")},
255 });
256
257 addComponent(m_staticData,
258 m_componentName + ":lifetime",
259 m_componentName,
260 DataTree {
261 {"class", "iana-hardware:sensor"},
262 {"sensor-data/value-type", "other"},
263 {"sensor-data/value-scale", "units"},
264 {"sensor-data/value-precision", "0"},
265 {"sensor-data/oper-status", "ok"},
266 {"sensor-data/units-display", "percent"s},
267 });
268}
269
270DataTree EMMC::operator()() const
271{
272 DataTree res(m_staticData);
273
274 auto emmcAttrs = m_emmc->attributes();
275 addSensorValue(res, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
276
277 return res;
278}
279}
280}