blob: f00e8a095c1ccb7843a972b528ca4f2850f9a93e [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 }
Tomáš Pecka7eb0c422023-04-21 15:36:33 +020037
38 res[componentPrefix + "state/oper-state"] = "enabled";
Tomáš Pecka339bc672020-11-11 15:59:03 +010039}
40
41/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
42void addSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value)
43{
44 const auto componentPrefix = xpathForComponent(componentName);
45 res[componentPrefix + "sensor-data/value"] = value;
46}
47}
48
49namespace velia::ietf_hardware {
50
51IETFHardware::IETFHardware() = default;
52
53IETFHardware::~IETFHardware() = default;
54
55std::map<std::string, std::string> IETFHardware::process()
56{
57 std::map<std::string, std::string> res;
58
59 for (auto& dataReader : m_callbacks) {
60 res.merge(dataReader());
61 }
62
63 res[ietfHardwareStatePrefix + "/last-change"] = velia::utils::yangTimeFormat(std::chrono::system_clock::now());
64 return res;
65}
66
67void IETFHardware::registerDataReader(const DataReader& callable)
68{
69 m_callbacks.push_back(callable);
70}
71
72/** @brief A namespace containing predefined data readers for IETFHardware class.
73 * @see IETFHardware for more information
74 */
75namespace data_reader {
76
77DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
78 : m_componentName(std::move(componentName))
79 , m_parent(std::move(parent))
80{
81}
82
83/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
84 * @param componentName the name of the component in the resulting tree
85 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
86 * @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.
87 */
88StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
89 : DataReader(std::move(componentName), std::move(parent))
90{
91 addComponent(m_staticData,
92 m_componentName,
93 m_parent,
94 dataTree);
95}
96
97DataTree StaticData::operator()() const { return m_staticData; }
98
99Fans::Fans(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount)
100 : DataReader(std::move(componentName), std::move(parent))
101 , m_hwmon(std::move(hwmon))
102 , m_fanChannelsCount(fanChannelsCount)
103{
104 // fans
105 addComponent(m_staticData,
106 m_componentName,
107 m_parent,
108 DataTree {
109 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
110 });
111
112 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
113 // fans -> fan_i
114 addComponent(m_staticData,
115 m_componentName + ":fan" + std::to_string(i),
116 m_componentName,
117 DataTree {
118 {"class", "iana-hardware:fan"},
119 });
120
121 // fans -> fan_i -> sensor-data
122 addComponent(m_staticData,
123 m_componentName + ":fan" + std::to_string(i) + ":rpm",
124 m_componentName + ":fan" + std::to_string(i),
125 DataTree {
126 {"class", "iana-hardware:sensor"},
127 {"sensor-data/value-type", "rpm"},
128 {"sensor-data/value-scale", "units"},
129 {"sensor-data/value-precision", "0"},
130 {"sensor-data/oper-status", "ok"},
131 });
132 }
133}
134
135DataTree Fans::operator()() const
136{
137 DataTree res(m_staticData);
138
Tomáš Pecka339bc672020-11-11 15:59:03 +0100139 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
140 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
141 const auto attribute = "fan"s + std::to_string(i) + "_input";
142
Václav Kubernátb0939dd2021-04-28 04:08:48 +0200143 addSensorValue(res, sensorComponentName, std::to_string(m_hwmon->attribute(attribute)));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100144 }
145
146 return res;
147}
148
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200149std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
150{
151 switch (type) {
152 case SensorType::Temperature:
153 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100154 case SensorType::Current:
155 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
156 case SensorType::Power:
157 return "power"s + std::to_string(sysfsChannelNr) + "_input";
158 case SensorType::VoltageAC:
159 case SensorType::VoltageDC:
160 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200161 }
162
163 __builtin_unreachable();
164}
165
166template <SensorType TYPE> const DataTree sysfsStaticData;
167template <> const DataTree sysfsStaticData<SensorType::Temperature> = {
168 {"class", "iana-hardware:sensor"},
169 {"sensor-data/value-type", "celsius"},
170 {"sensor-data/value-scale", "milli"},
171 {"sensor-data/value-precision", "0"},
172 {"sensor-data/oper-status", "ok"},
173};
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100174template <> const DataTree sysfsStaticData<SensorType::Current> = {
175 {"class", "iana-hardware:sensor"},
176 {"sensor-data/value-type", "amperes"},
177 {"sensor-data/value-scale", "milli"},
178 {"sensor-data/value-precision", "0"},
179 {"sensor-data/oper-status", "ok"},
180};
181template <> const DataTree sysfsStaticData<SensorType::Power> = {
182 {"class", "iana-hardware:sensor"},
183 {"sensor-data/value-type", "watts"},
184 {"sensor-data/value-scale", "micro"},
185 {"sensor-data/value-precision", "0"},
186 {"sensor-data/oper-status", "ok"},
187};
188template <> const DataTree sysfsStaticData<SensorType::VoltageAC> = {
189 {"class", "iana-hardware:sensor"},
190 {"sensor-data/value-type", "volts-AC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200191 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100192 {"sensor-data/value-precision", "0"},
193 {"sensor-data/oper-status", "ok"}
194};
195template <> const DataTree sysfsStaticData<SensorType::VoltageDC> = {
196 {"class", "iana-hardware:sensor"},
197 {"sensor-data/value-type", "volts-DC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200198 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100199 {"sensor-data/value-precision", "0"},
200 {"sensor-data/oper-status", "ok"}
201};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200202
203template <SensorType TYPE>
204SysfsValue<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 +0100205 : DataReader(std::move(componentName), std::move(parent))
206 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200207 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100208{
209 addComponent(m_staticData,
210 m_componentName,
211 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200212 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100213}
214
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200215template <SensorType TYPE>
216DataTree SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100217{
218 DataTree res(m_staticData);
219
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200220 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100221 addSensorValue(res, m_componentName, std::to_string(sensorValue));
222
223 return res;
224}
225
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100226template struct SysfsValue<SensorType::Current>;
227template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200228template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100229template struct SysfsValue<SensorType::VoltageAC>;
230template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200231
Tomáš Pecka339bc672020-11-11 15:59:03 +0100232EMMC::EMMC(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc)
233 : DataReader(std::move(componentName), std::move(parent))
234 , m_emmc(std::move(emmc))
235{
236 auto emmcAttrs = m_emmc->attributes();
237
238 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
239 std::string mfgDate = emmcAttrs.at("date");
Václav Kubernát069d3a92021-11-14 12:37:46 +0100240 // FIXME: replace this with C++20's <chrono> calendar features once Buildroot gets GCC 11+
241 struct tm date;
242 memset(&date, 0, sizeof(date));
243 date.tm_mday = 1;
244 date.tm_mon = std::stoul(mfgDate.substr(0, 2)) - 1;
245 date.tm_year = std::stoi(mfgDate.substr(3, 4)) - 1900;
246 date.tm_isdst = -1;
247 mfgDate = velia::utils::yangTimeFormat(std::chrono::system_clock::from_time_t(timegm(&date)));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100248
249 addComponent(m_staticData,
250 m_componentName,
251 m_parent,
252 DataTree {
253 {"class", "iana-hardware:module"},
254 {"mfg-date", mfgDate},
255 {"serial-num", emmcAttrs.at("serial")},
256 {"model-name", emmcAttrs.at("name")},
257 });
258
259 addComponent(m_staticData,
260 m_componentName + ":lifetime",
261 m_componentName,
262 DataTree {
263 {"class", "iana-hardware:sensor"},
264 {"sensor-data/value-type", "other"},
265 {"sensor-data/value-scale", "units"},
266 {"sensor-data/value-precision", "0"},
267 {"sensor-data/oper-status", "ok"},
268 {"sensor-data/units-display", "percent"s},
269 });
270}
271
272DataTree EMMC::operator()() const
273{
274 DataTree res(m_staticData);
275
276 auto emmcAttrs = m_emmc->attributes();
277 addSensorValue(res, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
278
279 return res;
280}
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200281
282void Group::registerDataReader(const IETFHardware::DataReader& callable)
283{
284 m_readers.emplace_back(callable);
285}
286
287DataTree Group::operator()() const
288{
289 DataTree res;
290 for (const auto& reader : m_readers) {
291 res.merge(reader());
292 }
293 return res;
294}
295
Tomáš Pecka339bc672020-11-11 15:59:03 +0100296}
297}