blob: 1f6e6721dc1e5778be0ae2ce22f730b4dbd0ac3e [file] [log] [blame]
Josh Lehande745422020-11-07 02:14:09 -08001#include "pid/ec/logging.hpp"
Patrick Ventureda4a5dd2018-08-31 09:42:48 -07002#include "pid/ec/pid.hpp"
Patrick Venturea58197c2018-06-11 15:29:45 -07003#include "pid/zone.hpp"
Patrick Ventureda4a5dd2018-08-31 09:42:48 -07004#include "sensors/manager.hpp"
5#include "test/controller_mock.hpp"
6#include "test/helpers.hpp"
7#include "test/sensor_mock.hpp"
Patrick Venturea58197c2018-06-11 15:29:45 -07008
Patrick Venturea83a3ec2020-08-04 09:52:05 -07009#include <sdbusplus/test/sdbus_mock.hpp>
10
Patrick Venturea58197c2018-06-11 15:29:45 -070011#include <chrono>
12#include <cstring>
Patrick Venturea58197c2018-06-11 15:29:45 -070013#include <vector>
14
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070015#include <gmock/gmock.h>
16#include <gtest/gtest.h>
Patrick Venturea58197c2018-06-11 15:29:45 -070017
Patrick Venturea0764872020-08-08 07:48:43 -070018namespace pid_control
19{
20namespace
21{
22
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070023using ::testing::_;
Patrick Venturea58197c2018-06-11 15:29:45 -070024using ::testing::IsNull;
25using ::testing::Return;
26using ::testing::StrEq;
Patrick Venturea58197c2018-06-11 15:29:45 -070027
28static std::string modeInterface = "xyz.openbmc_project.Control.Mode";
Harvey Wucc0232a2023-02-09 14:58:55 +080029static std::string debugZoneInterface = "xyz.openbmc_project.Debug.Pid.Zone";
ykchiu7c6d35d2023-05-10 17:01:46 +080030static std::string enableInterface = "xyz.openbmc_project.Object.Enable";
Harvey Wu37180062023-10-02 09:42:50 +080031static std::string debugThermalPowerInterface =
32 "xyz.openbmc_project.Debug.Pid.ThermalPower";
Patrick Venturea58197c2018-06-11 15:29:45 -070033
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070034namespace
35{
Patrick Venturea58197c2018-06-11 15:29:45 -070036
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070037TEST(PidZoneConstructorTest, BoringConstructorTest)
38{
Patrick Venturea58197c2018-06-11 15:29:45 -070039 // Build a PID Zone.
40
ykchiu7c6d35d2023-05-10 17:01:46 +080041 sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode,
42 sdbus_mock_enable;
Patrick Venturea58197c2018-06-11 15:29:45 -070043 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
44 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
45 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
ykchiu7c6d35d2023-05-10 17:01:46 +080046 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
Patrick Venturea58197c2018-06-11 15:29:45 -070047
48 EXPECT_CALL(sdbus_mock_host,
49 sd_bus_add_object_manager(
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070050 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors")))
Patrick Venturea58197c2018-06-11 15:29:45 -070051 .WillOnce(Return(0));
52
James Feist1fe08952019-05-07 09:17:16 -070053 SensorManager m(bus_mock_passive, bus_mock_host);
Patrick Venturea58197c2018-06-11 15:29:45 -070054
55 bool defer = true;
Delphine CC Chiu97889632023-11-06 11:32:46 +080056 bool accSetPoint = false;
Patrick Venturee2ec0f62018-09-04 12:30:27 -070057 const char* objPath = "/path/";
Patrick Venturea58197c2018-06-11 15:29:45 -070058 int64_t zone = 1;
James Feist3484bed2019-02-25 13:28:18 -080059 double minThermalOutput = 1000.0;
ykchiu9fe3a3c2023-05-11 13:43:54 +080060 double failSafePercent = 0;
Bonnie Lo0e8fc392022-10-05 10:20:55 +080061 conf::CycleTime cycleTime;
Patrick Venturea58197c2018-06-11 15:29:45 -070062
James Feist0709e2f2020-07-08 10:59:45 -070063 double d;
Patrick Venturea58197c2018-06-11 15:29:45 -070064 std::vector<std::string> properties;
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070065 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties,
James Feist0709e2f2020-07-08 10:59:45 -070066 &d);
Harvey Wucc0232a2023-02-09 14:58:55 +080067 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
68 properties, &d);
Patrick Venturea58197c2018-06-11 15:29:45 -070069
ykchiu7c6d35d2023-05-10 17:01:46 +080070 std::string sensorname = "temp1";
71 std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
72 sensorname;
73
74 double de;
75 std::vector<std::string> propertiesenable;
76 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
77 enableInterface, propertiesenable, &de);
78
Harvey Wu37180062023-10-02 09:42:50 +080079 EXPECT_CALL(sdbus_mock_enable,
80 sd_bus_add_object_vtable(
81 IsNull(), NotNull(), StrEq(pidsensorpath.c_str()),
82 StrEq(debugThermalPowerInterface), NotNull(), NotNull()))
83 .Times(::testing::AnyNumber())
84 .WillOnce(Return(0));
85
Bonnie Lo0e8fc392022-10-05 10:20:55 +080086 DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m,
Delphine CC Chiu97889632023-11-06 11:32:46 +080087 bus_mock_mode, objPath, defer, accSetPoint);
Patrick Venturea58197c2018-06-11 15:29:45 -070088 // Success.
89}
90
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070091} // namespace
Patrick Venturea58197c2018-06-11 15:29:45 -070092
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070093class PidZoneTest : public ::testing::Test
94{
95 protected:
96 PidZoneTest() :
97 property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(),
ykchiu7c6d35d2023-05-10 17:01:46 +080098 sdbus_mock_mode(), sdbus_mock_enable()
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070099 {
100 EXPECT_CALL(sdbus_mock_host,
101 sd_bus_add_object_manager(
102 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors")))
103 .WillOnce(Return(0));
Patrick Venturea58197c2018-06-11 15:29:45 -0700104
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700105 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
106 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
107 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
ykchiu7c6d35d2023-05-10 17:01:46 +0800108 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
Patrick Venturea58197c2018-06-11 15:29:45 -0700109
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700110 // Compiler weirdly not happy about just instantiating mgr(...);
James Feist1fe08952019-05-07 09:17:16 -0700111 SensorManager m(bus_mock_passive, bus_mock_host);
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700112 mgr = std::move(m);
Patrick Venturea58197c2018-06-11 15:29:45 -0700113
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700114 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
115 properties, &property_index);
Harvey Wucc0232a2023-02-09 14:58:55 +0800116 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
117 properties, &property_index);
Patrick Venturea58197c2018-06-11 15:29:45 -0700118
ykchiu7c6d35d2023-05-10 17:01:46 +0800119 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
120 enableInterface, propertiesenable,
121 &propertyenable_index);
Harvey Wu37180062023-10-02 09:42:50 +0800122 EXPECT_CALL(sdbus_mock_enable,
123 sd_bus_add_object_vtable(IsNull(), NotNull(),
124 StrEq(pidsensorpath.c_str()),
125 StrEq(debugThermalPowerInterface),
126 NotNull(), NotNull()))
127 .Times(::testing::AnyNumber())
128 .WillOnce(Return(0));
ykchiu7c6d35d2023-05-10 17:01:46 +0800129
Delphine CC Chiu97889632023-11-06 11:32:46 +0800130 zone = std::make_unique<DbusPidZone>(
131 zoneId, minThermalOutput, failSafePercent, cycleTime, mgr,
132 bus_mock_mode, objPath, defer, accSetPoint);
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700133 }
Patrick Venturea58197c2018-06-11 15:29:45 -0700134
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700135 // unused
James Feist0709e2f2020-07-08 10:59:45 -0700136 double property_index;
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700137 std::vector<std::string> properties;
ykchiu7c6d35d2023-05-10 17:01:46 +0800138 double propertyenable_index;
139 std::vector<std::string> propertiesenable;
Patrick Venturea58197c2018-06-11 15:29:45 -0700140
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700141 sdbusplus::SdBusMock sdbus_mock_passive;
142 sdbusplus::SdBusMock sdbus_mock_host;
143 sdbusplus::SdBusMock sdbus_mock_mode;
ykchiu7c6d35d2023-05-10 17:01:46 +0800144 sdbusplus::SdBusMock sdbus_mock_enable;
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700145 int64_t zoneId = 1;
James Feist3484bed2019-02-25 13:28:18 -0800146 double minThermalOutput = 1000.0;
ykchiu9fe3a3c2023-05-11 13:43:54 +0800147 double failSafePercent = 0;
Harvey Wu37180062023-10-02 09:42:50 +0800148 double setpoint = 50.0;
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700149 bool defer = true;
Delphine CC Chiu97889632023-11-06 11:32:46 +0800150 bool accSetPoint = false;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700151 const char* objPath = "/path/";
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700152 SensorManager mgr;
Bonnie Lo0e8fc392022-10-05 10:20:55 +0800153 conf::CycleTime cycleTime;
Patrick Venturea58197c2018-06-11 15:29:45 -0700154
ykchiu7c6d35d2023-05-10 17:01:46 +0800155 std::string sensorname = "temp1";
Harvey Wu37180062023-10-02 09:42:50 +0800156 std::string sensorType = "temp";
ykchiu7c6d35d2023-05-10 17:01:46 +0800157 std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
158 sensorname;
159
Patrick Venture597ebd62020-08-11 08:48:19 -0700160 std::unique_ptr<DbusPidZone> zone;
Patrick Venturea58197c2018-06-11 15:29:45 -0700161};
162
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700163TEST_F(PidZoneTest, GetZoneId_ReturnsExpected)
164{
Patrick Venturea58197c2018-06-11 15:29:45 -0700165 // Verifies the zoneId returned is what we expect.
166
Patrick Venture0bbeaf82018-10-30 18:50:31 -0700167 EXPECT_EQ(zoneId, zone->getZoneID());
Patrick Venturea58197c2018-06-11 15:29:45 -0700168}
169
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700170TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected)
171{
Patrick Venturea58197c2018-06-11 15:29:45 -0700172 // Verifies that the zone starts in manual mode. Verifies that one can set
173 // the mode.
174 EXPECT_FALSE(zone->getManualMode());
175
176 zone->setManualMode(true);
177 EXPECT_TRUE(zone->getManualMode());
178}
179
ykchiu7c6d35d2023-05-10 17:01:46 +0800180TEST_F(PidZoneTest, AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected)
181{
182 // Verifies that the zone starts in enable mode. Verifies that one can set
183 // enable the mode.
184 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
185
186 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
187 IsNull(), StrEq(pidsensorpath.c_str()),
188 StrEq(enableInterface), NotNull()))
189 .Times(::testing::AnyNumber())
190 .WillOnce(Invoke(
191 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
192 [[maybe_unused]] const char* interface, const char** names) {
193 EXPECT_STREQ("Enable", names[0]);
194 return 0;
Patrick Williamse1dbb592023-10-20 11:19:22 -0500195 }));
ykchiu7c6d35d2023-05-10 17:01:46 +0800196
Harvey Wu37180062023-10-02 09:42:50 +0800197 zone->addPidControlProcess(sensorname, sensorType, setpoint,
198 bus_mock_enable, pidsensorpath.c_str(), defer);
ykchiu7c6d35d2023-05-10 17:01:46 +0800199 EXPECT_TRUE(zone->isPidProcessEnabled(sensorname));
200}
201
Josh Lehana4146eb2020-10-01 11:49:09 -0700202TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode)
203{
204 // Tests adding a fan PID controller to the zone, and verifies it's
205 // touched during processing.
206
207 std::unique_ptr<PIDController> tpid =
208 std::make_unique<ControllerMock>("fan1", zone.get());
209 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
210
211 // Access the internal pid configuration to clear it out (unrelated to the
212 // test).
213 ec::pid_info_t* info = tpid->getPIDInfo();
214 std::memset(info, 0x00, sizeof(ec::pid_info_t));
215
216 zone->addFanPID(std::move(tpid));
217
218 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
219 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
220 EXPECT_CALL(*tmock, outputProc(_));
221
222 // while zone is in auto mode redundant writes should be disabled
223 EXPECT_FALSE(zone->getRedundantWrite());
224
225 // but switching from manual to auto enables a single redundant write
226 zone->setManualMode(true);
227 zone->setManualMode(false);
228 EXPECT_TRUE(zone->getRedundantWrite());
229
230 // after one iteration of a pid loop redundant write should be cleared
231 zone->processFans();
232 EXPECT_FALSE(zone->getRedundantWrite());
233}
234
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700235TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected)
236{
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700237 // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest
Nirav Shahccc8bb62022-02-17 21:06:51 -0800238 // and getMinThermalSetPoint.
Patrick Venturea58197c2018-06-11 15:29:45 -0700239
ykchiu7c6d35d2023-05-10 17:01:46 +0800240 // Need to add pid control process for the zone that can enable
241 // the process and add the set point.
242 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
243
244 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
245 IsNull(), StrEq(pidsensorpath.c_str()),
246 StrEq(enableInterface), NotNull()))
247 .Times(::testing::AnyNumber())
248 .WillOnce(Invoke(
249 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
250 [[maybe_unused]] const char* interface, const char** names) {
251 EXPECT_STREQ("Enable", names[0]);
252 return 0;
Patrick Williamse1dbb592023-10-20 11:19:22 -0500253 }));
ykchiu7c6d35d2023-05-10 17:01:46 +0800254
Harvey Wu37180062023-10-02 09:42:50 +0800255 zone->addPidControlProcess(sensorname, sensorType, setpoint,
256 bus_mock_enable, pidsensorpath.c_str(), defer);
ykchiu7c6d35d2023-05-10 17:01:46 +0800257
Patrick Venturea58197c2018-06-11 15:29:45 -0700258 // At least one value must be above the minimum thermal setpoint used in
259 // the constructor otherwise it'll choose that value
Patrick Venture5f59c0f2018-11-11 12:55:14 -0800260 std::vector<double> values = {100, 200, 300, 400, 500, 5000};
ykchiu7c6d35d2023-05-10 17:01:46 +0800261
Patrick Venturea58197c2018-06-11 15:29:45 -0700262 for (auto v : values)
263 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800264 zone->addSetPoint(v, sensorname);
Patrick Venturea58197c2018-06-11 15:29:45 -0700265 }
266
267 // This will pull the maximum RPM setpoint request.
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700268 zone->determineMaxSetPointRequest();
269 EXPECT_EQ(5000, zone->getMaxSetPointRequest());
Patrick Venturea58197c2018-06-11 15:29:45 -0700270
271 // Clear the values, so it'll choose the minimum thermal setpoint.
Patrick Venture9bbf3332019-07-16 10:50:37 -0700272 zone->clearSetPoints();
Patrick Venturea58197c2018-06-11 15:29:45 -0700273
274 // This will go through the RPM set point values and grab the maximum.
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700275 zone->determineMaxSetPointRequest();
Nirav Shahccc8bb62022-02-17 21:06:51 -0800276 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
Patrick Venturea58197c2018-06-11 15:29:45 -0700277}
278
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700279TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected)
280{
Patrick Venturea58197c2018-06-11 15:29:45 -0700281 // Tests adding several RPM setpoints, however, they're all lower than the
Patrick Venture7280e272019-02-11 10:45:32 -0800282 // configured minimal thermal setpoint RPM value.
Patrick Venturea58197c2018-06-11 15:29:45 -0700283
ykchiu7c6d35d2023-05-10 17:01:46 +0800284 // Need to add pid control process for the zone that can enable
285 // the process and add the set point.
286 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
287
288 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
289 IsNull(), StrEq(pidsensorpath.c_str()),
290 StrEq(enableInterface), NotNull()))
291 .Times(::testing::AnyNumber())
292 .WillOnce(Invoke(
293 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
294 [[maybe_unused]] const char* interface, const char** names) {
295 EXPECT_STREQ("Enable", names[0]);
296 return 0;
Patrick Williamse1dbb592023-10-20 11:19:22 -0500297 }));
ykchiu7c6d35d2023-05-10 17:01:46 +0800298
Harvey Wu37180062023-10-02 09:42:50 +0800299 zone->addPidControlProcess(sensorname, sensorType, setpoint,
300 bus_mock_enable, pidsensorpath.c_str(), defer);
ykchiu7c6d35d2023-05-10 17:01:46 +0800301
Patrick Venture5f59c0f2018-11-11 12:55:14 -0800302 std::vector<double> values = {100, 200, 300, 400, 500};
ykchiu7c6d35d2023-05-10 17:01:46 +0800303
Patrick Venturea58197c2018-06-11 15:29:45 -0700304 for (auto v : values)
305 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800306 zone->addSetPoint(v, sensorname);
Patrick Venturea58197c2018-06-11 15:29:45 -0700307 }
308
309 // This will pull the maximum RPM setpoint request.
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700310 zone->determineMaxSetPointRequest();
Patrick Venturea58197c2018-06-11 15:29:45 -0700311
312 // Verifies the value returned in the minimal thermal rpm set point.
Nirav Shahccc8bb62022-02-17 21:06:51 -0800313 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
Patrick Venturea58197c2018-06-11 15:29:45 -0700314}
315
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700316TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected)
317{
Patrick Venturea58197c2018-06-11 15:29:45 -0700318 // Verify the value used to create the object is stored.
ykchiu9fe3a3c2023-05-11 13:43:54 +0800319 // when the final failsafe percent is zero , it indicate
320 // no failsafe percent is configured  , set it to 100% as
321 // the default setting.
322
323 std::vector<double> values = {0, 0, 0};
324 int64_t defaultPercent = 100;
325
326 zone->addPidFailSafePercent("temp1", values[0]);
327 zone->addPidFailSafePercent("temp2", values[1]);
328 zone->addPidFailSafePercent("temp3", values[2]);
329
330 zone->initPidFailSafePercent();
331
332 EXPECT_EQ(defaultPercent, zone->getFailSafePercent());
333}
334
335TEST_F(PidZoneTest, GetFailSafePercent_VerifyReturnsExpected)
336{
337 // Tests adding PID controller with FailSafePercent to the zone,
338 // and verifies it's returned as expected.
339
340 std::vector<double> values = {60, 80, 70};
341 double max_value = 0;
342
343 for (const auto& value : values)
344 {
345 max_value = std::max(max_value, value);
346 }
347
348 zone->addPidFailSafePercent("temp1", values[0]);
349 zone->addPidFailSafePercent("temp2", values[1]);
350 zone->addPidFailSafePercent("temp3", values[2]);
351
352 zone->initPidFailSafePercent();
353
354 EXPECT_EQ(max_value, zone->getFailSafePercent());
Patrick Venturea58197c2018-06-11 15:29:45 -0700355}
356
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700357TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
358{
Patrick Venturea58197c2018-06-11 15:29:45 -0700359 // This test will add a couple thermal inputs, and verify that the zone
360 // initializes into failsafe mode, and will read each sensor.
361
362 std::string name1 = "temp1";
363 int64_t timeout = 1;
364
Patrick Williams8c051122023-05-10 07:50:59 -0500365 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
366 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700367 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700368
369 std::string name2 = "temp2";
Patrick Williams8c051122023-05-10 07:50:59 -0500370 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
371 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700372 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700373
374 std::string type = "unchecked";
375 mgr.addSensor(type, name1, std::move(sensor1));
376 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
377 mgr.addSensor(type, name2, std::move(sensor2));
378 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
379
380 // Now that the sensors exist, add them to the zone.
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800381 zone->addThermalInput(name1, false);
382 zone->addThermalInput(name2, false);
Patrick Venturea58197c2018-06-11 15:29:45 -0700383
384 // Initialize Zone
385 zone->initializeCache();
386
387 // Verify now in failsafe mode.
388 EXPECT_TRUE(zone->getFailSafeMode());
389
390 ReadReturn r1;
391 r1.value = 10.0;
392 r1.updated = std::chrono::high_resolution_clock::now();
393 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
394
395 ReadReturn r2;
396 r2.value = 11.0;
397 r2.updated = std::chrono::high_resolution_clock::now();
398 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
399
400 // Read the sensors, this will put the values into the cache.
401 zone->updateSensors();
402
403 // We should no longer be in failsafe mode.
404 EXPECT_FALSE(zone->getFailSafeMode());
405
406 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
407 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
408}
409
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700410TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
411{
Patrick Venturea58197c2018-06-11 15:29:45 -0700412 // This will add a couple fan inputs, and verify the values are cached.
413
414 std::string name1 = "fan1";
415 int64_t timeout = 2;
416
Patrick Williams8c051122023-05-10 07:50:59 -0500417 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
418 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700419 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700420
421 std::string name2 = "fan2";
Patrick Williams8c051122023-05-10 07:50:59 -0500422 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
423 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700424 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700425
426 std::string type = "unchecked";
427 mgr.addSensor(type, name1, std::move(sensor1));
428 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
429 mgr.addSensor(type, name2, std::move(sensor2));
430 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
431
432 // Now that the sensors exist, add them to the zone.
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800433 zone->addFanInput(name1, false);
434 zone->addFanInput(name2, false);
Patrick Venturea58197c2018-06-11 15:29:45 -0700435
436 // Initialize Zone
437 zone->initializeCache();
438
439 ReadReturn r1;
440 r1.value = 10.0;
441 r1.updated = std::chrono::high_resolution_clock::now();
442 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
443
444 ReadReturn r2;
445 r2.value = 11.0;
446 r2.updated = std::chrono::high_resolution_clock::now();
447 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
448
449 // Method under test will read through each fan sensor for the zone and
450 // cache the values.
451 zone->updateFanTelemetry();
452
453 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
454 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
455}
456
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700457TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
458{
Patrick Venturea58197c2018-06-11 15:29:45 -0700459 // On the second updateSensors call, the updated timestamp will be beyond
460 // the timeout limit.
461
462 int64_t timeout = 1;
463
464 std::string name1 = "temp1";
Patrick Williams8c051122023-05-10 07:50:59 -0500465 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
466 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700467 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700468
469 std::string name2 = "temp2";
Patrick Williams8c051122023-05-10 07:50:59 -0500470 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
471 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700472 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700473
474 std::string type = "unchecked";
475 mgr.addSensor(type, name1, std::move(sensor1));
476 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
477 mgr.addSensor(type, name2, std::move(sensor2));
478 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
479
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800480 zone->addThermalInput(name1, false);
481 zone->addThermalInput(name2, false);
Patrick Venturea58197c2018-06-11 15:29:45 -0700482
483 // Initialize Zone
484 zone->initializeCache();
485
486 // Verify now in failsafe mode.
487 EXPECT_TRUE(zone->getFailSafeMode());
488
489 ReadReturn r1;
490 r1.value = 10.0;
491 r1.updated = std::chrono::high_resolution_clock::now();
492 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
493
494 ReadReturn r2;
495 r2.value = 11.0;
496 r2.updated = std::chrono::high_resolution_clock::now();
497 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
498
499 zone->updateSensors();
500 EXPECT_FALSE(zone->getFailSafeMode());
501
502 // Ok, so we're not in failsafe mode, so let's set updated to the past.
503 // sensor1 will have an updated field older than its timeout value, but
504 // sensor2 will be fine. :D
505 r1.updated -= std::chrono::seconds(3);
506 r2.updated = std::chrono::high_resolution_clock::now();
507
508 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
509 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
510
511 // Method under test will read each sensor. One sensor's value is older
512 // than the timeout for that sensor and this triggers failsafe mode.
513 zone->updateSensors();
514 EXPECT_TRUE(zone->getFailSafeMode());
515}
516
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800517TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe)
518{
519 // This is similar to the above test, but because missingIsAcceptable
520 // is set for sensor1, the zone should not enter failsafe mode when
521 // only sensor1 goes missing.
522 // However, sensor2 going missing should still trigger failsafe mode.
523
524 int64_t timeout = 1;
525
526 std::string name1 = "temp1";
527 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
528 timeout);
529 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
530
531 std::string name2 = "temp2";
532 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
533 timeout);
534 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
535
536 std::string type = "unchecked";
537 mgr.addSensor(type, name1, std::move(sensor1));
538 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
539 mgr.addSensor(type, name2, std::move(sensor2));
540 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
541
542 // Only sensor1 has MissingIsAcceptable enabled for it
543 zone->addThermalInput(name1, true);
544 zone->addThermalInput(name2, false);
545
546 // Initialize Zone
547 zone->initializeCache();
548
549 // As sensors are not initialized, zone should be in failsafe mode
550 EXPECT_TRUE(zone->getFailSafeMode());
551
552 // r1 not populated here, intentionally, to simulate a sensor that
553 // is not available yet, perhaps takes a long time to start up.
554 ReadReturn r1;
555 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
556
557 ReadReturn r2;
558 r2.value = 11.0;
559 r2.updated = std::chrono::high_resolution_clock::now();
560 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
561
562 zone->updateSensors();
563
564 // Only sensor2 has been initialized here. Failsafe should be false,
565 // because sensor1 MissingIsAcceptable so it is OK for it to go missing.
566 EXPECT_FALSE(zone->getFailSafeMode());
567
568 r1.value = 10.0;
569 r1.updated = std::chrono::high_resolution_clock::now();
570
571 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
572 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
573 zone->updateSensors();
574
575 // Both sensors are now properly initialized
576 EXPECT_FALSE(zone->getFailSafeMode());
577
578 // Ok, so we're not in failsafe mode, so let's set updated to the past.
579 // sensor1 will have an updated field older than its timeout value, but
580 // sensor2 will be fine. :D
581 r1.updated -= std::chrono::seconds(3);
582 r2.updated = std::chrono::high_resolution_clock::now();
583
584 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
585 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
586 zone->updateSensors();
587
588 // MissingIsAcceptable is true for sensor1, so the zone should not be
589 // thrown into failsafe mode.
590 EXPECT_FALSE(zone->getFailSafeMode());
591
592 // Do the same thing, but for the opposite sensors: r1 is good,
593 // but r2 is set to some time in the past.
594 r1.updated = std::chrono::high_resolution_clock::now();
595 r2.updated -= std::chrono::seconds(3);
596
597 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
598 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
599 zone->updateSensors();
600
601 // Now, the zone should be in failsafe mode, because sensor2 does not
602 // have MissingIsAcceptable set true, it is still subject to failsafe.
603 EXPECT_TRUE(zone->getFailSafeMode());
604
605 r1.updated = std::chrono::high_resolution_clock::now();
606 r2.updated = std::chrono::high_resolution_clock::now();
607
608 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
609 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
610 zone->updateSensors();
611
612 // The failsafe mode should cease, as both sensors are good again.
613 EXPECT_FALSE(zone->getFailSafeMode());
614}
615
Will Liangded0ab52019-05-15 17:10:06 +0800616TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
617{
618 // This will add a couple fan inputs, and verify the values are cached.
619
620 std::string name1 = "fan1";
621 int64_t timeout = 2;
622
Patrick Williams8c051122023-05-10 07:50:59 -0500623 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
624 timeout);
Will Liangded0ab52019-05-15 17:10:06 +0800625 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
626
627 std::string name2 = "fan2";
Patrick Williams8c051122023-05-10 07:50:59 -0500628 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
629 timeout);
Will Liangded0ab52019-05-15 17:10:06 +0800630 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
631
632 std::string type = "unchecked";
633 mgr.addSensor(type, name1, std::move(sensor1));
634 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
635 mgr.addSensor(type, name2, std::move(sensor2));
636 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
637
638 // Now that the sensors exist, add them to the zone.
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800639 zone->addFanInput(name1, false);
640 zone->addFanInput(name2, false);
Will Liangded0ab52019-05-15 17:10:06 +0800641
642 // Initialize Zone
643 zone->initializeCache();
644
645 // Verify now in failsafe mode.
646 EXPECT_TRUE(zone->getFailSafeMode());
647
648 ReadReturn r1;
649 r1.value = 10.0;
650 r1.updated = std::chrono::high_resolution_clock::now();
651 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
652
653 ReadReturn r2;
654 r2.value = 11.0;
655 r2.updated = std::chrono::high_resolution_clock::now();
656 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
657
658 // Method under test will read through each fan sensor for the zone and
659 // cache the values.
660 zone->updateFanTelemetry();
661
662 // We should no longer be in failsafe mode.
663 EXPECT_FALSE(zone->getFailSafeMode());
664
665 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
666 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
667}
668
669TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
670{
671 // This will add a couple fan inputs, and verify the values are cached.
672
673 std::string name1 = "fan1";
674 int64_t timeout = 2;
675
Patrick Williams8c051122023-05-10 07:50:59 -0500676 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
677 timeout);
Will Liangded0ab52019-05-15 17:10:06 +0800678 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
679
680 std::string name2 = "fan2";
Patrick Williams8c051122023-05-10 07:50:59 -0500681 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
682 timeout);
Will Liangded0ab52019-05-15 17:10:06 +0800683 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
684
685 std::string type = "unchecked";
686 mgr.addSensor(type, name1, std::move(sensor1));
687 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
688 mgr.addSensor(type, name2, std::move(sensor2));
689 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
690
691 // Now that the sensors exist, add them to the zone.
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800692 zone->addFanInput(name1, false);
693 zone->addFanInput(name2, false);
Will Liangded0ab52019-05-15 17:10:06 +0800694
695 // Initialize Zone
696 zone->initializeCache();
697
698 // Verify now in failsafe mode.
699 EXPECT_TRUE(zone->getFailSafeMode());
700
701 ReadReturn r1;
702 r1.value = 10.0;
703 r1.updated = std::chrono::high_resolution_clock::now();
704 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
705
706 ReadReturn r2;
707 r2.value = 11.0;
708 r2.updated = std::chrono::high_resolution_clock::now();
709 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
710
711 // Method under test will read through each fan sensor for the zone and
712 // cache the values.
713 zone->updateFanTelemetry();
714
715 // We should no longer be in failsafe mode.
716 EXPECT_FALSE(zone->getFailSafeMode());
717
718 r1.updated -= std::chrono::seconds(3);
719 r2.updated = std::chrono::high_resolution_clock::now();
720
721 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
722 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
723
724 zone->updateFanTelemetry();
725 EXPECT_TRUE(zone->getFailSafeMode());
726}
727
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700728TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
729{
Patrick Venturea58197c2018-06-11 15:29:45 -0700730 // One can grab a sensor from the manager through the zone.
731
732 int64_t timeout = 1;
733
734 std::string name1 = "temp1";
Patrick Williams8c051122023-05-10 07:50:59 -0500735 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
736 timeout);
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700737 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700738
739 std::string type = "unchecked";
740 mgr.addSensor(type, name1, std::move(sensor1));
741 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
742
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800743 zone->addThermalInput(name1, false);
Patrick Venturea58197c2018-06-11 15:29:45 -0700744
745 // Verify method under test returns the pointer we expect.
746 EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
747}
748
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700749TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
750{
Patrick Venturea58197c2018-06-11 15:29:45 -0700751 // Tests adding a thermal PID controller to the zone, and verifies it's
752 // touched during processing.
753
754 std::unique_ptr<PIDController> tpid =
755 std::make_unique<ControllerMock>("thermal1", zone.get());
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700756 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700757
758 // Access the internal pid configuration to clear it out (unrelated to the
759 // test).
Patrick Venture563a3562018-10-30 09:31:26 -0700760 ec::pid_info_t* info = tpid->getPIDInfo();
Patrick Venturea58197c2018-06-11 15:29:45 -0700761 std::memset(info, 0x00, sizeof(ec::pid_info_t));
762
763 zone->addThermalPID(std::move(tpid));
764
Patrick Venture563a3562018-10-30 09:31:26 -0700765 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
766 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
767 EXPECT_CALL(*tmock, outputProc(_));
Patrick Venturea58197c2018-06-11 15:29:45 -0700768
769 // Method under test will, for each thermal PID, call setpt, input, and
770 // output.
Patrick Venture563a3562018-10-30 09:31:26 -0700771 zone->processThermals();
Patrick Venturea58197c2018-06-11 15:29:45 -0700772}
773
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700774TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
775{
Patrick Venturea58197c2018-06-11 15:29:45 -0700776 // Tests adding a fan PID controller to the zone, and verifies it's
777 // touched during processing.
778
779 std::unique_ptr<PIDController> tpid =
780 std::make_unique<ControllerMock>("fan1", zone.get());
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700781 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
Patrick Venturea58197c2018-06-11 15:29:45 -0700782
783 // Access the internal pid configuration to clear it out (unrelated to the
784 // test).
Patrick Venture563a3562018-10-30 09:31:26 -0700785 ec::pid_info_t* info = tpid->getPIDInfo();
Patrick Venturea58197c2018-06-11 15:29:45 -0700786 std::memset(info, 0x00, sizeof(ec::pid_info_t));
787
788 zone->addFanPID(std::move(tpid));
789
Patrick Venture563a3562018-10-30 09:31:26 -0700790 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
791 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
792 EXPECT_CALL(*tmock, outputProc(_));
Patrick Venturea58197c2018-06-11 15:29:45 -0700793
794 // Method under test will, for each fan PID, call setpt, input, and output.
Patrick Venture563a3562018-10-30 09:31:26 -0700795 zone->processFans();
Patrick Venturea58197c2018-06-11 15:29:45 -0700796}
797
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700798TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
799{
Patrick Venturea58197c2018-06-11 15:29:45 -0700800 // The manual(bool) method is inherited from the dbus mode interface.
801
802 // Verifies that someone doesn't remove the internal call to the dbus
803 // object from which we're inheriting.
804 EXPECT_CALL(sdbus_mock_mode,
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700805 sd_bus_emit_properties_changed_strv(
806 IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
Harvey.Wua1ae4fa2022-10-28 17:38:35 +0800807 .WillOnce(Invoke(
808 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
809 [[maybe_unused]] const char* interface, const char** names) {
Patrick Williams8c051122023-05-10 07:50:59 -0500810 EXPECT_STREQ("Manual", names[0]);
811 return 0;
Patrick Williamse1dbb592023-10-20 11:19:22 -0500812 }));
Patrick Venturea58197c2018-06-11 15:29:45 -0700813
814 // Method under test will set the manual mode to true and broadcast this
815 // change on dbus.
816 zone->manual(true);
817 EXPECT_TRUE(zone->getManualMode());
818}
819
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700820TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
821{
Patrick Venturea58197c2018-06-11 15:29:45 -0700822 // This property is implemented by us as read-only, such that trying to
823 // write to it will have no effect.
824 EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
825}
Patrick Venturea0764872020-08-08 07:48:43 -0700826
827} // namespace
828} // namespace pid_control