1 /*
2 // Copyright (c) 2018 Intel 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 #pragma once
17
18 #include "app.hpp"
19 #include "dbus_singleton.hpp"
20 #include "dbus_utility.hpp"
21 #include "generated/enums/resource.hpp"
22 #include "generated/enums/sensor.hpp"
23 #include "query.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "str_utility.hpp"
26 #include "utils/dbus_utils.hpp"
27 #include "utils/json_utils.hpp"
28 #include "utils/query_param.hpp"
29
30 #include <boost/system/error_code.hpp>
31 #include <boost/url/format.hpp>
32 #include <sdbusplus/asio/property.hpp>
33 #include <sdbusplus/unpack_properties.hpp>
34
35 #include <array>
36 #include <cmath>
37 #include <iterator>
38 #include <limits>
39 #include <map>
40 #include <ranges>
41 #include <set>
42 #include <string>
43 #include <string_view>
44 #include <utility>
45 #include <variant>
46
47 namespace redfish
48 {
49
50 namespace sensors
51 {
52 namespace node
53 {
54 static constexpr std::string_view power = "Power";
55 static constexpr std::string_view sensors = "Sensors";
56 static constexpr std::string_view thermal = "Thermal";
57 } // namespace node
58
59 // clang-format off
60 namespace dbus
61 {
62 constexpr auto powerPaths = std::to_array<std::string_view>({
63 "/xyz/openbmc_project/sensors/voltage",
64 "/xyz/openbmc_project/sensors/power"
65 });
66
getSensorPaths()67 constexpr auto getSensorPaths(){
68 if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){
69 return std::to_array<std::string_view>({
70 "/xyz/openbmc_project/sensors/power",
71 "/xyz/openbmc_project/sensors/current",
72 "/xyz/openbmc_project/sensors/airflow",
73 "/xyz/openbmc_project/sensors/humidity",
74 "/xyz/openbmc_project/sensors/voltage",
75 "/xyz/openbmc_project/sensors/fan_tach",
76 "/xyz/openbmc_project/sensors/temperature",
77 "/xyz/openbmc_project/sensors/fan_pwm",
78 "/xyz/openbmc_project/sensors/altitude",
79 "/xyz/openbmc_project/sensors/energy",
80 "/xyz/openbmc_project/sensors/utilization"});
81 } else {
82 return std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power",
83 "/xyz/openbmc_project/sensors/current",
84 "/xyz/openbmc_project/sensors/airflow",
85 "/xyz/openbmc_project/sensors/humidity",
86 "/xyz/openbmc_project/sensors/utilization"});
87 }
88 }
89
90 constexpr auto sensorPaths = getSensorPaths();
91
92 constexpr auto thermalPaths = std::to_array<std::string_view>({
93 "/xyz/openbmc_project/sensors/fan_tach",
94 "/xyz/openbmc_project/sensors/temperature",
95 "/xyz/openbmc_project/sensors/fan_pwm"
96 });
97
98 } // namespace dbus
99 // clang-format on
100
101 using sensorPair =
102 std::pair<std::string_view, std::span<const std::string_view>>;
103 static constexpr std::array<sensorPair, 3> paths = {
104 {{node::power, dbus::powerPaths},
105 {node::sensors, dbus::sensorPaths},
106 {node::thermal, dbus::thermalPaths}}};
107
toReadingType(std::string_view sensorType)108 inline sensor::ReadingType toReadingType(std::string_view sensorType)
109 {
110 if (sensorType == "voltage")
111 {
112 return sensor::ReadingType::Voltage;
113 }
114 if (sensorType == "power")
115 {
116 return sensor::ReadingType::Power;
117 }
118 if (sensorType == "current")
119 {
120 return sensor::ReadingType::Current;
121 }
122 if (sensorType == "fan_tach")
123 {
124 return sensor::ReadingType::Rotational;
125 }
126 if (sensorType == "temperature")
127 {
128 return sensor::ReadingType::Temperature;
129 }
130 if (sensorType == "fan_pwm" || sensorType == "utilization")
131 {
132 return sensor::ReadingType::Percent;
133 }
134 if (sensorType == "humidity")
135 {
136 return sensor::ReadingType::Humidity;
137 }
138 if (sensorType == "altitude")
139 {
140 return sensor::ReadingType::Altitude;
141 }
142 if (sensorType == "airflow")
143 {
144 return sensor::ReadingType::AirFlow;
145 }
146 if (sensorType == "energy")
147 {
148 return sensor::ReadingType::EnergyJoules;
149 }
150 return sensor::ReadingType::Invalid;
151 }
152
toReadingUnits(std::string_view sensorType)153 inline std::string_view toReadingUnits(std::string_view sensorType)
154 {
155 if (sensorType == "voltage")
156 {
157 return "V";
158 }
159 if (sensorType == "power")
160 {
161 return "W";
162 }
163 if (sensorType == "current")
164 {
165 return "A";
166 }
167 if (sensorType == "fan_tach")
168 {
169 return "RPM";
170 }
171 if (sensorType == "temperature")
172 {
173 return "Cel";
174 }
175 if (sensorType == "fan_pwm" || sensorType == "utilization" ||
176 sensorType == "humidity")
177 {
178 return "%";
179 }
180 if (sensorType == "altitude")
181 {
182 return "m";
183 }
184 if (sensorType == "airflow")
185 {
186 return "cft_i/min";
187 }
188 if (sensorType == "energy")
189 {
190 return "J";
191 }
192 return "";
193 }
194 } // namespace sensors
195
196 /**
197 * SensorsAsyncResp
198 * Gathers data needed for response processing after async calls are done
199 */
200 class SensorsAsyncResp
201 {
202 public:
203 using DataCompleteCb = std::function<void(
204 const boost::beast::http::status status,
205 const std::map<std::string, std::string>& uriToDbus)>;
206
207 struct SensorData
208 {
209 const std::string name;
210 std::string uri;
211 const std::string dbusPath;
212 };
213
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,std::string_view subNode)214 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
215 const std::string& chassisIdIn,
216 std::span<const std::string_view> typesIn,
217 std::string_view subNode) :
218 asyncResp(asyncRespIn),
219 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode),
220 efficientExpand(false)
221 {}
222
223 // Store extra data about sensor mapping and return it in callback
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,std::string_view subNode,DataCompleteCb && creationComplete)224 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
225 const std::string& chassisIdIn,
226 std::span<const std::string_view> typesIn,
227 std::string_view subNode,
228 DataCompleteCb&& creationComplete) :
229 asyncResp(asyncRespIn),
230 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode),
231 efficientExpand(false), metadata{std::vector<SensorData>()},
232 dataComplete{std::move(creationComplete)}
233 {}
234
235 // sensor collections expand
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,const std::string_view & subNode,bool efficientExpandIn)236 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
237 const std::string& chassisIdIn,
238 std::span<const std::string_view> typesIn,
239 const std::string_view& subNode, bool efficientExpandIn) :
240 asyncResp(asyncRespIn),
241 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode),
242 efficientExpand(efficientExpandIn)
243 {}
244
~SensorsAsyncResp()245 ~SensorsAsyncResp()
246 {
247 if (asyncResp->res.result() ==
248 boost::beast::http::status::internal_server_error)
249 {
250 // Reset the json object to clear out any data that made it in
251 // before the error happened todo(ed) handle error condition with
252 // proper code
253 asyncResp->res.jsonValue = nlohmann::json::object();
254 }
255
256 if (dataComplete && metadata)
257 {
258 std::map<std::string, std::string> map;
259 if (asyncResp->res.result() == boost::beast::http::status::ok)
260 {
261 for (auto& sensor : *metadata)
262 {
263 map.emplace(sensor.uri, sensor.dbusPath);
264 }
265 }
266 dataComplete(asyncResp->res.result(), map);
267 }
268 }
269
270 SensorsAsyncResp(const SensorsAsyncResp&) = delete;
271 SensorsAsyncResp(SensorsAsyncResp&&) = delete;
272 SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete;
273 SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete;
274
addMetadata(const nlohmann::json & sensorObject,const std::string & dbusPath)275 void addMetadata(const nlohmann::json& sensorObject,
276 const std::string& dbusPath)
277 {
278 if (metadata)
279 {
280 metadata->emplace_back(SensorData{
281 sensorObject["Name"], sensorObject["@odata.id"], dbusPath});
282 }
283 }
284
updateUri(const std::string & name,const std::string & uri)285 void updateUri(const std::string& name, const std::string& uri)
286 {
287 if (metadata)
288 {
289 for (auto& sensor : *metadata)
290 {
291 if (sensor.name == name)
292 {
293 sensor.uri = uri;
294 }
295 }
296 }
297 }
298
299 const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
300 const std::string chassisId;
301 const std::span<const std::string_view> types;
302 const std::string chassisSubNode;
303 const bool efficientExpand;
304
305 private:
306 std::optional<std::vector<SensorData>> metadata;
307 DataCompleteCb dataComplete;
308 };
309
310 /**
311 * Possible states for physical inventory leds
312 */
313 enum class LedState
314 {
315 OFF,
316 ON,
317 BLINK,
318 UNKNOWN
319 };
320
321 /**
322 * D-Bus inventory item associated with one or more sensors.
323 */
324 class InventoryItem
325 {
326 public:
InventoryItem(const std::string & objPath)327 explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
328 {
329 // Set inventory item name to last node of object path
330 sdbusplus::message::object_path path(objectPath);
331 name = path.filename();
332 if (name.empty())
333 {
334 BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
335 }
336 }
337
338 std::string objectPath;
339 std::string name;
340 bool isPresent = true;
341 bool isFunctional = true;
342 bool isPowerSupply = false;
343 int powerSupplyEfficiencyPercent = -1;
344 std::string manufacturer;
345 std::string model;
346 std::string partNumber;
347 std::string serialNumber;
348 std::set<std::string> sensors;
349 std::string ledObjectPath;
350 LedState ledState = LedState::UNKNOWN;
351 };
352
353 /**
354 * @brief Get objects with connection necessary for sensors
355 * @param SensorsAsyncResp Pointer to object holding response data
356 * @param sensorNames Sensors retrieved from chassis
357 * @param callback Callback for processing gathered connections
358 */
359 template <typename Callback>
getObjectsWithConnection(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)360 void getObjectsWithConnection(
361 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
362 const std::shared_ptr<std::set<std::string>>& sensorNames,
363 Callback&& callback)
364 {
365 BMCWEB_LOG_DEBUG("getObjectsWithConnection enter");
366 const std::string path = "/xyz/openbmc_project/sensors";
367 constexpr std::array<std::string_view, 1> interfaces = {
368 "xyz.openbmc_project.Sensor.Value"};
369
370 // Make call to ObjectMapper to find all sensors objects
371 dbus::utility::getSubTree(
372 path, 2, interfaces,
373 [callback = std::forward<Callback>(callback), sensorsAsyncResp,
374 sensorNames](const boost::system::error_code& ec,
375 const dbus::utility::MapperGetSubTreeResponse& subtree) {
376 // Response handler for parsing objects subtree
377 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter");
378 if (ec)
379 {
380 messages::internalError(sensorsAsyncResp->asyncResp->res);
381 BMCWEB_LOG_ERROR(
382 "getObjectsWithConnection resp_handler: Dbus error {}", ec);
383 return;
384 }
385
386 BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size());
387
388 // Make unique list of connections only for requested sensor types and
389 // found in the chassis
390 std::set<std::string> connections;
391 std::set<std::pair<std::string, std::string>> objectsWithConnection;
392
393 BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size());
394 for (const std::string& tsensor : *sensorNames)
395 {
396 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor);
397 }
398
399 for (const std::pair<
400 std::string,
401 std::vector<std::pair<std::string, std::vector<std::string>>>>&
402 object : subtree)
403 {
404 if (sensorNames->find(object.first) != sensorNames->end())
405 {
406 for (const std::pair<std::string, std::vector<std::string>>&
407 objData : object.second)
408 {
409 BMCWEB_LOG_DEBUG("Adding connection: {}", objData.first);
410 connections.insert(objData.first);
411 objectsWithConnection.insert(
412 std::make_pair(object.first, objData.first));
413 }
414 }
415 }
416 BMCWEB_LOG_DEBUG("Found {} connections", connections.size());
417 callback(std::move(connections), std::move(objectsWithConnection));
418 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit");
419 });
420 BMCWEB_LOG_DEBUG("getObjectsWithConnection exit");
421 }
422
423 /**
424 * @brief Create connections necessary for sensors
425 * @param SensorsAsyncResp Pointer to object holding response data
426 * @param sensorNames Sensors retrieved from chassis
427 * @param callback Callback for processing gathered connections
428 */
429 template <typename Callback>
getConnections(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> sensorNames,Callback && callback)430 void getConnections(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
431 const std::shared_ptr<std::set<std::string>> sensorNames,
432 Callback&& callback)
433 {
434 auto objectsWithConnectionCb =
435 [callback = std::forward<Callback>(callback)](
436 const std::set<std::string>& connections,
437 const std::set<std::pair<std::string, std::string>>&
438 /*objectsWithConnection*/) { callback(connections); };
439 getObjectsWithConnection(sensorsAsyncResp, sensorNames,
440 std::move(objectsWithConnectionCb));
441 }
442
443 /**
444 * @brief Shrinks the list of sensors for processing
445 * @param SensorsAysncResp The class holding the Redfish response
446 * @param allSensors A list of all the sensors associated to the
447 * chassis element (i.e. baseboard, front panel, etc...)
448 * @param activeSensors A list that is a reduction of the incoming
449 * allSensors list. Eliminate Thermal sensors when a Power request is
450 * made, and eliminate Power sensors when a Thermal request is made.
451 */
reduceSensorList(crow::Response & res,std::string_view chassisSubNode,std::span<const std::string_view> sensorTypes,const std::vector<std::string> * allSensors,const std::shared_ptr<std::set<std::string>> & activeSensors)452 inline void reduceSensorList(
453 crow::Response& res, std::string_view chassisSubNode,
454 std::span<const std::string_view> sensorTypes,
455 const std::vector<std::string>* allSensors,
456 const std::shared_ptr<std::set<std::string>>& activeSensors)
457 {
458 if ((allSensors == nullptr) || (activeSensors == nullptr))
459 {
460 messages::resourceNotFound(res, chassisSubNode,
461 chassisSubNode == sensors::node::thermal
462 ? "Temperatures"
463 : "Voltages");
464
465 return;
466 }
467 if (allSensors->empty())
468 {
469 // Nothing to do, the activeSensors object is also empty
470 return;
471 }
472
473 for (std::string_view type : sensorTypes)
474 {
475 for (const std::string& sensor : *allSensors)
476 {
477 if (sensor.starts_with(type))
478 {
479 activeSensors->emplace(sensor);
480 }
481 }
482 }
483 }
484
485 /*
486 *Populates the top level collection for a given subnode. Populates
487 *SensorCollection, Power, or Thermal schemas.
488 *
489 * */
populateChassisNode(nlohmann::json & jsonValue,std::string_view chassisSubNode)490 inline void populateChassisNode(nlohmann::json& jsonValue,
491 std::string_view chassisSubNode)
492 {
493 if (chassisSubNode == sensors::node::power)
494 {
495 jsonValue["@odata.type"] = "#Power.v1_5_2.Power";
496 }
497 else if (chassisSubNode == sensors::node::thermal)
498 {
499 jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal";
500 jsonValue["Fans"] = nlohmann::json::array();
501 jsonValue["Temperatures"] = nlohmann::json::array();
502 }
503 else if (chassisSubNode == sensors::node::sensors)
504 {
505 jsonValue["@odata.type"] = "#SensorCollection.SensorCollection";
506 jsonValue["Description"] = "Collection of Sensors for this Chassis";
507 jsonValue["Members"] = nlohmann::json::array();
508 jsonValue["Members@odata.count"] = 0;
509 }
510
511 if (chassisSubNode != sensors::node::sensors)
512 {
513 jsonValue["Id"] = chassisSubNode;
514 }
515 jsonValue["Name"] = chassisSubNode;
516 }
517
518 /**
519 * @brief Retrieves requested chassis sensors and redundancy data from DBus .
520 * @param SensorsAsyncResp Pointer to object holding response data
521 * @param callback Callback for next step in gathered sensor processing
522 */
523 template <typename Callback>
getChassis(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view chassisId,std::string_view chassisSubNode,std::span<const std::string_view> sensorTypes,Callback && callback)524 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
525 std::string_view chassisId, std::string_view chassisSubNode,
526 std::span<const std::string_view> sensorTypes,
527 Callback&& callback)
528 {
529 BMCWEB_LOG_DEBUG("getChassis enter");
530 constexpr std::array<std::string_view, 2> interfaces = {
531 "xyz.openbmc_project.Inventory.Item.Board",
532 "xyz.openbmc_project.Inventory.Item.Chassis"};
533
534 // Get the Chassis Collection
535 dbus::utility::getSubTreePaths(
536 "/xyz/openbmc_project/inventory", 0, interfaces,
537 [callback = std::forward<Callback>(callback), asyncResp,
538 chassisIdStr{std::string(chassisId)},
539 chassisSubNode{std::string(chassisSubNode)}, sensorTypes](
540 const boost::system::error_code& ec,
541 const dbus::utility::MapperGetSubTreePathsResponse& chassisPaths) {
542 BMCWEB_LOG_DEBUG("getChassis respHandler enter");
543 if (ec)
544 {
545 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec);
546 messages::internalError(asyncResp->res);
547 return;
548 }
549 const std::string* chassisPath = nullptr;
550 for (const std::string& chassis : chassisPaths)
551 {
552 sdbusplus::message::object_path path(chassis);
553 std::string chassisName = path.filename();
554 if (chassisName.empty())
555 {
556 BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis);
557 continue;
558 }
559 if (chassisName == chassisIdStr)
560 {
561 chassisPath = &chassis;
562 break;
563 }
564 }
565 if (chassisPath == nullptr)
566 {
567 messages::resourceNotFound(asyncResp->res, "Chassis", chassisIdStr);
568 return;
569 }
570 populateChassisNode(asyncResp->res.jsonValue, chassisSubNode);
571
572 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
573 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode);
574
575 // Get the list of all sensors for this Chassis element
576 std::string sensorPath = *chassisPath + "/all_sensors";
577 dbus::utility::getAssociationEndPoints(
578 sensorPath,
579 [asyncResp, chassisSubNode, sensorTypes,
580 callback = std::forward<const Callback>(callback)](
581 const boost::system::error_code& ec2,
582 const dbus::utility::MapperEndPoints& nodeSensorList) {
583 if (ec2)
584 {
585 if (ec2.value() != EBADR)
586 {
587 messages::internalError(asyncResp->res);
588 return;
589 }
590 }
591 const std::shared_ptr<std::set<std::string>> culledSensorList =
592 std::make_shared<std::set<std::string>>();
593 reduceSensorList(asyncResp->res, chassisSubNode, sensorTypes,
594 &nodeSensorList, culledSensorList);
595 BMCWEB_LOG_DEBUG("Finishing with {}", culledSensorList->size());
596 callback(culledSensorList);
597 });
598 });
599 BMCWEB_LOG_DEBUG("getChassis exit");
600 }
601
602 /**
603 * @brief Returns the Redfish State value for the specified inventory item.
604 * @param inventoryItem D-Bus inventory item associated with a sensor.
605 * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
606 * available.
607 * @return State value for inventory item.
608 */
getState(const InventoryItem * inventoryItem,const bool sensorAvailable)609 inline resource::State getState(const InventoryItem* inventoryItem,
610 const bool sensorAvailable)
611 {
612 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
613 {
614 return resource::State::Absent;
615 }
616
617 if (!sensorAvailable)
618 {
619 return resource::State::UnavailableOffline;
620 }
621
622 return resource::State::Enabled;
623 }
624
625 /**
626 * @brief Returns the Redfish Health value for the specified sensor.
627 * @param sensorJson Sensor JSON object.
628 * @param valuesDict Map of all sensor DBus values.
629 * @param inventoryItem D-Bus inventory item associated with the sensor. Will
630 * be nullptr if no associated inventory item was found.
631 * @return Health value for sensor.
632 */
getHealth(nlohmann::json & sensorJson,const dbus::utility::DBusPropertiesMap & valuesDict,const InventoryItem * inventoryItem)633 inline std::string getHealth(nlohmann::json& sensorJson,
634 const dbus::utility::DBusPropertiesMap& valuesDict,
635 const InventoryItem* inventoryItem)
636 {
637 // Get current health value (if any) in the sensor JSON object. Some JSON
638 // objects contain multiple sensors (such as PowerSupplies). We want to set
639 // the overall health to be the most severe of any of the sensors.
640 std::string currentHealth;
641 auto statusIt = sensorJson.find("Status");
642 if (statusIt != sensorJson.end())
643 {
644 auto healthIt = statusIt->find("Health");
645 if (healthIt != statusIt->end())
646 {
647 std::string* health = healthIt->get_ptr<std::string*>();
648 if (health != nullptr)
649 {
650 currentHealth = *health;
651 }
652 }
653 }
654
655 // If current health in JSON object is already Critical, return that. This
656 // should override the sensor health, which might be less severe.
657 if (currentHealth == "Critical")
658 {
659 return "Critical";
660 }
661
662 const bool* criticalAlarmHigh = nullptr;
663 const bool* criticalAlarmLow = nullptr;
664 const bool* warningAlarmHigh = nullptr;
665 const bool* warningAlarmLow = nullptr;
666
667 const bool success = sdbusplus::unpackPropertiesNoThrow(
668 dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
669 criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
670 "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
671 warningAlarmLow);
672
673 if (success)
674 {
675 // Check if sensor has critical threshold alarm
676 if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
677 (criticalAlarmLow != nullptr && *criticalAlarmLow))
678 {
679 return "Critical";
680 }
681 }
682
683 // Check if associated inventory item is not functional
684 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
685 {
686 return "Critical";
687 }
688
689 // If current health in JSON object is already Warning, return that. This
690 // should override the sensor status, which might be less severe.
691 if (currentHealth == "Warning")
692 {
693 return "Warning";
694 }
695
696 if (success)
697 {
698 // Check if sensor has warning threshold alarm
699 if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
700 (warningAlarmLow != nullptr && *warningAlarmLow))
701 {
702 return "Warning";
703 }
704 }
705
706 return "OK";
707 }
708
setLedState(nlohmann::json & sensorJson,const InventoryItem * inventoryItem)709 inline void setLedState(nlohmann::json& sensorJson,
710 const InventoryItem* inventoryItem)
711 {
712 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
713 {
714 switch (inventoryItem->ledState)
715 {
716 case LedState::OFF:
717 sensorJson["IndicatorLED"] = "Off";
718 break;
719 case LedState::ON:
720 sensorJson["IndicatorLED"] = "Lit";
721 break;
722 case LedState::BLINK:
723 sensorJson["IndicatorLED"] = "Blinking";
724 break;
725 default:
726 break;
727 }
728 }
729 }
730
731 /**
732 * @brief Builds a json sensor representation of a sensor.
733 * @param sensorName The name of the sensor to be built
734 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to
735 * build
736 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
737 * @param propertiesDict A dictionary of the properties to build the sensor
738 * from.
739 * @param sensorJson The json object to fill
740 * @param inventoryItem D-Bus inventory item associated with the sensor. Will
741 * be nullptr if no associated inventory item was found.
742 */
objectPropertiesToJson(std::string_view sensorName,std::string_view sensorType,std::string_view chassisSubNode,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)743 inline void objectPropertiesToJson(
744 std::string_view sensorName, std::string_view sensorType,
745 std::string_view chassisSubNode,
746 const dbus::utility::DBusPropertiesMap& propertiesDict,
747 nlohmann::json& sensorJson, InventoryItem* inventoryItem)
748 {
749 if (chassisSubNode == sensors::node::sensors)
750 {
751 std::string subNodeEscaped(sensorType);
752 auto remove = std::ranges::remove(subNodeEscaped, '_');
753 subNodeEscaped.erase(std::ranges::begin(remove), subNodeEscaped.end());
754
755 // For sensors in SensorCollection we set Id instead of MemberId,
756 // including power sensors.
757 subNodeEscaped += '_';
758 subNodeEscaped += sensorName;
759 sensorJson["Id"] = std::move(subNodeEscaped);
760
761 std::string sensorNameEs(sensorName);
762 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
763 sensorJson["Name"] = std::move(sensorNameEs);
764 }
765 else if (sensorType != "power")
766 {
767 // Set MemberId and Name for non-power sensors. For PowerSupplies and
768 // PowerControl, those properties have more general values because
769 // multiple sensors can be stored in the same JSON object.
770 std::string sensorNameEs(sensorName);
771 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
772 sensorJson["Name"] = std::move(sensorNameEs);
773 }
774
775 const bool* checkAvailable = nullptr;
776 bool available = true;
777 const bool success = sdbusplus::unpackPropertiesNoThrow(
778 dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
779 checkAvailable);
780 if (!success)
781 {
782 messages::internalError();
783 }
784 if (checkAvailable != nullptr)
785 {
786 available = *checkAvailable;
787 }
788
789 sensorJson["Status"]["State"] = getState(inventoryItem, available);
790 sensorJson["Status"]["Health"] = getHealth(sensorJson, propertiesDict,
791 inventoryItem);
792
793 // Parameter to set to override the type we get from dbus, and force it to
794 // int, regardless of what is available. This is used for schemas like fan,
795 // that require integers, not floats.
796 bool forceToInt = false;
797
798 nlohmann::json::json_pointer unit("/Reading");
799 if (chassisSubNode == sensors::node::sensors)
800 {
801 sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
802
803 sensor::ReadingType readingType = sensors::toReadingType(sensorType);
804 if (readingType == sensor::ReadingType::Invalid)
805 {
806 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
807 sensorType);
808 }
809 else
810 {
811 sensorJson["ReadingType"] = readingType;
812 }
813
814 std::string_view readingUnits = sensors::toReadingUnits(sensorType);
815 if (readingUnits.empty())
816 {
817 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
818 sensorType);
819 }
820 else
821 {
822 sensorJson["ReadingUnits"] = readingUnits;
823 }
824 }
825 else if (sensorType == "temperature")
826 {
827 unit = "/ReadingCelsius"_json_pointer;
828 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
829 // TODO(ed) Documentation says that path should be type fan_tach,
830 // implementation seems to implement fan
831 }
832 else if (sensorType == "fan" || sensorType == "fan_tach")
833 {
834 unit = "/Reading"_json_pointer;
835 sensorJson["ReadingUnits"] = "RPM";
836 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
837 setLedState(sensorJson, inventoryItem);
838 forceToInt = true;
839 }
840 else if (sensorType == "fan_pwm")
841 {
842 unit = "/Reading"_json_pointer;
843 sensorJson["ReadingUnits"] = "Percent";
844 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
845 setLedState(sensorJson, inventoryItem);
846 forceToInt = true;
847 }
848 else if (sensorType == "voltage")
849 {
850 unit = "/ReadingVolts"_json_pointer;
851 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
852 }
853 else if (sensorType == "power")
854 {
855 std::string lower;
856 std::ranges::transform(sensorName, std::back_inserter(lower),
857 bmcweb::asciiToLower);
858 if (lower == "total_power")
859 {
860 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
861 // Put multiple "sensors" into a single PowerControl, so have
862 // generic names for MemberId and Name. Follows Redfish mockup.
863 sensorJson["MemberId"] = "0";
864 sensorJson["Name"] = "Chassis Power Control";
865 unit = "/PowerConsumedWatts"_json_pointer;
866 }
867 else if (lower.find("input") != std::string::npos)
868 {
869 unit = "/PowerInputWatts"_json_pointer;
870 }
871 else
872 {
873 unit = "/PowerOutputWatts"_json_pointer;
874 }
875 }
876 else
877 {
878 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", sensorName);
879 return;
880 }
881 // Map of dbus interface name, dbus property name and redfish property_name
882 std::vector<
883 std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
884 properties;
885 properties.reserve(7);
886
887 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
888
889 if (chassisSubNode == sensors::node::sensors)
890 {
891 properties.emplace_back(
892 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
893 "/Thresholds/UpperCaution/Reading"_json_pointer);
894 properties.emplace_back(
895 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
896 "/Thresholds/LowerCaution/Reading"_json_pointer);
897 properties.emplace_back(
898 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
899 "/Thresholds/UpperCritical/Reading"_json_pointer);
900 properties.emplace_back(
901 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
902 "/Thresholds/LowerCritical/Reading"_json_pointer);
903 }
904 else if (sensorType != "power")
905 {
906 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
907 "WarningHigh",
908 "/UpperThresholdNonCritical"_json_pointer);
909 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
910 "WarningLow",
911 "/LowerThresholdNonCritical"_json_pointer);
912 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
913 "CriticalHigh",
914 "/UpperThresholdCritical"_json_pointer);
915 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
916 "CriticalLow",
917 "/LowerThresholdCritical"_json_pointer);
918 }
919
920 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
921
922 if (chassisSubNode == sensors::node::sensors)
923 {
924 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
925 "/ReadingRangeMin"_json_pointer);
926 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
927 "/ReadingRangeMax"_json_pointer);
928 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
929 "Accuracy", "/Accuracy"_json_pointer);
930 }
931 else if (sensorType == "temperature")
932 {
933 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
934 "/MinReadingRangeTemp"_json_pointer);
935 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
936 "/MaxReadingRangeTemp"_json_pointer);
937 }
938 else if (sensorType != "power")
939 {
940 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
941 "/MinReadingRange"_json_pointer);
942 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
943 "/MaxReadingRange"_json_pointer);
944 }
945
946 for (const std::tuple<const char*, const char*,
947 nlohmann::json::json_pointer>& p : properties)
948 {
949 for (const auto& [valueName, valueVariant] : propertiesDict)
950 {
951 if (valueName != std::get<1>(p))
952 {
953 continue;
954 }
955
956 // The property we want to set may be nested json, so use
957 // a json_pointer for easy indexing into the json structure.
958 const nlohmann::json::json_pointer& key = std::get<2>(p);
959
960 const double* doubleValue = std::get_if<double>(&valueVariant);
961 if (doubleValue == nullptr)
962 {
963 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
964 continue;
965 }
966 if (!std::isfinite(*doubleValue))
967 {
968 if (valueName == "Value")
969 {
970 // Readings are allowed to be NAN for unavailable; coerce
971 // them to null in the json response.
972 sensorJson[key] = nullptr;
973 continue;
974 }
975 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
976 valueName, *doubleValue);
977 continue;
978 }
979 if (forceToInt)
980 {
981 sensorJson[key] = static_cast<int64_t>(*doubleValue);
982 }
983 else
984 {
985 sensorJson[key] = *doubleValue;
986 }
987 }
988 }
989 }
990
991 /**
992 * @brief Builds a json sensor representation of a sensor.
993 * @param sensorName The name of the sensor to be built
994 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to
995 * build
996 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
997 * @param interfacesDict A dictionary of the interfaces and properties of said
998 * interfaces to be built from
999 * @param sensorJson The json object to fill
1000 * @param inventoryItem D-Bus inventory item associated with the sensor. Will
1001 * be nullptr if no associated inventory item was found.
1002 */
objectInterfacesToJson(const std::string & sensorName,const std::string & sensorType,const std::string & chassisSubNode,const dbus::utility::DBusInterfacesMap & interfacesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)1003 inline void objectInterfacesToJson(
1004 const std::string& sensorName, const std::string& sensorType,
1005 const std::string& chassisSubNode,
1006 const dbus::utility::DBusInterfacesMap& interfacesDict,
1007 nlohmann::json& sensorJson, InventoryItem* inventoryItem)
1008 {
1009 for (const auto& [interface, valuesDict] : interfacesDict)
1010 {
1011 objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
1012 valuesDict, sensorJson, inventoryItem);
1013 }
1014 BMCWEB_LOG_DEBUG("Added sensor {}", sensorName);
1015 }
1016
populateFanRedundancy(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)1017 inline void populateFanRedundancy(
1018 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
1019 {
1020 constexpr std::array<std::string_view, 1> interfaces = {
1021 "xyz.openbmc_project.Control.FanRedundancy"};
1022 dbus::utility::getSubTree(
1023 "/xyz/openbmc_project/control", 2, interfaces,
1024 [sensorsAsyncResp](
1025 const boost::system::error_code& ec,
1026 const dbus::utility::MapperGetSubTreeResponse& resp) {
1027 if (ec)
1028 {
1029 return; // don't have to have this interface
1030 }
1031 for (const std::pair<std::string, dbus::utility::MapperServiceMap>&
1032 pathPair : resp)
1033 {
1034 const std::string& path = pathPair.first;
1035 const dbus::utility::MapperServiceMap& objDict = pathPair.second;
1036 if (objDict.empty())
1037 {
1038 continue; // this should be impossible
1039 }
1040
1041 const std::string& owner = objDict.begin()->first;
1042 dbus::utility::getAssociationEndPoints(
1043 path + "/chassis",
1044 [path, owner, sensorsAsyncResp](
1045 const boost::system::error_code& ec2,
1046 const dbus::utility::MapperEndPoints& endpoints) {
1047 if (ec2)
1048 {
1049 return; // if they don't have an association we
1050 // can't tell what chassis is
1051 }
1052 auto found = std::ranges::find_if(
1053 endpoints, [sensorsAsyncResp](const std::string& entry) {
1054 return entry.find(sensorsAsyncResp->chassisId) !=
1055 std::string::npos;
1056 });
1057
1058 if (found == endpoints.end())
1059 {
1060 return;
1061 }
1062 sdbusplus::asio::getAllProperties(
1063 *crow::connections::systemBus, owner, path,
1064 "xyz.openbmc_project.Control.FanRedundancy",
1065 [path, sensorsAsyncResp](
1066 const boost::system::error_code& ec3,
1067 const dbus::utility::DBusPropertiesMap& ret) {
1068 if (ec3)
1069 {
1070 return; // don't have to have this
1071 // interface
1072 }
1073
1074 const uint8_t* allowedFailures = nullptr;
1075 const std::vector<std::string>* collection = nullptr;
1076 const std::string* status = nullptr;
1077
1078 const bool success = sdbusplus::unpackPropertiesNoThrow(
1079 dbus_utils::UnpackErrorPrinter(), ret,
1080 "AllowedFailures", allowedFailures, "Collection",
1081 collection, "Status", status);
1082
1083 if (!success)
1084 {
1085 messages::internalError(
1086 sensorsAsyncResp->asyncResp->res);
1087 return;
1088 }
1089
1090 if (allowedFailures == nullptr || collection == nullptr ||
1091 status == nullptr)
1092 {
1093 BMCWEB_LOG_ERROR("Invalid redundancy interface");
1094 messages::internalError(
1095 sensorsAsyncResp->asyncResp->res);
1096 return;
1097 }
1098
1099 sdbusplus::message::object_path objectPath(path);
1100 std::string name = objectPath.filename();
1101 if (name.empty())
1102 {
1103 // this should be impossible
1104 messages::internalError(
1105 sensorsAsyncResp->asyncResp->res);
1106 return;
1107 }
1108 std::ranges::replace(name, '_', ' ');
1109
1110 std::string health;
1111
1112 if (status->ends_with("Full"))
1113 {
1114 health = "OK";
1115 }
1116 else if (status->ends_with("Degraded"))
1117 {
1118 health = "Warning";
1119 }
1120 else
1121 {
1122 health = "Critical";
1123 }
1124 nlohmann::json::array_t redfishCollection;
1125 const auto& fanRedfish =
1126 sensorsAsyncResp->asyncResp->res.jsonValue["Fans"];
1127 for (const std::string& item : *collection)
1128 {
1129 sdbusplus::message::object_path itemPath(item);
1130 std::string itemName = itemPath.filename();
1131 if (itemName.empty())
1132 {
1133 continue;
1134 }
1135 /*
1136 todo(ed): merge patch that fixes the names
1137 std::replace(itemName.begin(),
1138 itemName.end(), '_', ' ');*/
1139 auto schemaItem = std::ranges::find_if(
1140 fanRedfish, [itemName](const nlohmann::json& fan) {
1141 return fan["Name"] == itemName;
1142 });
1143 if (schemaItem != fanRedfish.end())
1144 {
1145 nlohmann::json::object_t collectionId;
1146 collectionId["@odata.id"] =
1147 (*schemaItem)["@odata.id"];
1148 redfishCollection.emplace_back(
1149 std::move(collectionId));
1150 }
1151 else
1152 {
1153 BMCWEB_LOG_ERROR("failed to find fan in schema");
1154 messages::internalError(
1155 sensorsAsyncResp->asyncResp->res);
1156 return;
1157 }
1158 }
1159
1160 size_t minNumNeeded = collection->empty()
1161 ? 0
1162 : collection->size() -
1163 *allowedFailures;
1164 nlohmann::json& jResp = sensorsAsyncResp->asyncResp->res
1165 .jsonValue["Redundancy"];
1166
1167 nlohmann::json::object_t redundancy;
1168 boost::urls::url url =
1169 boost::urls::format("/redfish/v1/Chassis/{}/{}",
1170 sensorsAsyncResp->chassisId,
1171 sensorsAsyncResp->chassisSubNode);
1172 url.set_fragment(("/Redundancy"_json_pointer / jResp.size())
1173 .to_string());
1174 redundancy["@odata.id"] = std::move(url);
1175 redundancy["@odata.type"] = "#Redundancy.v1_3_2.Redundancy";
1176 redundancy["MinNumNeeded"] = minNumNeeded;
1177 redundancy["Mode"] = "N+m";
1178 redundancy["Name"] = name;
1179 redundancy["RedundancySet"] = redfishCollection;
1180 redundancy["Status"]["Health"] = health;
1181 redundancy["Status"]["State"] = "Enabled";
1182
1183 jResp.emplace_back(std::move(redundancy));
1184 });
1185 });
1186 }
1187 });
1188 }
1189
1190 inline void
sortJSONResponse(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)1191 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
1192 {
1193 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
1194 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
1195 if (sensorsAsyncResp->chassisSubNode == sensors::node::power)
1196 {
1197 sensorHeaders = {"Voltages", "PowerSupplies"};
1198 }
1199 for (const std::string& sensorGroup : sensorHeaders)
1200 {
1201 nlohmann::json::iterator entry = response.find(sensorGroup);
1202 if (entry != response.end())
1203 {
1204 std::sort(entry->begin(), entry->end(),
1205 [](const nlohmann::json& c1, const nlohmann::json& c2) {
1206 return c1["Name"] < c2["Name"];
1207 });
1208
1209 // add the index counts to the end of each entry
1210 size_t count = 0;
1211 for (nlohmann::json& sensorJson : *entry)
1212 {
1213 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
1214 if (odata == sensorJson.end())
1215 {
1216 continue;
1217 }
1218 std::string* value = odata->get_ptr<std::string*>();
1219 if (value != nullptr)
1220 {
1221 *value += "/" + std::to_string(count);
1222 sensorJson["MemberId"] = std::to_string(count);
1223 count++;
1224 sensorsAsyncResp->updateUri(sensorJson["Name"], *value);
1225 }
1226 }
1227 }
1228 }
1229 }
1230
1231 /**
1232 * @brief Finds the inventory item with the specified object path.
1233 * @param inventoryItems D-Bus inventory items associated with sensors.
1234 * @param invItemObjPath D-Bus object path of inventory item.
1235 * @return Inventory item within vector, or nullptr if no match found.
1236 */
findInventoryItem(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & invItemObjPath)1237 inline InventoryItem* findInventoryItem(
1238 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1239 const std::string& invItemObjPath)
1240 {
1241 for (InventoryItem& inventoryItem : *inventoryItems)
1242 {
1243 if (inventoryItem.objectPath == invItemObjPath)
1244 {
1245 return &inventoryItem;
1246 }
1247 }
1248 return nullptr;
1249 }
1250
1251 /**
1252 * @brief Finds the inventory item associated with the specified sensor.
1253 * @param inventoryItems D-Bus inventory items associated with sensors.
1254 * @param sensorObjPath D-Bus object path of sensor.
1255 * @return Inventory item within vector, or nullptr if no match found.
1256 */
findInventoryItemForSensor(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & sensorObjPath)1257 inline InventoryItem* findInventoryItemForSensor(
1258 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1259 const std::string& sensorObjPath)
1260 {
1261 for (InventoryItem& inventoryItem : *inventoryItems)
1262 {
1263 if (inventoryItem.sensors.contains(sensorObjPath))
1264 {
1265 return &inventoryItem;
1266 }
1267 }
1268 return nullptr;
1269 }
1270
1271 /**
1272 * @brief Finds the inventory item associated with the specified led path.
1273 * @param inventoryItems D-Bus inventory items associated with sensors.
1274 * @param ledObjPath D-Bus object path of led.
1275 * @return Inventory item within vector, or nullptr if no match found.
1276 */
1277 inline InventoryItem*
findInventoryItemForLed(std::vector<InventoryItem> & inventoryItems,const std::string & ledObjPath)1278 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems,
1279 const std::string& ledObjPath)
1280 {
1281 for (InventoryItem& inventoryItem : inventoryItems)
1282 {
1283 if (inventoryItem.ledObjectPath == ledObjPath)
1284 {
1285 return &inventoryItem;
1286 }
1287 }
1288 return nullptr;
1289 }
1290
1291 /**
1292 * @brief Adds inventory item and associated sensor to specified vector.
1293 *
1294 * Adds a new InventoryItem to the vector if necessary. Searches for an
1295 * existing InventoryItem with the specified object path. If not found, one is
1296 * added to the vector.
1297 *
1298 * Next, the specified sensor is added to the set of sensors associated with the
1299 * InventoryItem.
1300 *
1301 * @param inventoryItems D-Bus inventory items associated with sensors.
1302 * @param invItemObjPath D-Bus object path of inventory item.
1303 * @param sensorObjPath D-Bus object path of sensor
1304 */
addInventoryItem(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & invItemObjPath,const std::string & sensorObjPath)1305 inline void addInventoryItem(
1306 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1307 const std::string& invItemObjPath, const std::string& sensorObjPath)
1308 {
1309 // Look for inventory item in vector
1310 InventoryItem* inventoryItem = findInventoryItem(inventoryItems,
1311 invItemObjPath);
1312
1313 // If inventory item doesn't exist in vector, add it
1314 if (inventoryItem == nullptr)
1315 {
1316 inventoryItems->emplace_back(invItemObjPath);
1317 inventoryItem = &(inventoryItems->back());
1318 }
1319
1320 // Add sensor to set of sensors associated with inventory item
1321 inventoryItem->sensors.emplace(sensorObjPath);
1322 }
1323
1324 /**
1325 * @brief Stores D-Bus data in the specified inventory item.
1326 *
1327 * Finds D-Bus data in the specified map of interfaces. Stores the data in the
1328 * specified InventoryItem.
1329 *
1330 * This data is later used to provide sensor property values in the JSON
1331 * response.
1332 *
1333 * @param inventoryItem Inventory item where data will be stored.
1334 * @param interfacesDict Map containing D-Bus interfaces and their properties
1335 * for the specified inventory item.
1336 */
storeInventoryItemData(InventoryItem & inventoryItem,const dbus::utility::DBusInterfacesMap & interfacesDict)1337 inline void storeInventoryItemData(
1338 InventoryItem& inventoryItem,
1339 const dbus::utility::DBusInterfacesMap& interfacesDict)
1340 {
1341 // Get properties from Inventory.Item interface
1342
1343 for (const auto& [interface, values] : interfacesDict)
1344 {
1345 if (interface == "xyz.openbmc_project.Inventory.Item")
1346 {
1347 for (const auto& [name, dbusValue] : values)
1348 {
1349 if (name == "Present")
1350 {
1351 const bool* value = std::get_if<bool>(&dbusValue);
1352 if (value != nullptr)
1353 {
1354 inventoryItem.isPresent = *value;
1355 }
1356 }
1357 }
1358 }
1359 // Check if Inventory.Item.PowerSupply interface is present
1360
1361 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply")
1362 {
1363 inventoryItem.isPowerSupply = true;
1364 }
1365
1366 // Get properties from Inventory.Decorator.Asset interface
1367 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
1368 {
1369 for (const auto& [name, dbusValue] : values)
1370 {
1371 if (name == "Manufacturer")
1372 {
1373 const std::string* value =
1374 std::get_if<std::string>(&dbusValue);
1375 if (value != nullptr)
1376 {
1377 inventoryItem.manufacturer = *value;
1378 }
1379 }
1380 if (name == "Model")
1381 {
1382 const std::string* value =
1383 std::get_if<std::string>(&dbusValue);
1384 if (value != nullptr)
1385 {
1386 inventoryItem.model = *value;
1387 }
1388 }
1389 if (name == "SerialNumber")
1390 {
1391 const std::string* value =
1392 std::get_if<std::string>(&dbusValue);
1393 if (value != nullptr)
1394 {
1395 inventoryItem.serialNumber = *value;
1396 }
1397 }
1398 if (name == "PartNumber")
1399 {
1400 const std::string* value =
1401 std::get_if<std::string>(&dbusValue);
1402 if (value != nullptr)
1403 {
1404 inventoryItem.partNumber = *value;
1405 }
1406 }
1407 }
1408 }
1409
1410 if (interface ==
1411 "xyz.openbmc_project.State.Decorator.OperationalStatus")
1412 {
1413 for (const auto& [name, dbusValue] : values)
1414 {
1415 if (name == "Functional")
1416 {
1417 const bool* value = std::get_if<bool>(&dbusValue);
1418 if (value != nullptr)
1419 {
1420 inventoryItem.isFunctional = *value;
1421 }
1422 }
1423 }
1424 }
1425 }
1426 }
1427
1428 /**
1429 * @brief Gets D-Bus data for inventory items associated with sensors.
1430 *
1431 * Uses the specified connections (services) to obtain D-Bus data for inventory
1432 * items associated with sensors. Stores the resulting data in the
1433 * inventoryItems vector.
1434 *
1435 * This data is later used to provide sensor property values in the JSON
1436 * response.
1437 *
1438 * Finds the inventory item data asynchronously. Invokes callback when data has
1439 * been obtained.
1440 *
1441 * The callback must have the following signature:
1442 * @code
1443 * callback(void)
1444 * @endcode
1445 *
1446 * This function is called recursively, obtaining data asynchronously from one
1447 * connection in each call. This ensures the callback is not invoked until the
1448 * last asynchronous function has completed.
1449 *
1450 * @param sensorsAsyncResp Pointer to object holding response data.
1451 * @param inventoryItems D-Bus inventory items associated with sensors.
1452 * @param invConnections Connections that provide data for the inventory items.
1453 * implements ObjectManager.
1454 * @param callback Callback to invoke when inventory data has been obtained.
1455 * @param invConnectionsIndex Current index in invConnections. Only specified
1456 * in recursive calls to this function.
1457 */
1458 template <typename Callback>
getInventoryItemsData(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,std::shared_ptr<std::vector<InventoryItem>> inventoryItems,std::shared_ptr<std::set<std::string>> invConnections,Callback && callback,size_t invConnectionsIndex=0)1459 static void getInventoryItemsData(
1460 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1461 std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1462 std::shared_ptr<std::set<std::string>> invConnections, Callback&& callback,
1463 size_t invConnectionsIndex = 0)
1464 {
1465 BMCWEB_LOG_DEBUG("getInventoryItemsData enter");
1466
1467 // If no more connections left, call callback
1468 if (invConnectionsIndex >= invConnections->size())
1469 {
1470 callback();
1471 BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
1472 return;
1473 }
1474
1475 // Get inventory item data from current connection
1476 auto it = invConnections->begin();
1477 std::advance(it, invConnectionsIndex);
1478 if (it != invConnections->end())
1479 {
1480 const std::string& invConnection = *it;
1481
1482 // Get all object paths and their interfaces for current connection
1483 sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
1484 dbus::utility::getManagedObjects(
1485 invConnection, path,
1486 [sensorsAsyncResp, inventoryItems, invConnections,
1487 callback = std::forward<Callback>(callback), invConnectionsIndex](
1488 const boost::system::error_code& ec,
1489 const dbus::utility::ManagedObjectType& resp) {
1490 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler enter");
1491 if (ec)
1492 {
1493 BMCWEB_LOG_ERROR(
1494 "getInventoryItemsData respHandler DBus error {}", ec);
1495 messages::internalError(sensorsAsyncResp->asyncResp->res);
1496 return;
1497 }
1498
1499 // Loop through returned object paths
1500 for (const auto& objDictEntry : resp)
1501 {
1502 const std::string& objPath =
1503 static_cast<const std::string&>(objDictEntry.first);
1504
1505 // If this object path is one of the specified inventory items
1506 InventoryItem* inventoryItem = findInventoryItem(inventoryItems,
1507 objPath);
1508 if (inventoryItem != nullptr)
1509 {
1510 // Store inventory data in InventoryItem
1511 storeInventoryItemData(*inventoryItem, objDictEntry.second);
1512 }
1513 }
1514
1515 // Recurse to get inventory item data from next connection
1516 getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1517 invConnections, std::move(callback),
1518 invConnectionsIndex + 1);
1519
1520 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler exit");
1521 });
1522 }
1523
1524 BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
1525 }
1526
1527 /**
1528 * @brief Gets connections that provide D-Bus data for inventory items.
1529 *
1530 * Gets the D-Bus connections (services) that provide data for the inventory
1531 * items that are associated with sensors.
1532 *
1533 * Finds the connections asynchronously. Invokes callback when information has
1534 * been obtained.
1535 *
1536 * The callback must have the following signature:
1537 * @code
1538 * callback(std::shared_ptr<std::set<std::string>> invConnections)
1539 * @endcode
1540 *
1541 * @param sensorsAsyncResp Pointer to object holding response data.
1542 * @param inventoryItems D-Bus inventory items associated with sensors.
1543 * @param callback Callback to invoke when connections have been obtained.
1544 */
1545 template <typename Callback>
getInventoryItemsConnections(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,Callback && callback)1546 static void getInventoryItemsConnections(
1547 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1548 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1549 Callback&& callback)
1550 {
1551 BMCWEB_LOG_DEBUG("getInventoryItemsConnections enter");
1552
1553 const std::string path = "/xyz/openbmc_project/inventory";
1554 constexpr std::array<std::string_view, 4> interfaces = {
1555 "xyz.openbmc_project.Inventory.Item",
1556 "xyz.openbmc_project.Inventory.Item.PowerSupply",
1557 "xyz.openbmc_project.Inventory.Decorator.Asset",
1558 "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1559
1560 // Make call to ObjectMapper to find all inventory items
1561 dbus::utility::getSubTree(
1562 path, 0, interfaces,
1563 [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1564 inventoryItems](
1565 const boost::system::error_code& ec,
1566 const dbus::utility::MapperGetSubTreeResponse& subtree) {
1567 // Response handler for parsing output from GetSubTree
1568 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler enter");
1569 if (ec)
1570 {
1571 messages::internalError(sensorsAsyncResp->asyncResp->res);
1572 BMCWEB_LOG_ERROR(
1573 "getInventoryItemsConnections respHandler DBus error {}", ec);
1574 return;
1575 }
1576
1577 // Make unique list of connections for desired inventory items
1578 std::shared_ptr<std::set<std::string>> invConnections =
1579 std::make_shared<std::set<std::string>>();
1580
1581 // Loop through objects from GetSubTree
1582 for (const std::pair<
1583 std::string,
1584 std::vector<std::pair<std::string, std::vector<std::string>>>>&
1585 object : subtree)
1586 {
1587 // Check if object path is one of the specified inventory items
1588 const std::string& objPath = object.first;
1589 if (findInventoryItem(inventoryItems, objPath) != nullptr)
1590 {
1591 // Store all connections to inventory item
1592 for (const std::pair<std::string, std::vector<std::string>>&
1593 objData : object.second)
1594 {
1595 const std::string& invConnection = objData.first;
1596 invConnections->insert(invConnection);
1597 }
1598 }
1599 }
1600
1601 callback(invConnections);
1602 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler exit");
1603 });
1604 BMCWEB_LOG_DEBUG("getInventoryItemsConnections exit");
1605 }
1606
1607 /**
1608 * @brief Gets associations from sensors to inventory items.
1609 *
1610 * Looks for ObjectMapper associations from the specified sensors to related
1611 * inventory items. Then finds the associations from those inventory items to
1612 * their LEDs, if any.
1613 *
1614 * Finds the inventory items asynchronously. Invokes callback when information
1615 * has been obtained.
1616 *
1617 * The callback must have the following signature:
1618 * @code
1619 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1620 * @endcode
1621 *
1622 * @param sensorsAsyncResp Pointer to object holding response data.
1623 * @param sensorNames All sensors within the current chassis.
1624 * implements ObjectManager.
1625 * @param callback Callback to invoke when inventory items have been obtained.
1626 */
1627 template <typename Callback>
getInventoryItemAssociations(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)1628 static void getInventoryItemAssociations(
1629 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1630 const std::shared_ptr<std::set<std::string>>& sensorNames,
1631 Callback&& callback)
1632 {
1633 BMCWEB_LOG_DEBUG("getInventoryItemAssociations enter");
1634
1635 // Call GetManagedObjects on the ObjectMapper to get all associations
1636 sdbusplus::message::object_path path("/");
1637 dbus::utility::getManagedObjects(
1638 "xyz.openbmc_project.ObjectMapper", path,
1639 [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1640 sensorNames](const boost::system::error_code& ec,
1641 const dbus::utility::ManagedObjectType& resp) {
1642 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler enter");
1643 if (ec)
1644 {
1645 BMCWEB_LOG_ERROR(
1646 "getInventoryItemAssociations respHandler DBus error {}", ec);
1647 messages::internalError(sensorsAsyncResp->asyncResp->res);
1648 return;
1649 }
1650
1651 // Create vector to hold list of inventory items
1652 std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1653 std::make_shared<std::vector<InventoryItem>>();
1654
1655 // Loop through returned object paths
1656 std::string sensorAssocPath;
1657 sensorAssocPath.reserve(128); // avoid memory allocations
1658 for (const auto& objDictEntry : resp)
1659 {
1660 const std::string& objPath =
1661 static_cast<const std::string&>(objDictEntry.first);
1662
1663 // If path is inventory association for one of the specified sensors
1664 for (const std::string& sensorName : *sensorNames)
1665 {
1666 sensorAssocPath = sensorName;
1667 sensorAssocPath += "/inventory";
1668 if (objPath == sensorAssocPath)
1669 {
1670 // Get Association interface for object path
1671 for (const auto& [interface, values] : objDictEntry.second)
1672 {
1673 if (interface == "xyz.openbmc_project.Association")
1674 {
1675 for (const auto& [valueName, value] : values)
1676 {
1677 if (valueName == "endpoints")
1678 {
1679 const std::vector<std::string>* endpoints =
1680 std::get_if<std::vector<std::string>>(
1681 &value);
1682 if ((endpoints != nullptr) &&
1683 !endpoints->empty())
1684 {
1685 // Add inventory item to vector
1686 const std::string& invItemPath =
1687 endpoints->front();
1688 addInventoryItem(inventoryItems,
1689 invItemPath,
1690 sensorName);
1691 }
1692 }
1693 }
1694 }
1695 }
1696 break;
1697 }
1698 }
1699 }
1700
1701 // Now loop through the returned object paths again, this time to
1702 // find the leds associated with the inventory items we just found
1703 std::string inventoryAssocPath;
1704 inventoryAssocPath.reserve(128); // avoid memory allocations
1705 for (const auto& objDictEntry : resp)
1706 {
1707 const std::string& objPath =
1708 static_cast<const std::string&>(objDictEntry.first);
1709
1710 for (InventoryItem& inventoryItem : *inventoryItems)
1711 {
1712 inventoryAssocPath = inventoryItem.objectPath;
1713 inventoryAssocPath += "/leds";
1714 if (objPath == inventoryAssocPath)
1715 {
1716 for (const auto& [interface, values] : objDictEntry.second)
1717 {
1718 if (interface == "xyz.openbmc_project.Association")
1719 {
1720 for (const auto& [valueName, value] : values)
1721 {
1722 if (valueName == "endpoints")
1723 {
1724 const std::vector<std::string>* endpoints =
1725 std::get_if<std::vector<std::string>>(
1726 &value);
1727 if ((endpoints != nullptr) &&
1728 !endpoints->empty())
1729 {
1730 // Add inventory item to vector
1731 // Store LED path in inventory item
1732 const std::string& ledPath =
1733 endpoints->front();
1734 inventoryItem.ledObjectPath = ledPath;
1735 }
1736 }
1737 }
1738 }
1739 }
1740
1741 break;
1742 }
1743 }
1744 }
1745 callback(inventoryItems);
1746 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler exit");
1747 });
1748
1749 BMCWEB_LOG_DEBUG("getInventoryItemAssociations exit");
1750 }
1751
1752 /**
1753 * @brief Gets D-Bus data for inventory item leds associated with sensors.
1754 *
1755 * Uses the specified connections (services) to obtain D-Bus data for inventory
1756 * item leds associated with sensors. Stores the resulting data in the
1757 * inventoryItems vector.
1758 *
1759 * This data is later used to provide sensor property values in the JSON
1760 * response.
1761 *
1762 * Finds the inventory item led data asynchronously. Invokes callback when data
1763 * has been obtained.
1764 *
1765 * The callback must have the following signature:
1766 * @code
1767 * callback()
1768 * @endcode
1769 *
1770 * This function is called recursively, obtaining data asynchronously from one
1771 * connection in each call. This ensures the callback is not invoked until the
1772 * last asynchronous function has completed.
1773 *
1774 * @param sensorsAsyncResp Pointer to object holding response data.
1775 * @param inventoryItems D-Bus inventory items associated with sensors.
1776 * @param ledConnections Connections that provide data for the inventory leds.
1777 * @param callback Callback to invoke when inventory data has been obtained.
1778 * @param ledConnectionsIndex Current index in ledConnections. Only specified
1779 * in recursive calls to this function.
1780 */
1781 template <typename Callback>
getInventoryLedData(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,std::shared_ptr<std::vector<InventoryItem>> inventoryItems,std::shared_ptr<std::map<std::string,std::string>> ledConnections,Callback && callback,size_t ledConnectionsIndex=0)1782 void getInventoryLedData(
1783 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1784 std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1785 std::shared_ptr<std::map<std::string, std::string>> ledConnections,
1786 Callback&& callback, size_t ledConnectionsIndex = 0)
1787 {
1788 BMCWEB_LOG_DEBUG("getInventoryLedData enter");
1789
1790 // If no more connections left, call callback
1791 if (ledConnectionsIndex >= ledConnections->size())
1792 {
1793 callback();
1794 BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1795 return;
1796 }
1797
1798 // Get inventory item data from current connection
1799 auto it = ledConnections->begin();
1800 std::advance(it, ledConnectionsIndex);
1801 if (it != ledConnections->end())
1802 {
1803 const std::string& ledPath = (*it).first;
1804 const std::string& ledConnection = (*it).second;
1805 // Response handler for Get State property
1806 auto respHandler =
1807 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1808 callback = std::forward<Callback>(callback), ledConnectionsIndex](
1809 const boost::system::error_code& ec, const std::string& state) {
1810 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler enter");
1811 if (ec)
1812 {
1813 BMCWEB_LOG_ERROR(
1814 "getInventoryLedData respHandler DBus error {}", ec);
1815 messages::internalError(sensorsAsyncResp->asyncResp->res);
1816 return;
1817 }
1818
1819 BMCWEB_LOG_DEBUG("Led state: {}", state);
1820 // Find inventory item with this LED object path
1821 InventoryItem* inventoryItem =
1822 findInventoryItemForLed(*inventoryItems, ledPath);
1823 if (inventoryItem != nullptr)
1824 {
1825 // Store LED state in InventoryItem
1826 if (state.ends_with("On"))
1827 {
1828 inventoryItem->ledState = LedState::ON;
1829 }
1830 else if (state.ends_with("Blink"))
1831 {
1832 inventoryItem->ledState = LedState::BLINK;
1833 }
1834 else if (state.ends_with("Off"))
1835 {
1836 inventoryItem->ledState = LedState::OFF;
1837 }
1838 else
1839 {
1840 inventoryItem->ledState = LedState::UNKNOWN;
1841 }
1842 }
1843
1844 // Recurse to get LED data from next connection
1845 getInventoryLedData(sensorsAsyncResp, inventoryItems,
1846 ledConnections, std::move(callback),
1847 ledConnectionsIndex + 1);
1848
1849 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler exit");
1850 };
1851
1852 // Get the State property for the current LED
1853 sdbusplus::asio::getProperty<std::string>(
1854 *crow::connections::systemBus, ledConnection, ledPath,
1855 "xyz.openbmc_project.Led.Physical", "State",
1856 std::move(respHandler));
1857 }
1858
1859 BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1860 }
1861
1862 /**
1863 * @brief Gets LED data for LEDs associated with given inventory items.
1864 *
1865 * Gets the D-Bus connections (services) that provide LED data for the LEDs
1866 * associated with the specified inventory items. Then gets the LED data from
1867 * each connection and stores it in the inventory item.
1868 *
1869 * This data is later used to provide sensor property values in the JSON
1870 * response.
1871 *
1872 * Finds the LED data asynchronously. Invokes callback when information has
1873 * been obtained.
1874 *
1875 * The callback must have the following signature:
1876 * @code
1877 * callback()
1878 * @endcode
1879 *
1880 * @param sensorsAsyncResp Pointer to object holding response data.
1881 * @param inventoryItems D-Bus inventory items associated with sensors.
1882 * @param callback Callback to invoke when inventory items have been obtained.
1883 */
1884 template <typename Callback>
getInventoryLeds(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,std::shared_ptr<std::vector<InventoryItem>> inventoryItems,Callback && callback)1885 void getInventoryLeds(
1886 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1887 std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1888 Callback&& callback)
1889 {
1890 BMCWEB_LOG_DEBUG("getInventoryLeds enter");
1891
1892 const std::string path = "/xyz/openbmc_project";
1893 constexpr std::array<std::string_view, 1> interfaces = {
1894 "xyz.openbmc_project.Led.Physical"};
1895
1896 // Make call to ObjectMapper to find all inventory items
1897 dbus::utility::getSubTree(
1898 path, 0, interfaces,
1899 [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1900 inventoryItems](
1901 const boost::system::error_code& ec,
1902 const dbus::utility::MapperGetSubTreeResponse& subtree) {
1903 // Response handler for parsing output from GetSubTree
1904 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter");
1905 if (ec)
1906 {
1907 messages::internalError(sensorsAsyncResp->asyncResp->res);
1908 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}", ec);
1909 return;
1910 }
1911
1912 // Build map of LED object paths to connections
1913 std::shared_ptr<std::map<std::string, std::string>> ledConnections =
1914 std::make_shared<std::map<std::string, std::string>>();
1915
1916 // Loop through objects from GetSubTree
1917 for (const std::pair<
1918 std::string,
1919 std::vector<std::pair<std::string, std::vector<std::string>>>>&
1920 object : subtree)
1921 {
1922 // Check if object path is LED for one of the specified inventory
1923 // items
1924 const std::string& ledPath = object.first;
1925 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr)
1926 {
1927 // Add mapping from ledPath to connection
1928 const std::string& connection = object.second.begin()->first;
1929 (*ledConnections)[ledPath] = connection;
1930 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath, connection);
1931 }
1932 }
1933
1934 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections,
1935 std::move(callback));
1936 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit");
1937 });
1938 BMCWEB_LOG_DEBUG("getInventoryLeds exit");
1939 }
1940
1941 /**
1942 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
1943 *
1944 * Uses the specified connections (services) (currently assumes just one) to
1945 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
1946 * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
1947 *
1948 * This data is later used to provide sensor property values in the JSON
1949 * response.
1950 *
1951 * Finds the Power Supply Attributes data asynchronously. Invokes callback
1952 * when data has been obtained.
1953 *
1954 * The callback must have the following signature:
1955 * @code
1956 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1957 * @endcode
1958 *
1959 * @param sensorsAsyncResp Pointer to object holding response data.
1960 * @param inventoryItems D-Bus inventory items associated with sensors.
1961 * @param psAttributesConnections Connections that provide data for the Power
1962 * Supply Attributes
1963 * @param callback Callback to invoke when data has been obtained.
1964 */
1965 template <typename Callback>
getPowerSupplyAttributesData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,std::shared_ptr<std::vector<InventoryItem>> inventoryItems,const std::map<std::string,std::string> & psAttributesConnections,Callback && callback)1966 void getPowerSupplyAttributesData(
1967 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1968 std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1969 const std::map<std::string, std::string>& psAttributesConnections,
1970 Callback&& callback)
1971 {
1972 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter");
1973
1974 if (psAttributesConnections.empty())
1975 {
1976 BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!");
1977 callback(inventoryItems);
1978 return;
1979 }
1980
1981 // Assuming just one connection (service) for now
1982 auto it = psAttributesConnections.begin();
1983
1984 const std::string& psAttributesPath = (*it).first;
1985 const std::string& psAttributesConnection = (*it).second;
1986
1987 // Response handler for Get DeratingFactor property
1988 auto respHandler = [sensorsAsyncResp, inventoryItems,
1989 callback = std::forward<Callback>(callback)](
1990 const boost::system::error_code& ec,
1991 const uint32_t value) {
1992 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter");
1993 if (ec)
1994 {
1995 BMCWEB_LOG_ERROR(
1996 "getPowerSupplyAttributesData respHandler DBus error {}", ec);
1997 messages::internalError(sensorsAsyncResp->asyncResp->res);
1998 return;
1999 }
2000
2001 BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value);
2002 // Store value in Power Supply Inventory Items
2003 for (InventoryItem& inventoryItem : *inventoryItems)
2004 {
2005 if (inventoryItem.isPowerSupply)
2006 {
2007 inventoryItem.powerSupplyEfficiencyPercent =
2008 static_cast<int>(value);
2009 }
2010 }
2011
2012 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit");
2013 callback(inventoryItems);
2014 };
2015
2016 // Get the DeratingFactor property for the PowerSupplyAttributes
2017 // Currently only property on the interface/only one we care about
2018 sdbusplus::asio::getProperty<uint32_t>(
2019 *crow::connections::systemBus, psAttributesConnection, psAttributesPath,
2020 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
2021 std::move(respHandler));
2022
2023 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit");
2024 }
2025
2026 /**
2027 * @brief Gets the Power Supply Attributes such as EfficiencyPercent
2028 *
2029 * Gets the D-Bus connection (service) that provides Power Supply Attributes
2030 * data. Then gets the Power Supply Attributes data from the connection
2031 * (currently just assumes 1 connection) and stores the data in the inventory
2032 * item.
2033 *
2034 * This data is later used to provide sensor property values in the JSON
2035 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
2036 *
2037 * Finds the Power Supply Attributes data asynchronously. Invokes callback
2038 * when information has been obtained.
2039 *
2040 * The callback must have the following signature:
2041 * @code
2042 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2043 * @endcode
2044 *
2045 * @param sensorsAsyncResp Pointer to object holding response data.
2046 * @param inventoryItems D-Bus inventory items associated with sensors.
2047 * @param callback Callback to invoke when data has been obtained.
2048 */
2049 template <typename Callback>
getPowerSupplyAttributes(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,std::shared_ptr<std::vector<InventoryItem>> inventoryItems,Callback && callback)2050 void getPowerSupplyAttributes(
2051 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2052 std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2053 Callback&& callback)
2054 {
2055 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter");
2056
2057 // Only need the power supply attributes when the Power Schema
2058 if (sensorsAsyncResp->chassisSubNode != sensors::node::power)
2059 {
2060 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power");
2061 callback(inventoryItems);
2062 return;
2063 }
2064
2065 constexpr std::array<std::string_view, 1> interfaces = {
2066 "xyz.openbmc_project.Control.PowerSupplyAttributes"};
2067
2068 // Make call to ObjectMapper to find the PowerSupplyAttributes service
2069 dbus::utility::getSubTree(
2070 "/xyz/openbmc_project", 0, interfaces,
2071 [callback = std::forward<Callback>(callback), sensorsAsyncResp,
2072 inventoryItems](
2073 const boost::system::error_code& ec,
2074 const dbus::utility::MapperGetSubTreeResponse& subtree) {
2075 // Response handler for parsing output from GetSubTree
2076 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter");
2077 if (ec)
2078 {
2079 messages::internalError(sensorsAsyncResp->asyncResp->res);
2080 BMCWEB_LOG_ERROR(
2081 "getPowerSupplyAttributes respHandler DBus error {}", ec);
2082 return;
2083 }
2084 if (subtree.empty())
2085 {
2086 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
2087 callback(inventoryItems);
2088 return;
2089 }
2090
2091 // Currently we only support 1 power supply attribute, use this for
2092 // all the power supplies. Build map of object path to connection.
2093 // Assume just 1 connection and 1 path for now.
2094 std::map<std::string, std::string> psAttributesConnections;
2095
2096 if (subtree[0].first.empty() || subtree[0].second.empty())
2097 {
2098 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
2099 callback(inventoryItems);
2100 return;
2101 }
2102
2103 const std::string& psAttributesPath = subtree[0].first;
2104 const std::string& connection = subtree[0].second.begin()->first;
2105
2106 if (connection.empty())
2107 {
2108 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
2109 callback(inventoryItems);
2110 return;
2111 }
2112
2113 psAttributesConnections[psAttributesPath] = connection;
2114 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath,
2115 connection);
2116
2117 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
2118 psAttributesConnections,
2119 std::move(callback));
2120 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit");
2121 });
2122 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit");
2123 }
2124
2125 /**
2126 * @brief Gets inventory items associated with sensors.
2127 *
2128 * Finds the inventory items that are associated with the specified sensors.
2129 * Then gets D-Bus data for the inventory items, such as presence and VPD.
2130 *
2131 * This data is later used to provide sensor property values in the JSON
2132 * response.
2133 *
2134 * Finds the inventory items asynchronously. Invokes callback when the
2135 * inventory items have been obtained.
2136 *
2137 * The callback must have the following signature:
2138 * @code
2139 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2140 * @endcode
2141 *
2142 * @param sensorsAsyncResp Pointer to object holding response data.
2143 * @param sensorNames All sensors within the current chassis.
2144 * implements ObjectManager.
2145 * @param callback Callback to invoke when inventory items have been obtained.
2146 */
2147 template <typename Callback>
2148 static void
getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> sensorNames,Callback && callback)2149 getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2150 const std::shared_ptr<std::set<std::string>> sensorNames,
2151 Callback&& callback)
2152 {
2153 BMCWEB_LOG_DEBUG("getInventoryItems enter");
2154 auto getInventoryItemAssociationsCb =
2155 [sensorsAsyncResp, callback = std::forward<Callback>(callback)](
2156 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) {
2157 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter");
2158 auto getInventoryItemsConnectionsCb =
2159 [sensorsAsyncResp, inventoryItems,
2160 callback = std::forward<const Callback>(callback)](
2161 std::shared_ptr<std::set<std::string>> invConnections) {
2162 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter");
2163 auto getInventoryItemsDataCb = [sensorsAsyncResp, inventoryItems,
2164 callback{std::move(callback)}]() {
2165 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter");
2166
2167 auto getInventoryLedsCb = [sensorsAsyncResp, inventoryItems,
2168 callback{std::move(callback)}]() {
2169 BMCWEB_LOG_DEBUG("getInventoryLedsCb enter");
2170 // Find Power Supply Attributes and get the data
2171 getPowerSupplyAttributes(sensorsAsyncResp, inventoryItems,
2172 std::move(callback));
2173 BMCWEB_LOG_DEBUG("getInventoryLedsCb exit");
2174 };
2175
2176 // Find led connections and get the data
2177 getInventoryLeds(sensorsAsyncResp, inventoryItems,
2178 std::move(getInventoryLedsCb));
2179 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit");
2180 };
2181
2182 // Get inventory item data from connections
2183 getInventoryItemsData(sensorsAsyncResp, inventoryItems,
2184 invConnections,
2185 std::move(getInventoryItemsDataCb));
2186 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit");
2187 };
2188
2189 // Get connections that provide inventory item data
2190 getInventoryItemsConnections(sensorsAsyncResp, inventoryItems,
2191 std::move(getInventoryItemsConnectionsCb));
2192 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit");
2193 };
2194
2195 // Get associations from sensors to inventory items
2196 getInventoryItemAssociations(sensorsAsyncResp, sensorNames,
2197 std::move(getInventoryItemAssociationsCb));
2198 BMCWEB_LOG_DEBUG("getInventoryItems exit");
2199 }
2200
2201 /**
2202 * @brief Returns JSON PowerSupply object for the specified inventory item.
2203 *
2204 * Searches for a JSON PowerSupply object that matches the specified inventory
2205 * item. If one is not found, a new PowerSupply object is added to the JSON
2206 * array.
2207 *
2208 * Multiple sensors are often associated with one power supply inventory item.
2209 * As a result, multiple sensor values are stored in one JSON PowerSupply
2210 * object.
2211 *
2212 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
2213 * @param inventoryItem Inventory item for the power supply.
2214 * @param chassisId Chassis that contains the power supply.
2215 * @return JSON PowerSupply object for the specified inventory item.
2216 */
getPowerSupply(nlohmann::json & powerSupplyArray,const InventoryItem & inventoryItem,const std::string & chassisId)2217 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
2218 const InventoryItem& inventoryItem,
2219 const std::string& chassisId)
2220 {
2221 std::string nameS;
2222 nameS.resize(inventoryItem.name.size());
2223 std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' ');
2224 // Check if matching PowerSupply object already exists in JSON array
2225 for (nlohmann::json& powerSupply : powerSupplyArray)
2226 {
2227 nlohmann::json::iterator nameIt = powerSupply.find("Name");
2228 if (nameIt == powerSupply.end())
2229 {
2230 continue;
2231 }
2232 const std::string* name = nameIt->get_ptr<std::string*>();
2233 if (name == nullptr)
2234 {
2235 continue;
2236 }
2237 if (nameS == *name)
2238 {
2239 return powerSupply;
2240 }
2241 }
2242
2243 // Add new PowerSupply object to JSON array
2244 powerSupplyArray.push_back({});
2245 nlohmann::json& powerSupply = powerSupplyArray.back();
2246 boost::urls::url url = boost::urls::format("/redfish/v1/Chassis/{}/Power",
2247 chassisId);
2248 url.set_fragment(("/PowerSupplies"_json_pointer).to_string());
2249 powerSupply["@odata.id"] = std::move(url);
2250 std::string escaped;
2251 escaped.resize(inventoryItem.name.size());
2252 std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' ');
2253 powerSupply["Name"] = std::move(escaped);
2254 powerSupply["Manufacturer"] = inventoryItem.manufacturer;
2255 powerSupply["Model"] = inventoryItem.model;
2256 powerSupply["PartNumber"] = inventoryItem.partNumber;
2257 powerSupply["SerialNumber"] = inventoryItem.serialNumber;
2258 setLedState(powerSupply, &inventoryItem);
2259
2260 if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
2261 {
2262 powerSupply["EfficiencyPercent"] =
2263 inventoryItem.powerSupplyEfficiencyPercent;
2264 }
2265
2266 powerSupply["Status"]["State"] = getState(&inventoryItem, true);
2267 const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
2268 powerSupply["Status"]["Health"] = health;
2269
2270 return powerSupply;
2271 }
2272
2273 /**
2274 * @brief Gets the values of the specified sensors.
2275 *
2276 * Stores the results as JSON in the SensorsAsyncResp.
2277 *
2278 * Gets the sensor values asynchronously. Stores the results later when the
2279 * information has been obtained.
2280 *
2281 * The sensorNames set contains all requested sensors for the current chassis.
2282 *
2283 * To minimize the number of DBus calls, the DBus method
2284 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
2285 * values of all sensors provided by a connection (service).
2286 *
2287 * The connections set contains all the connections that provide sensor values.
2288 *
2289 * The InventoryItem vector contains D-Bus inventory items associated with the
2290 * sensors. Inventory item data is needed for some Redfish sensor properties.
2291 *
2292 * @param SensorsAsyncResp Pointer to object holding response data.
2293 * @param sensorNames All requested sensors within the current chassis.
2294 * @param connections Connections that provide sensor values.
2295 * implements ObjectManager.
2296 * @param inventoryItems Inventory items associated with the sensors.
2297 */
getSensorData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,const std::set<std::string> & connections,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems)2298 inline void getSensorData(
2299 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2300 const std::shared_ptr<std::set<std::string>>& sensorNames,
2301 const std::set<std::string>& connections,
2302 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
2303 {
2304 BMCWEB_LOG_DEBUG("getSensorData enter");
2305 // Get managed objects from all services exposing sensors
2306 for (const std::string& connection : connections)
2307 {
2308 sdbusplus::message::object_path sensorPath(
2309 "/xyz/openbmc_project/sensors");
2310 dbus::utility::getManagedObjects(
2311 connection, sensorPath,
2312 [sensorsAsyncResp, sensorNames,
2313 inventoryItems](const boost::system::error_code& ec,
2314 const dbus::utility::ManagedObjectType& resp) {
2315 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter");
2316 if (ec)
2317 {
2318 BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec);
2319 messages::internalError(sensorsAsyncResp->asyncResp->res);
2320 return;
2321 }
2322 // Go through all objects and update response with sensor data
2323 for (const auto& objDictEntry : resp)
2324 {
2325 const std::string& objPath =
2326 static_cast<const std::string&>(objDictEntry.first);
2327 BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}",
2328 objPath);
2329
2330 std::vector<std::string> split;
2331 // Reserve space for
2332 // /xyz/openbmc_project/sensors/<name>/<subname>
2333 split.reserve(6);
2334 // NOLINTNEXTLINE
2335 bmcweb::split(split, objPath, '/');
2336 if (split.size() < 6)
2337 {
2338 BMCWEB_LOG_ERROR("Got path that isn't long enough {}",
2339 objPath);
2340 continue;
2341 }
2342 // These indexes aren't intuitive, as split puts an empty
2343 // string at the beginning
2344 const std::string& sensorType = split[4];
2345 const std::string& sensorName = split[5];
2346 BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName,
2347 sensorType);
2348 if (sensorNames->find(objPath) == sensorNames->end())
2349 {
2350 BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName);
2351 continue;
2352 }
2353
2354 // Find inventory item (if any) associated with sensor
2355 InventoryItem* inventoryItem =
2356 findInventoryItemForSensor(inventoryItems, objPath);
2357
2358 const std::string& sensorSchema =
2359 sensorsAsyncResp->chassisSubNode;
2360
2361 nlohmann::json* sensorJson = nullptr;
2362
2363 if (sensorSchema == sensors::node::sensors &&
2364 !sensorsAsyncResp->efficientExpand)
2365 {
2366 std::string sensorTypeEscaped(sensorType);
2367 auto remove = std::ranges::remove(sensorTypeEscaped, '_');
2368
2369 sensorTypeEscaped.erase(std::ranges::begin(remove),
2370 sensorTypeEscaped.end());
2371 std::string sensorId(sensorTypeEscaped);
2372 sensorId += "_";
2373 sensorId += sensorName;
2374
2375 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] =
2376 boost::urls::format("/redfish/v1/Chassis/{}/{}/{}",
2377 sensorsAsyncResp->chassisId,
2378 sensorsAsyncResp->chassisSubNode,
2379 sensorId);
2380 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue);
2381 }
2382 else
2383 {
2384 std::string fieldName;
2385 if (sensorsAsyncResp->efficientExpand)
2386 {
2387 fieldName = "Members";
2388 }
2389 else if (sensorType == "temperature")
2390 {
2391 fieldName = "Temperatures";
2392 }
2393 else if (sensorType == "fan" || sensorType == "fan_tach" ||
2394 sensorType == "fan_pwm")
2395 {
2396 fieldName = "Fans";
2397 }
2398 else if (sensorType == "voltage")
2399 {
2400 fieldName = "Voltages";
2401 }
2402 else if (sensorType == "power")
2403 {
2404 if (sensorName == "total_power")
2405 {
2406 fieldName = "PowerControl";
2407 }
2408 else if ((inventoryItem != nullptr) &&
2409 (inventoryItem->isPowerSupply))
2410 {
2411 fieldName = "PowerSupplies";
2412 }
2413 else
2414 {
2415 // Other power sensors are in SensorCollection
2416 continue;
2417 }
2418 }
2419 else
2420 {
2421 BMCWEB_LOG_ERROR("Unsure how to handle sensorType {}",
2422 sensorType);
2423 continue;
2424 }
2425
2426 nlohmann::json& tempArray =
2427 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName];
2428 if (fieldName == "PowerControl")
2429 {
2430 if (tempArray.empty())
2431 {
2432 // Put multiple "sensors" into a single
2433 // PowerControl. Follows MemberId naming and
2434 // naming in power.hpp.
2435 nlohmann::json::object_t power;
2436 boost::urls::url url = boost::urls::format(
2437 "/redfish/v1/Chassis/{}/{}",
2438 sensorsAsyncResp->chassisId,
2439 sensorsAsyncResp->chassisSubNode);
2440 url.set_fragment((""_json_pointer / fieldName / "0")
2441 .to_string());
2442 power["@odata.id"] = std::move(url);
2443 tempArray.emplace_back(std::move(power));
2444 }
2445 sensorJson = &(tempArray.back());
2446 }
2447 else if (fieldName == "PowerSupplies")
2448 {
2449 if (inventoryItem != nullptr)
2450 {
2451 sensorJson =
2452 &(getPowerSupply(tempArray, *inventoryItem,
2453 sensorsAsyncResp->chassisId));
2454 }
2455 }
2456 else if (fieldName == "Members")
2457 {
2458 std::string sensorTypeEscaped(sensorType);
2459 auto remove = std::ranges::remove(sensorTypeEscaped,
2460 '_');
2461 sensorTypeEscaped.erase(std::ranges::begin(remove),
2462 sensorTypeEscaped.end());
2463 std::string sensorId(sensorTypeEscaped);
2464 sensorId += "_";
2465 sensorId += sensorName;
2466
2467 nlohmann::json::object_t member;
2468 member["@odata.id"] = boost::urls::format(
2469 "/redfish/v1/Chassis/{}/{}/{}",
2470 sensorsAsyncResp->chassisId,
2471 sensorsAsyncResp->chassisSubNode, sensorId);
2472 tempArray.emplace_back(std::move(member));
2473 sensorJson = &(tempArray.back());
2474 }
2475 else
2476 {
2477 nlohmann::json::object_t member;
2478 boost::urls::url url = boost::urls::format(
2479 "/redfish/v1/Chassis/{}/{}",
2480 sensorsAsyncResp->chassisId,
2481 sensorsAsyncResp->chassisSubNode);
2482 url.set_fragment(
2483 (""_json_pointer / fieldName).to_string());
2484 member["@odata.id"] = std::move(url);
2485 tempArray.emplace_back(std::move(member));
2486 sensorJson = &(tempArray.back());
2487 }
2488 }
2489
2490 if (sensorJson != nullptr)
2491 {
2492 objectInterfacesToJson(sensorName, sensorType,
2493 sensorsAsyncResp->chassisSubNode,
2494 objDictEntry.second, *sensorJson,
2495 inventoryItem);
2496
2497 std::string path = "/xyz/openbmc_project/sensors/";
2498 path += sensorType;
2499 path += "/";
2500 path += sensorName;
2501 sensorsAsyncResp->addMetadata(*sensorJson, path);
2502 }
2503 }
2504 if (sensorsAsyncResp.use_count() == 1)
2505 {
2506 sortJSONResponse(sensorsAsyncResp);
2507 if (sensorsAsyncResp->chassisSubNode ==
2508 sensors::node::sensors &&
2509 sensorsAsyncResp->efficientExpand)
2510 {
2511 sensorsAsyncResp->asyncResp->res
2512 .jsonValue["Members@odata.count"] =
2513 sensorsAsyncResp->asyncResp->res.jsonValue["Members"]
2514 .size();
2515 }
2516 else if (sensorsAsyncResp->chassisSubNode ==
2517 sensors::node::thermal)
2518 {
2519 populateFanRedundancy(sensorsAsyncResp);
2520 }
2521 }
2522 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit");
2523 });
2524 }
2525 BMCWEB_LOG_DEBUG("getSensorData exit");
2526 }
2527
2528 inline void
processSensorList(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames)2529 processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2530 const std::shared_ptr<std::set<std::string>>& sensorNames)
2531 {
2532 auto getConnectionCb = [sensorsAsyncResp, sensorNames](
2533 const std::set<std::string>& connections) {
2534 BMCWEB_LOG_DEBUG("getConnectionCb enter");
2535 auto getInventoryItemsCb =
2536 [sensorsAsyncResp, sensorNames,
2537 connections](const std::shared_ptr<std::vector<InventoryItem>>&
2538 inventoryItems) {
2539 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter");
2540 // Get sensor data and store results in JSON
2541 getSensorData(sensorsAsyncResp, sensorNames, connections,
2542 inventoryItems);
2543 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit");
2544 };
2545
2546 // Get inventory items associated with sensors
2547 getInventoryItems(sensorsAsyncResp, sensorNames,
2548 std::move(getInventoryItemsCb));
2549
2550 BMCWEB_LOG_DEBUG("getConnectionCb exit");
2551 };
2552
2553 // Get set of connections that provide sensor values
2554 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2555 }
2556
2557 /**
2558 * @brief Entry point for retrieving sensors data related to requested
2559 * chassis.
2560 * @param SensorsAsyncResp Pointer to object holding response data
2561 */
2562 inline void
getChassisData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)2563 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2564 {
2565 BMCWEB_LOG_DEBUG("getChassisData enter");
2566 auto getChassisCb =
2567 [sensorsAsyncResp](
2568 const std::shared_ptr<std::set<std::string>>& sensorNames) {
2569 BMCWEB_LOG_DEBUG("getChassisCb enter");
2570 processSensorList(sensorsAsyncResp, sensorNames);
2571 BMCWEB_LOG_DEBUG("getChassisCb exit");
2572 };
2573 // SensorCollection doesn't contain the Redundancy property
2574 if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors)
2575 {
2576 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2577 nlohmann::json::array();
2578 }
2579 // Get set of sensors in chassis
2580 getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId,
2581 sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types,
2582 std::move(getChassisCb));
2583 BMCWEB_LOG_DEBUG("getChassisData exit");
2584 }
2585
2586 /**
2587 * @brief Find the requested sensorName in the list of all sensors supplied by
2588 * the chassis node
2589 *
2590 * @param sensorName The sensor name supplied in the PATCH request
2591 * @param sensorsList The list of sensors managed by the chassis node
2592 * @param sensorsModified The list of sensors that were found as a result of
2593 * repeated calls to this function
2594 */
2595 inline bool
findSensorNameUsingSensorPath(std::string_view sensorName,const std::set<std::string> & sensorsList,std::set<std::string> & sensorsModified)2596 findSensorNameUsingSensorPath(std::string_view sensorName,
2597 const std::set<std::string>& sensorsList,
2598 std::set<std::string>& sensorsModified)
2599 {
2600 for (const auto& chassisSensor : sensorsList)
2601 {
2602 sdbusplus::message::object_path path(chassisSensor);
2603 std::string thisSensorName = path.filename();
2604 if (thisSensorName.empty())
2605 {
2606 continue;
2607 }
2608 if (thisSensorName == sensorName)
2609 {
2610 sensorsModified.emplace(chassisSensor);
2611 return true;
2612 }
2613 }
2614 return false;
2615 }
2616
2617 inline std::pair<std::string, std::string>
splitSensorNameAndType(std::string_view sensorId)2618 splitSensorNameAndType(std::string_view sensorId)
2619 {
2620 size_t index = sensorId.find('_');
2621 if (index == std::string::npos)
2622 {
2623 return std::make_pair<std::string, std::string>("", "");
2624 }
2625 std::string sensorType{sensorId.substr(0, index)};
2626 std::string sensorName{sensorId.substr(index + 1)};
2627 // fan_pwm and fan_tach need special handling
2628 if (sensorType == "fantach" || sensorType == "fanpwm")
2629 {
2630 sensorType.insert(3, 1, '_');
2631 }
2632 return std::make_pair(sensorType, sensorName);
2633 }
2634
2635 /**
2636 * @brief Entry point for overriding sensor values of given sensor
2637 *
2638 * @param sensorAsyncResp response object
2639 * @param allCollections Collections extract from sensors' request patch info
2640 * @param chassisSubNode Chassis Node for which the query has to happen
2641 */
setSensorsOverride(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,std::unordered_map<std::string,std::vector<nlohmann::json::object_t>> & allCollections)2642 inline void setSensorsOverride(
2643 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2644 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>&
2645 allCollections)
2646 {
2647 BMCWEB_LOG_INFO("setSensorsOverride for subNode{}",
2648 sensorAsyncResp->chassisSubNode);
2649
2650 std::string_view propertyValueName;
2651 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2652 std::string memberId;
2653 double value = 0.0;
2654 for (auto& collectionItems : allCollections)
2655 {
2656 if (collectionItems.first == "Temperatures")
2657 {
2658 propertyValueName = "ReadingCelsius";
2659 }
2660 else if (collectionItems.first == "Fans")
2661 {
2662 propertyValueName = "Reading";
2663 }
2664 else
2665 {
2666 propertyValueName = "ReadingVolts";
2667 }
2668 for (auto& item : collectionItems.second)
2669 {
2670 if (!json_util::readJsonObject(
2671 item, sensorAsyncResp->asyncResp->res, "MemberId", memberId,
2672 propertyValueName, value))
2673 {
2674 return;
2675 }
2676 overrideMap.emplace(memberId,
2677 std::make_pair(value, collectionItems.first));
2678 }
2679 }
2680
2681 auto getChassisSensorListCb =
2682 [sensorAsyncResp, overrideMap,
2683 propertyValueNameStr = std::string(propertyValueName)](
2684 const std::shared_ptr<std::set<std::string>>& sensorsList) {
2685 // Match sensor names in the PATCH request to those managed by the
2686 // chassis node
2687 const std::shared_ptr<std::set<std::string>> sensorNames =
2688 std::make_shared<std::set<std::string>>();
2689 for (const auto& item : overrideMap)
2690 {
2691 const auto& sensor = item.first;
2692 std::pair<std::string, std::string> sensorNameType =
2693 splitSensorNameAndType(sensor);
2694 if (!findSensorNameUsingSensorPath(sensorNameType.second,
2695 *sensorsList, *sensorNames))
2696 {
2697 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first);
2698 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2699 item.second.second, item.first);
2700 return;
2701 }
2702 }
2703 // Get the connection to which the memberId belongs
2704 auto getObjectsWithConnectionCb =
2705 [sensorAsyncResp, overrideMap, propertyValueNameStr](
2706 const std::set<std::string>& /*connections*/,
2707 const std::set<std::pair<std::string, std::string>>&
2708 objectsWithConnection) {
2709 if (objectsWithConnection.size() != overrideMap.size())
2710 {
2711 BMCWEB_LOG_INFO(
2712 "Unable to find all objects with proper connection {} requested {}",
2713 objectsWithConnection.size(), overrideMap.size());
2714 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2715 sensorAsyncResp->chassisSubNode ==
2716 sensors::node::thermal
2717 ? "Temperatures"
2718 : "Voltages",
2719 "Count");
2720 return;
2721 }
2722 for (const auto& item : objectsWithConnection)
2723 {
2724 sdbusplus::message::object_path path(item.first);
2725 std::string sensorName = path.filename();
2726 if (sensorName.empty())
2727 {
2728 messages::internalError(sensorAsyncResp->asyncResp->res);
2729 return;
2730 }
2731 std::string id = path.parent_path().filename();
2732 auto remove = std::ranges::remove(id, '_');
2733 id.erase(std::ranges::begin(remove), id.end());
2734 id += "_";
2735 id += sensorName;
2736
2737 const auto& iterator = overrideMap.find(id);
2738 if (iterator == overrideMap.end())
2739 {
2740 BMCWEB_LOG_INFO("Unable to find sensor object{}",
2741 item.first);
2742 messages::internalError(sensorAsyncResp->asyncResp->res);
2743 return;
2744 }
2745 setDbusProperty(sensorAsyncResp->asyncResp,
2746 propertyValueNameStr, item.second, item.first,
2747 "xyz.openbmc_project.Sensor.Value", "Value",
2748 iterator->second.first);
2749 }
2750 };
2751 // Get object with connection for the given sensor name
2752 getObjectsWithConnection(sensorAsyncResp, sensorNames,
2753 std::move(getObjectsWithConnectionCb));
2754 };
2755 // get full sensor list for the given chassisId and cross verify the sensor.
2756 getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
2757 sensorAsyncResp->chassisSubNode, sensorAsyncResp->types,
2758 std::move(getChassisSensorListCb));
2759 }
2760
2761 /**
2762 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2763 * path of the sensor.
2764 *
2765 * Function builds valid Redfish response for sensor query of given chassis and
2766 * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2767 * it to caller in a callback.
2768 *
2769 * @param chassis Chassis for which retrieval should be performed
2770 * @param node Node (group) of sensors. See sensors::node for supported values
2771 * @param mapComplete Callback to be called with retrieval result
2772 */
2773 template <typename Callback>
retrieveUriToDbusMap(const std::string & chassis,const std::string & node,Callback && mapComplete)2774 inline void retrieveUriToDbusMap(const std::string& chassis,
2775 const std::string& node,
2776 Callback&& mapComplete)
2777 {
2778 decltype(sensors::paths)::const_iterator pathIt =
2779 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(),
2780 [&node](auto&& val) { return val.first == node; });
2781 if (pathIt == sensors::paths.cend())
2782 {
2783 BMCWEB_LOG_ERROR("Wrong node provided : {}", node);
2784 std::map<std::string, std::string> noop;
2785 mapComplete(boost::beast::http::status::bad_request, noop);
2786 return;
2787 }
2788
2789 auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
2790 auto callback = [asyncResp,
2791 mapCompleteCb = std::forward<Callback>(mapComplete)](
2792 const boost::beast::http::status status,
2793 const std::map<std::string, std::string>& uriToDbus) {
2794 mapCompleteCb(status, uriToDbus);
2795 };
2796
2797 auto resp = std::make_shared<SensorsAsyncResp>(
2798 asyncResp, chassis, pathIt->second, node, std::move(callback));
2799 getChassisData(resp);
2800 }
2801
2802 namespace sensors
2803 {
2804
getChassisCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view chassisId,std::string_view chassisSubNode,const std::shared_ptr<std::set<std::string>> & sensorNames)2805 inline void getChassisCallback(
2806 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2807 std::string_view chassisId, std::string_view chassisSubNode,
2808 const std::shared_ptr<std::set<std::string>>& sensorNames)
2809 {
2810 BMCWEB_LOG_DEBUG("getChassisCallback enter ");
2811
2812 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
2813 for (const std::string& sensor : *sensorNames)
2814 {
2815 BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor);
2816
2817 sdbusplus::message::object_path path(sensor);
2818 std::string sensorName = path.filename();
2819 if (sensorName.empty())
2820 {
2821 BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor);
2822 messages::internalError(asyncResp->res);
2823 return;
2824 }
2825 std::string type = path.parent_path().filename();
2826 // fan_tach has an underscore in it, so remove it to "normalize" the
2827 // type in the URI
2828 auto remove = std::ranges::remove(type, '_');
2829 type.erase(std::ranges::begin(remove), type.end());
2830
2831 nlohmann::json::object_t member;
2832 std::string id = type;
2833 id += "_";
2834 id += sensorName;
2835 member["@odata.id"] = boost::urls::format(
2836 "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id);
2837
2838 entriesArray.emplace_back(std::move(member));
2839 }
2840
2841 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
2842 BMCWEB_LOG_DEBUG("getChassisCallback exit");
2843 }
2844
handleSensorCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2845 inline void handleSensorCollectionGet(
2846 App& app, const crow::Request& req,
2847 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2848 const std::string& chassisId)
2849 {
2850 query_param::QueryCapabilities capabilities = {
2851 .canDelegateExpandLevel = 1,
2852 };
2853 query_param::Query delegatedQuery;
2854 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
2855 delegatedQuery, capabilities))
2856 {
2857 return;
2858 }
2859
2860 if (delegatedQuery.expandType != query_param::ExpandType::None)
2861 {
2862 // we perform efficient expand.
2863 auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>(
2864 asyncResp, chassisId, sensors::dbus::sensorPaths,
2865 sensors::node::sensors,
2866 /*efficientExpand=*/true);
2867 getChassisData(sensorsAsyncResp);
2868
2869 BMCWEB_LOG_DEBUG(
2870 "SensorCollection doGet exit via efficient expand handler");
2871 return;
2872 }
2873
2874 // We get all sensors as hyperlinkes in the chassis (this
2875 // implies we reply on the default query parameters handler)
2876 getChassis(asyncResp, chassisId, sensors::node::sensors, dbus::sensorPaths,
2877 std::bind_front(sensors::getChassisCallback, asyncResp,
2878 chassisId, sensors::node::sensors));
2879 }
2880
2881 inline void
getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & sensorPath,const::dbus::utility::MapperGetObject & mapperResponse)2882 getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2883 const std::string& sensorPath,
2884 const ::dbus::utility::MapperGetObject& mapperResponse)
2885 {
2886 if (mapperResponse.size() != 1)
2887 {
2888 messages::internalError(asyncResp->res);
2889 return;
2890 }
2891 const auto& valueIface = *mapperResponse.begin();
2892 const std::string& connectionName = valueIface.first;
2893 BMCWEB_LOG_DEBUG("Looking up {}", connectionName);
2894 BMCWEB_LOG_DEBUG("Path {}", sensorPath);
2895
2896 sdbusplus::asio::getAllProperties(
2897 *crow::connections::systemBus, connectionName, sensorPath, "",
2898 [asyncResp,
2899 sensorPath](const boost::system::error_code& ec,
2900 const ::dbus::utility::DBusPropertiesMap& valuesDict) {
2901 if (ec)
2902 {
2903 messages::internalError(asyncResp->res);
2904 return;
2905 }
2906 sdbusplus::message::object_path path(sensorPath);
2907 std::string name = path.filename();
2908 path = path.parent_path();
2909 std::string type = path.filename();
2910 objectPropertiesToJson(name, type, sensors::node::sensors, valuesDict,
2911 asyncResp->res.jsonValue, nullptr);
2912 });
2913 }
2914
handleSensorGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & sensorId)2915 inline void handleSensorGet(App& app, const crow::Request& req,
2916 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2917 const std::string& chassisId,
2918 const std::string& sensorId)
2919 {
2920 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2921 {
2922 return;
2923 }
2924 std::pair<std::string, std::string> nameType =
2925 splitSensorNameAndType(sensorId);
2926 if (nameType.first.empty() || nameType.second.empty())
2927 {
2928 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2929 return;
2930 }
2931
2932 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2933 "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId);
2934
2935 BMCWEB_LOG_DEBUG("Sensor doGet enter");
2936
2937 constexpr std::array<std::string_view, 1> interfaces = {
2938 "xyz.openbmc_project.Sensor.Value"};
2939 std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first +
2940 '/' + nameType.second;
2941 // Get a list of all of the sensors that implement Sensor.Value
2942 // and get the path and service name associated with the sensor
2943 ::dbus::utility::getDbusObject(
2944 sensorPath, interfaces,
2945 [asyncResp, sensorId,
2946 sensorPath](const boost::system::error_code& ec,
2947 const ::dbus::utility::MapperGetObject& subtree) {
2948 BMCWEB_LOG_DEBUG("respHandler1 enter");
2949 if (ec == boost::system::errc::io_error)
2950 {
2951 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths");
2952 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2953 return;
2954 }
2955 if (ec)
2956 {
2957 messages::internalError(asyncResp->res);
2958 BMCWEB_LOG_ERROR(
2959 "Sensor getSensorPaths resp_handler: Dbus error {}", ec);
2960 return;
2961 }
2962 getSensorFromDbus(asyncResp, sensorPath, subtree);
2963 BMCWEB_LOG_DEBUG("respHandler1 exit");
2964 });
2965 }
2966
2967 } // namespace sensors
2968
requestRoutesSensorCollection(App & app)2969 inline void requestRoutesSensorCollection(App& app)
2970 {
2971 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
2972 .privileges(redfish::privileges::getSensorCollection)
2973 .methods(boost::beast::http::verb::get)(
2974 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app)));
2975 }
2976
requestRoutesSensor(App & app)2977 inline void requestRoutesSensor(App& app)
2978 {
2979 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
2980 .privileges(redfish::privileges::getSensor)
2981 .methods(boost::beast::http::verb::get)(
2982 std::bind_front(sensors::handleSensorGet, std::ref(app)));
2983 }
2984
2985 } // namespace redfish
2986