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 "count_state_floor.hpp"
17 
18 #include "../manager.hpp"
19 #include "../zone.hpp"
20 #include "action.hpp"
21 #include "group.hpp"
22 #include "sdeventplus.hpp"
23 
24 namespace phosphor::fan::control::json
25 {
26 
CountStateFloor(const json & jsonObj,const std::vector<Group> & groups)27 CountStateFloor::CountStateFloor(const json& jsonObj,
28                                  const std::vector<Group>& groups) :
29     ActionBase(jsonObj, groups)
30 {
31     setCount(jsonObj);
32     setState(jsonObj);
33     setFloor(jsonObj);
34     setDelayTime(jsonObj);
35 }
36 
run(Zone & zone)37 void CountStateFloor::run(Zone& zone)
38 {
39     auto countReached = doCount();
40 
41     if (_delayTime == std::chrono::seconds::zero())
42     {
43         // If no delay time configured, can immediately update the hold.
44         zone.setFloorHold(getUniqueName(), _floor, countReached);
45         return;
46     }
47 
48     if (!countReached)
49     {
50         if (_timer && _timer->isEnabled())
51         {
52             record("Stopping delay timer");
53             _timer->setEnabled(false);
54         }
55 
56         zone.setFloorHold(getUniqueName(), _floor, countReached);
57         return;
58     }
59 
60     // The count has been reached and a delay is configured, so either:
61     // 1. This hold has already been set, so don't need to do anything else.
62     // 2. The timer hasn't been started yet, so start it (May need to create
63     //    it first).
64     // 3. The timer is already running, don't need to do anything else.
65     // When the timer expires, then count again and set the hold.
66 
67     if (zone.hasFloorHold(getUniqueName()))
68     {
69         return;
70     }
71 
72     if (!_timer)
73     {
74         _timer = std::make_unique<Timer>(util::SDEventPlus::getEvent(),
75                                          [&zone, this](Timer&) {
76             zone.setFloorHold(getUniqueName(), _floor, doCount());
77         });
78     }
79 
80     if (!_timer->isEnabled())
81     {
82         record("Starting delay timer");
83         _timer->restartOnce(_delayTime);
84     }
85 }
86 
doCount()87 bool CountStateFloor::doCount()
88 {
89     size_t numAtState = 0;
90 
91     for (const auto& group : _groups)
92     {
93         for (const auto& member : group.getMembers())
94         {
95             try
96             {
97                 if (Manager::getObjValueVariant(member, group.getInterface(),
98                                                 group.getProperty()) == _state)
99                 {
100                     numAtState++;
101                     if (numAtState >= _count)
102                     {
103                         return true;
104                     }
105                 }
106             }
107             catch (const std::out_of_range& oore)
108             {
109                 // Default to property not equal when not found
110             }
111         }
112     }
113 
114     return false;
115 }
116 
setCount(const json & jsonObj)117 void CountStateFloor::setCount(const json& jsonObj)
118 {
119     if (!jsonObj.contains("count"))
120     {
121         throw ActionParseError{ActionBase::getName(),
122                                "Missing required count value"};
123     }
124     _count = jsonObj["count"].get<size_t>();
125 }
126 
setState(const json & jsonObj)127 void CountStateFloor::setState(const json& jsonObj)
128 {
129     if (!jsonObj.contains("state"))
130     {
131         throw ActionParseError{ActionBase::getName(),
132                                "Missing required state value"};
133     }
134     _state = getJsonValue(jsonObj["state"]);
135 }
136 
setFloor(const json & jsonObj)137 void CountStateFloor::setFloor(const json& jsonObj)
138 {
139     if (!jsonObj.contains("floor"))
140     {
141         throw ActionParseError{ActionBase::getName(),
142                                "Missing required floor value"};
143     }
144     _floor = jsonObj["floor"].get<uint64_t>();
145 }
146 
setDelayTime(const json & jsonObj)147 void CountStateFloor::setDelayTime(const json& jsonObj)
148 {
149     if (jsonObj.contains("delay"))
150     {
151         _delayTime = std::chrono::seconds(jsonObj["delay"].get<size_t>());
152     }
153 }
154 
155 } // namespace phosphor::fan::control::json
156