Skip to content

Commit a501d3c

Browse files
authored
PolledTimeout Class for wrapping millis() loops (WIP) (#5198)
* PolledTimeout Class for wrapping millis() loops * Add yield policies, improve reset, add host tests * Fix copyright, comments * adjust host tests for better time precision * add fuzzyness to timing tests for CI jitter * add blink example with polledTimeout * improve namespace and type naming, add copyright, comments * fix astyle
1 parent cd05bae commit a501d3c

File tree

4 files changed

+381
-1
lines changed

4 files changed

+381
-1
lines changed

Diff for: cores/esp8266/PolledTimeout.h

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#ifndef __POLLEDTIMING_H__
2+
#define __POLLEDTIMING_H__
3+
4+
5+
/*
6+
PolledTimeout.h - Encapsulation of a polled Timeout
7+
8+
Copyright (c) 2018 Daniel Salazar. All rights reserved.
9+
This file is part of the esp8266 core for Arduino environment.
10+
11+
This library is free software; you can redistribute it and/or
12+
modify it under the terms of the GNU Lesser General Public
13+
License as published by the Free Software Foundation; either
14+
version 2.1 of the License, or (at your option) any later version.
15+
16+
This library is distributed in the hope that it will be useful,
17+
but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
Lesser General Public License for more details.
20+
21+
You should have received a copy of the GNU Lesser General Public
22+
License along with this library; if not, write to the Free Software
23+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24+
*/
25+
26+
27+
28+
namespace esp8266
29+
{
30+
31+
32+
namespace polledTimeout
33+
{
34+
35+
namespace YieldPolicy
36+
{
37+
38+
struct DoNothing
39+
{
40+
static void execute() {}
41+
};
42+
43+
struct YieldOrSkip
44+
{
45+
static void execute() {delay(0);}
46+
};
47+
48+
} //YieldPolicy
49+
50+
51+
template <bool PeriodicT, typename YieldPolicyT = YieldPolicy::DoNothing>
52+
class timeoutTemplate
53+
{
54+
public:
55+
using timeType = decltype(millis());
56+
57+
timeoutTemplate(timeType timeout)
58+
: _timeout(timeout), _start(millis())
59+
{}
60+
61+
bool expired()
62+
{
63+
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
64+
if(PeriodicT) //in case of false: gets optimized away
65+
return expiredRetrigger();
66+
return expiredOneShot();
67+
}
68+
69+
operator bool()
70+
{
71+
return expired();
72+
}
73+
74+
void reset(timeType newTimeout)
75+
{
76+
_timeout = newTimeout;
77+
reset();
78+
}
79+
80+
void reset()
81+
{
82+
_start = millis();
83+
}
84+
85+
protected:
86+
bool checkExpired(timeType t) const
87+
{
88+
return (t - _start) >= _timeout;
89+
}
90+
91+
bool expiredRetrigger()
92+
{
93+
timeType current = millis();
94+
if(checkExpired(current))
95+
{
96+
unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout)
97+
_start += n * _timeout;
98+
return true;
99+
}
100+
return false;
101+
}
102+
103+
bool expiredOneShot() const
104+
{
105+
return checkExpired(millis());
106+
}
107+
108+
timeType _timeout;
109+
timeType _start;
110+
};
111+
112+
using oneShot = polledTimeout::timeoutTemplate<false>;
113+
using periodic = polledTimeout::timeoutTemplate<true>;
114+
115+
} //polledTimeout
116+
117+
118+
/* A 1-shot timeout that auto-yields when in CONT can be built as follows:
119+
* using oneShotYield = esp8266::polledTimeout::timeoutTemplate<false, esp8266::polledTimeout::YieldPolicy::YieldOrSkip>;
120+
*
121+
* Other policies can be implemented by the user, e.g.: simple yield that panics in SYS, and the polledTimeout types built as needed as shown above, without modifying this file.
122+
*/
123+
124+
}//esp8266
125+
126+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
ESP8266 Blink with polledTimeout by Daniel Salazar
3+
4+
Copyright (c) 2018 Daniel Salazar. All rights reserved.
5+
This file is part of the esp8266 core for Arduino environment.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20+
21+
22+
Note that this sketch uses LED_BUILTIN to find the pin with the internal LED
23+
*/
24+
25+
26+
#include <PolledTimeout.h>
27+
28+
void ledOn() {
29+
digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level
30+
}
31+
32+
void ledOff() {
33+
digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH
34+
}
35+
36+
void ledToggle() {
37+
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Change the state of the LED
38+
}
39+
40+
41+
42+
esp8266::polledTimeout::periodic halfPeriod(500); //use fully qualified type and avoid importing all ::esp8266 namespace to the global namespace
43+
44+
// the setup function runs only once at start
45+
void setup() {
46+
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
47+
48+
using esp8266::polledTimeout::oneShot; //import the type to the local namespace
49+
50+
//STEP1; turn the led ON
51+
ledOn();
52+
53+
//STEP2: wait for ON timeout
54+
oneShot timeoutOn(2000);
55+
while (!timeoutOn) {
56+
yield();
57+
}
58+
59+
//STEP3: turn the led OFF
60+
ledOff();
61+
62+
//STEP4: wait for OFF timeout to assure the led is kept off for this time before exiting setup
63+
oneShot timeoutOff(2000);
64+
while (!timeoutOff) {
65+
yield();
66+
}
67+
68+
//Done with STEPs, do other stuff
69+
halfPeriod.reset(); //halfPeriod is global, so it gets inited on sketch start. Clear it here to make it ready for loop, where it's actually used.
70+
}
71+
72+
73+
// the loop function runs over and over again forever
74+
void loop() {
75+
if (halfPeriod) {
76+
ledToggle();
77+
}
78+
}

Diff for: tests/host/Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ TEST_CPP_FILES := \
107107
fs/test_fs.cpp \
108108
core/test_pgmspace.cpp \
109109
core/test_md5builder.cpp \
110-
core/test_string.cpp
110+
core/test_string.cpp \
111+
core/test_PolledTimeout.cpp
111112

112113
PREINCLUDES := \
113114
-include common/mock.h \

Diff for: tests/host/core/test_PolledTimeout.cpp

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#include <catch.hpp>
2+
#include "PolledTimeout.h"
3+
4+
//This won't work for
5+
template<typename argT>
6+
inline bool
7+
fuzzycomp(argT a, argT b)
8+
{
9+
const argT epsilon = 10;
10+
return (std::max(a,b) - std::min(a,b) <= epsilon);
11+
}
12+
13+
TEST_CASE("OneShot Timeout 3000ms", "[polledTimeout]")
14+
{
15+
using esp8266::polledTimeout::oneShot;
16+
using timeType = oneShot::timeType;
17+
timeType before, after, delta;
18+
19+
Serial.println("OneShot Timeout 3000ms");
20+
21+
oneShot timeout(3000);
22+
before = millis();
23+
while(!timeout.expired())
24+
yield();
25+
after = millis();
26+
27+
delta = after - before;
28+
Serial.printf("delta = %lu\n", delta);
29+
30+
REQUIRE(fuzzycomp(delta, (timeType)3000));
31+
32+
33+
Serial.print("reset\n");
34+
35+
timeout.reset();
36+
before = millis();
37+
while(!timeout)
38+
yield();
39+
after = millis();
40+
41+
delta = after - before;
42+
Serial.printf("delta = %lu\n", delta);
43+
44+
REQUIRE(fuzzycomp(delta, (timeType)3000));
45+
}
46+
47+
TEST_CASE("OneShot Timeout 3000ms reset to 1000ms", "[polledTimeout]")
48+
{
49+
using esp8266::polledTimeout::oneShot;
50+
using timeType = oneShot::timeType;
51+
timeType before, after, delta;
52+
53+
Serial.println("OneShot Timeout 3000ms");
54+
55+
oneShot timeout(3000);
56+
before = millis();
57+
while(!timeout.expired())
58+
yield();
59+
after = millis();
60+
61+
delta = after - before;
62+
Serial.printf("delta = %lu\n", delta);
63+
64+
REQUIRE(fuzzycomp(delta, (timeType)3000));
65+
66+
67+
Serial.print("reset\n");
68+
69+
timeout.reset(1000);
70+
before = millis();
71+
while(!timeout)
72+
yield();
73+
after = millis();
74+
75+
delta = after - before;
76+
Serial.printf("delta = %lu\n", delta);
77+
78+
REQUIRE(fuzzycomp(delta, (timeType)1000));
79+
}
80+
81+
TEST_CASE("Periodic Timeout 1T 3000ms", "[polledTimeout]")
82+
{
83+
using esp8266::polledTimeout::periodic;
84+
using timeType = periodic::timeType;
85+
timeType before, after, delta;
86+
87+
Serial.println("Periodic Timeout 1T 3000ms");
88+
89+
periodic timeout(3000);
90+
before = millis();
91+
while(!timeout)
92+
yield();
93+
after = millis();
94+
95+
delta = after - before;
96+
Serial.printf("delta = %lu\n", delta);
97+
98+
REQUIRE(fuzzycomp(delta, (timeType)3000));
99+
100+
Serial.print("no reset needed\n");
101+
102+
before = millis();
103+
while(!timeout)
104+
yield();
105+
after = millis();
106+
107+
delta = after - before;
108+
Serial.printf("delta = %lu\n", delta);
109+
110+
REQUIRE(fuzzycomp(delta, (timeType)3000));
111+
}
112+
113+
TEST_CASE("Periodic Timeout 10T 1000ms", "[polledTimeout]")
114+
{
115+
using esp8266::polledTimeout::periodic;
116+
using timeType = periodic::timeType;
117+
timeType before, after, delta;
118+
119+
Serial.println("Periodic 10T Timeout 1000ms");
120+
121+
int counter = 10;
122+
123+
periodic timeout(1000);
124+
before = millis();
125+
while(1)
126+
{
127+
if(timeout)
128+
{
129+
Serial.print("*");
130+
if(!--counter)
131+
break;
132+
yield();
133+
}
134+
}
135+
after = millis();
136+
137+
delta = after - before;
138+
Serial.printf("\ndelta = %lu\n", delta);
139+
REQUIRE(fuzzycomp(delta, (timeType)10000));
140+
}
141+
142+
TEST_CASE("OneShot Timeout 3000ms reset to 1000ms custom yield", "[polledTimeout]")
143+
{
144+
using YieldOrSkipPolicy = esp8266::polledTimeout::YieldPolicy::YieldOrSkip;
145+
using oneShotYield = esp8266::polledTimeout::timeoutTemplate<false, YieldOrSkipPolicy>;
146+
using timeType = oneShotYield::timeType;
147+
timeType before, after, delta;
148+
149+
Serial.println("OneShot Timeout 3000ms");
150+
151+
152+
oneShotYield timeout(3000);
153+
before = millis();
154+
while(!timeout.expired());
155+
after = millis();
156+
157+
delta = after - before;
158+
Serial.printf("delta = %lu\n", delta);
159+
160+
REQUIRE(fuzzycomp(delta, (timeType)3000));
161+
162+
163+
Serial.print("reset\n");
164+
165+
timeout.reset(1000);
166+
before = millis();
167+
while(!timeout);
168+
after = millis();
169+
170+
delta = after - before;
171+
Serial.printf("delta = %lu\n", delta);
172+
173+
REQUIRE(fuzzycomp(delta, (timeType)1000));
174+
}
175+

0 commit comments

Comments
 (0)