roo_control
API Documentation for roo_control
Loading...
Searching...
No Matches
inert_switch.h
Go to the documentation of this file.
1#pragma once
2
4#include "roo_scheduler.h"
5#include "roo_time.h"
6
7namespace roo_control {
8
9/// Default backoff policy for InertSwitch retries.
10roo_time::Duration DefaultBackoff(int retry_count);
11
12/// Switch that adds inertia between state changes on top of a raw actuator.
13///
14/// Useful to counter-act bouncing that could damage physical relays.
15///
16/// This switch provides some degree of fault tolerance: as long as the actuator
17/// reports false from setState(), the inert switch will keep retrying calling
18/// it. (You can specify a retry policy for doing so; the default policy is a
19/// randomized exponential backoff). However, when the actuator reports true
20/// from setState(), the inert switch trusts it to enforce the setting.
21///
22/// If you need stronger fault tolerance, e.g. when the switch is remotely
23/// controlled, consider using a FaultTolerantSwitch.
24template <typename StateT>
25class InertSwitch : public Switch<StateT> {
26 public:
27 using State = StateT;
28
29 InertSwitch(roo_scheduler::Scheduler& scheduler, Switch<StateT>& actuator,
30 roo_time::Duration inertia = roo_time::Millis(500))
31 : scheduler_(scheduler),
32 actuator_(actuator),
33 inertia_(inertia),
34 backoff_policy_(DefaultBackoff),
35 deferred_setter_([this]() { deferredSet(); }),
36 deferred_set_pending_(false),
37 initialized_(false),
38 when_switched_(roo_time::Uptime::Now()),
39 failure_count_(0) {}
40
41 virtual ~InertSwitch() = default;
42
43 /// Returns the actual state the switch is currently at.
44 bool getState(State& result) const override {
45 return actuator_.getState(result);
46 }
47
48 /// Returns the state that the switch has been requested to take.
49 ///
50 /// The actual state may lag behind due to inertia. Returns false if the
51 /// intended state has never been set yet.
52 bool getIntendedState(State& state) const {
53 if (!initialized_) return false;
54 state = intended_state_;
55 return true;
56 }
57
58 /// Sets the intended state of the switch.
59 ///
60 /// If the intended state of the switch was already set to the same value,
61 /// returns immediately. Otherwise, if the last state change was more ago
62 /// than the `inertia` interval, the state change is immediately attempted by
63 /// calling setState() on the actuator. Otherwise, the state change attempt is
64 /// scheduled to occur after the `inertia` interval since the last change.
65 ///
66 /// In case that setting the state fails, a retry is scheduled, according to
67 /// the retry policy specified at creation time (by default, a randomized
68 /// exponential backoff, truncated at 5s). The retries continue until
69 /// `actuator.setState()` returns true.
70 bool setState(State state) override {
71 if (initialized_ && intended_state_ == state) {
72 return true;
73 }
74 intended_state_ = state;
75 initialized_ = true;
76 // Note: if the requested state is different than the current
77 // intended_state_, the actuator may be in the process of delivering the
78 // previous state change, and the observed state might not yet reflect it.
79 // Therefore, we never rely on the observed state and always call the
80 // actuator.
81 roo_time::Uptime now = roo_time::Uptime::Now();
82 roo_time::Uptime deferred_set_time = when_switched_ + inertia_;
83 if (deferred_set_time <= now) {
84 // The minimums have been met; set immediately.
85 set();
86 } else {
87 // Unless already scheduled to update, schedule to update with delay.
88 if (!deferred_set_pending_) {
89 deferred_set_pending_ = true;
90 scheduler_.scheduleOn(deferred_set_time, deferred_setter_,
91 roo_scheduler::PRIORITY_ELEVATED);
92 }
93 }
95 return true;
96 }
97
98 /// Returns true if a deferred update is pending.
99 bool hasPendingChange() const { return deferred_set_pending_; }
100
101 /// Returns the time of last actual state change.
102 roo_time::Uptime whenSwitched() const { return when_switched_; }
103
104 /// Returns the inertia interval.
105 roo_time::Duration intertia() const { return inertia_; }
106
107 protected:
108 /// Can be overridden to receive state change notifications.
109 ///
110 /// Triggers when either the intended state changes, or setState() on the
111 /// actuator succeeds (confirming update request of the actual state), or
112 /// both.
113 virtual void stateChanged() const {}
114
115 private:
116 bool set() {
117 if (actuator_.setState(intended_state_)) {
118 when_switched_ = roo_time::Uptime::Now();
119 failure_count_ = 0;
120 return true;
121 }
122 ++failure_count_;
123 deferred_set_pending_ = true;
124 roo_time::Duration delay = backoff_policy_(failure_count_);
125 scheduler_.scheduleAfter(delay, deferred_setter_,
126 roo_scheduler::PRIORITY_ELEVATED);
127 return false;
128 }
129
130 // Called by the scheduler to execute the deferred state change.
131 void deferredSet() {
132 deferred_set_pending_ = false;
133 if (set()) {
134 stateChanged();
135 }
136 }
137
138 /// Used to schedule deferred state updates.
139 roo_scheduler::Scheduler& scheduler_;
140
141 /// The underlying switch that we're driving.
142 Switch<State>& actuator_;
143
144 /// The minimum interval between state changes to impose on the actuator.
145 roo_time::Duration inertia_;
146
147 /// The retry policy to handle set failures on the actuator.
148 std::function<roo_time::Duration(int retry_count)> backoff_policy_;
149
150 /// Helper to execute the scheduled deferred set.
151 roo_scheduler::Task deferred_setter_;
152
153 /// Whether a deferred set is scheduled.
154 bool deferred_set_pending_;
155
156 /// True when the intended state has been set at least once.
157 bool initialized_;
158
159 /// The intended state of the switch.
160 State intended_state_;
161
162 /// The time when the actual state has most recently changed.
163 roo_time::Uptime when_switched_;
164
165 /// Failures since the last successful state reconciliation.
166 int failure_count_;
167};
168
169/// Convenience alias for a binary inert switch.
171
172} // namespace roo_control
Switch that adds inertia between state changes on top of a raw actuator.
bool getIntendedState(State &state) const
Returns the state that the switch has been requested to take.
virtual void stateChanged() const
Can be overridden to receive state change notifications.
roo_time::Uptime whenSwitched() const
Returns the time of last actual state change.
InertSwitch(roo_scheduler::Scheduler &scheduler, Switch< StateT > &actuator, roo_time::Duration inertia=roo_time::Millis(500))
virtual ~InertSwitch()=default
bool hasPendingChange() const
Returns true if a deferred update is pending.
bool getState(State &result) const override
Returns the actual state the switch is currently at.
bool setState(State state) override
Sets the intended state of the switch.
roo_time::Duration intertia() const
Returns the inertia interval.
An abstraction of a multi-state settable switch.
Definition switch.h:24
roo_time::Duration DefaultBackoff(int retry_count)
Default backoff policy for InertSwitch retries.