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