1 /** 2 * Copyright © 2021 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "timer_based_actions.hpp" 17 18 #include "../manager.hpp" 19 #include "action.hpp" 20 #include "event.hpp" 21 #include "group.hpp" 22 #include "sdbusplus.hpp" 23 #include "sdeventplus.hpp" 24 #include "zone.hpp" 25 26 #include <nlohmann/json.hpp> 27 28 #include <algorithm> 29 #include <chrono> 30 #include <format> 31 32 namespace phosphor::fan::control::json 33 { 34 35 using json = nlohmann::json; 36 37 TimerBasedActions::TimerBasedActions(const json& jsonObj, 38 const std::vector<Group>& groups) : 39 ActionBase(jsonObj, groups), 40 _timer(util::SDEventPlus::getEvent(), 41 std::bind(&TimerBasedActions::timerExpired, this)) 42 { 43 // If any of groups' value == nullopt(i.e. not configured), action is 44 // driven by the service owned state of the group members 45 _byOwner = std::any_of(_groups.begin(), _groups.end(), 46 [](const auto& group) { 47 return group.getValue() == std::nullopt; 48 }); 49 50 setTimerConf(jsonObj); 51 setActions(jsonObj); 52 } 53 54 void TimerBasedActions::run(Zone& zone) 55 { 56 if (_byOwner) 57 { 58 // If any service providing a group member is not owned, start 59 // timer and if all members' services are owned, stop timer. 60 if (std::any_of(_groups.begin(), _groups.end(), [](const auto& group) { 61 const auto& members = group.getMembers(); 62 return std::any_of(members.begin(), members.end(), 63 [&group](const auto& member) { 64 return !Manager::hasOwner(member, group.getInterface()); 65 }); 66 })) 67 { 68 startTimer(); 69 } 70 else 71 { 72 stopTimer(); 73 } 74 } 75 else 76 { 77 auto* mgr = zone.getManager(); 78 // If all group members have a given value and it matches what's 79 // in the cache, start timer and if any do not match, stop 80 // timer. 81 if (std::all_of(_groups.begin(), _groups.end(), 82 [&mgr](const auto& group) { 83 const auto& members = group.getMembers(); 84 return std::all_of(members.begin(), members.end(), 85 [&mgr, &group](const auto& member) { 86 return group.getValue() == 87 mgr->getProperty(member, group.getInterface(), 88 group.getProperty()); 89 }); 90 })) 91 { 92 // Timer will be started(and never stopped) when _groups is empty 93 startTimer(); 94 } 95 else 96 { 97 stopTimer(); 98 } 99 } 100 } 101 102 void TimerBasedActions::startTimer() 103 { 104 if (!_timer.isEnabled()) 105 { 106 if (_type == TimerType::repeating) 107 { 108 _timer.restart(_interval); 109 } 110 else if (_type == TimerType::oneshot) 111 { 112 _timer.restartOnce(_interval); 113 } 114 } 115 } 116 117 void TimerBasedActions::stopTimer() 118 { 119 if (_timer.isEnabled()) 120 { 121 _timer.setEnabled(false); 122 } 123 else 124 { 125 // Perform the actions in case state changed after the configured time 126 std::for_each(_actions.begin(), _actions.end(), 127 [](auto& action) { action->run(); }); 128 } 129 } 130 131 void TimerBasedActions::timerExpired() 132 { 133 // Perform the actions 134 std::for_each(_actions.begin(), _actions.end(), 135 [](auto& action) { action->run(); }); 136 } 137 138 void TimerBasedActions::setZones( 139 std::vector<std::reference_wrapper<Zone>>& zones) 140 { 141 for (auto& zone : zones) 142 { 143 this->addZone(zone); 144 // Add zone to _actions 145 std::for_each(_actions.begin(), _actions.end(), 146 [&zone](std::unique_ptr<ActionBase>& action) { 147 action->addZone(zone); 148 }); 149 } 150 } 151 152 void TimerBasedActions::setTimerConf(const json& jsonObj) 153 { 154 if (!jsonObj.contains("timer")) 155 { 156 throw ActionParseError{getName(), "Missing required timer entry"}; 157 } 158 auto jsonTimer = jsonObj["timer"]; 159 if (!jsonTimer.contains("interval") || !jsonTimer.contains("type")) 160 { 161 throw ActionParseError{ 162 getName(), "Missing required timer parameters {interval, type}"}; 163 } 164 165 // Interval provided in microseconds 166 _interval = static_cast<std::chrono::microseconds>( 167 jsonTimer["interval"].get<uint64_t>()); 168 169 // Retrieve type of timer 170 auto type = jsonTimer["type"].get<std::string>(); 171 if (type == "oneshot") 172 { 173 _type = TimerType::oneshot; 174 } 175 else if (type == "repeating") 176 { 177 _type = TimerType::repeating; 178 } 179 else 180 { 181 throw ActionParseError{ 182 getName(), std::format("Timer type '{}' is not supported", type)}; 183 } 184 } 185 186 void TimerBasedActions::setActions(const json& jsonObj) 187 { 188 if (!jsonObj.contains("actions")) 189 { 190 throw ActionParseError{getName(), "Missing required actions entry"}; 191 } 192 for (const auto& jsonAct : jsonObj["actions"]) 193 { 194 if (!jsonAct.contains("name")) 195 { 196 throw ActionParseError{getName(), "Missing required action name"}; 197 } 198 199 // Get any configured profile restrictions on the action 200 std::vector<std::string> profiles; 201 if (jsonAct.contains("profiles")) 202 { 203 for (const auto& profile : jsonAct["profiles"]) 204 { 205 profiles.emplace_back(profile.get<std::string>()); 206 } 207 } 208 209 // Set the groups configured for each action run when the timer expires 210 std::vector<Group> groups; 211 Event::setGroups(jsonAct, profiles, groups); 212 213 // List of zones is set on these actions by overriden setZones() 214 auto actObj = ActionFactory::getAction( 215 jsonAct["name"].get<std::string>(), jsonAct, std::move(groups), {}); 216 if (actObj) 217 { 218 _actions.emplace_back(std::move(actObj)); 219 } 220 } 221 } 222 223 } // namespace phosphor::fan::control::json 224