Refactored code, unit tests added, GA activated.
This commit is contained in:
parent
1c870374cd
commit
a653f0e3d6
31
.clang-format
Normal file
31
.clang-format
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
AlignConsecutiveMacros: AcrossEmptyLines
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: Never
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterUnion: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
ColumnLimit: 0
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
IndentWidth: 4
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
PointerAlignment: Middle
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
NamespaceIndentation: All
|
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = false
|
||||||
|
tab_width = 4
|
34
.gitea/workflows/pio_tests.yml
Normal file
34
.gitea/workflows/pio_tests.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: PlatformIO flow
|
||||||
|
run-name: ${{ gitea.actor }} is testing
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Configure caching
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pip
|
||||||
|
~/.platformio/.cache
|
||||||
|
key: ${{ runner.os }}-pio
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 'pypy3.9'
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
- name: Run native unit tests
|
||||||
|
run: |
|
||||||
|
pio test
|
43
.gitignore
vendored
43
.gitignore
vendored
@ -1,32 +1,11 @@
|
|||||||
# Prerequisites
|
.pio
|
||||||
*.d
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
# Compiled Object files
|
.vscode/launch.json
|
||||||
*.slo
|
.vscode/ipch
|
||||||
*.lo
|
dist
|
||||||
*.o
|
# C Lion
|
||||||
*.obj
|
.idea
|
||||||
|
cmake-build-*
|
||||||
# Precompiled Headers
|
CMakeLists.txt
|
||||||
*.gch
|
CMakeListsPrivate.txt
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Interval - Arduino knihovna pro časování pomocí intervalů
|
# Interval - Arduino knihovna pro časování pomocí intervalů
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Koncepce programové konstrukce aplikace pro Arduino spočívá ve dvou hlavních metodách – **setup()** a **loop()**, ve které program neustále běží.
|
Koncepce programové konstrukce aplikace pro Arduino spočívá ve dvou hlavních metodách – **setup()** a **loop()**, ve které program neustále běží.
|
||||||
Pro pohodlnější práci s obsluhou periodických procesů jsem napsal jednoduchou knihovnu, která tyto úkoly umožňuje realizovat velmi elegantním způsobem.
|
Pro pohodlnější práci s obsluhou periodických procesů jsem napsal jednoduchou knihovnu, která tyto úkoly umožňuje realizovat velmi elegantním způsobem.
|
||||||
|
|
||||||
|
17
platformio.ini
Normal file
17
platformio.ini
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[platformio]
|
||||||
|
default_envs = native
|
||||||
|
|
||||||
|
[env:native]
|
||||||
|
; build for desktop, not embedded device
|
||||||
|
; expects GCC to be installed and available on the computer!
|
||||||
|
platform = native
|
||||||
|
; build source code in src/ too
|
||||||
|
test_build_src = yes
|
||||||
|
;test_framework = custom ; uncomment this if you need debugging messages to be shown (or else run with `pio test -v`)
|
||||||
|
debug_test = test_interval
|
||||||
|
lib_deps =
|
||||||
|
https://github.com/FabioBatSilva/ArduinoFake/archive/refs/heads/master.zip
|
||||||
|
build_flags =
|
||||||
|
-std=gnu++17
|
||||||
|
-DUNITY_OUTPUT_COLOR
|
||||||
|
build_type = debug
|
102
src/interval.cpp
102
src/interval.cpp
@ -1,74 +1,82 @@
|
|||||||
#include "interval.h"
|
#include "interval.h"
|
||||||
|
|
||||||
// Public Methods //////////////////////////////////////////////////////////////
|
// Returns the remaining time until expiration, correctly handling overflow
|
||||||
uint32_t Interval::remains() const
|
uint32_t Interval::remains() const {
|
||||||
{
|
uint32_t currentTime = millis();
|
||||||
|
return _timeout - (currentTime - _timefrom);
|
||||||
return _timeout - (millis() - _timefrom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Interval::elapsed() const
|
// Returns the elapsed time since the interval started
|
||||||
{
|
uint32_t Interval::elapsed() const {
|
||||||
|
|
||||||
return millis() - _timefrom;
|
return millis() - _timefrom;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Interval::expired()
|
// Checks if the interval has expired based on the mode
|
||||||
{
|
bool Interval::expired() {
|
||||||
bool result = false;
|
uint32_t currentTime = millis();
|
||||||
|
uint32_t elapsedTime = currentTime - _timefrom;
|
||||||
|
|
||||||
if (_done) {
|
switch (_mode) {
|
||||||
if (1 == _mode) {
|
case ONESHOT:
|
||||||
// oneshot mode
|
// In oneshot mode, return true only once upon expiration
|
||||||
if ((millis() - _timefrom) >= _timeout) {
|
if (elapsedTime >= _timeout) {
|
||||||
_done = 0;
|
if (_active) {
|
||||||
result = true;
|
_active = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (2 == _mode) {
|
break;
|
||||||
// periodic mode
|
case PERIODIC:
|
||||||
if ((millis() - _timefrom) >= _timeout) {
|
// In periodic mode, return true and reset the timer on expiration
|
||||||
result = true;
|
if (elapsedTime >= _timeout) {
|
||||||
_timefrom = millis();
|
_timefrom += _timeout;
|
||||||
|
if (currentTime - _timefrom >= _timeout) {
|
||||||
|
_timefrom = currentTime;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
// compatibility mode
|
case COMPATIBILITY:
|
||||||
if ((millis() - _timefrom) >= _timeout)
|
default:
|
||||||
result = true;
|
// In compatibility mode, always return true if the interval has expired
|
||||||
}
|
if (_active) {
|
||||||
|
if (elapsedTime >= _timeout) {
|
||||||
|
_active = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true; // already expired
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interval::set(uint32_t tmout)
|
// Sets the interval in compatibility mode
|
||||||
{
|
void Interval::set(const uint32_t tmout) {
|
||||||
|
|
||||||
_timefrom = millis();
|
_timefrom = millis();
|
||||||
_timeout = tmout;
|
_timeout = tmout;
|
||||||
_mode = 0;
|
_mode = COMPATIBILITY;
|
||||||
_done = 0xff;
|
_active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interval::setOneshot(uint32_t tmout)
|
// Sets the interval in oneshot mode
|
||||||
{
|
void Interval::setOneshot(const uint32_t tmout) {
|
||||||
|
|
||||||
_timefrom = millis();
|
_timefrom = millis();
|
||||||
_timeout = tmout;
|
_timeout = tmout;
|
||||||
_mode = 1;
|
_mode = ONESHOT;
|
||||||
_done = 0xff;
|
_active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interval::setPeriodic(uint32_t tmout)
|
// Sets the interval in periodic mode
|
||||||
{
|
void Interval::setPeriodic(const uint32_t tmout) {
|
||||||
|
|
||||||
_timefrom = millis();
|
_timefrom = millis();
|
||||||
_timeout = tmout;
|
_timeout = tmout;
|
||||||
_mode = 2;
|
_mode = PERIODIC;
|
||||||
_done = 0xff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interval::reload()
|
// Reloads the interval timer (resets the starting time)
|
||||||
{
|
void Interval::reload() {
|
||||||
|
|
||||||
_timefrom = millis();
|
_timefrom = millis();
|
||||||
_done = 0xff;
|
_active = true;
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,52 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
/*
|
||||||
/* Interval
|
* Interval
|
||||||
* Copyright (C) 2014, 2016, 2018, 2019, 2023 Pavel Brychta http://www.xpablo.cz
|
* Copyright (C) 2014, 2016, 2018, 2019, 2023, 2025 Pavel Brychta http://www.xpablo.cz
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the MIT License
|
* This program is free software: you can redistribute it and/or modify it under the terms of the MIT License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
class Interval
|
class Interval {
|
||||||
{
|
public:
|
||||||
protected:
|
// Enumeration for interval modes
|
||||||
uint32_t _timefrom = 0;
|
enum IntervalMode {
|
||||||
uint32_t _timeout = 0;
|
COMPATIBILITY = 0,
|
||||||
uint8_t _mode = 0; // mode of actual operation (compatibility, oneshot, periodic)
|
ONESHOT = 1,
|
||||||
uint8_t _done = 0xff; // compatibility mode autostart
|
PERIODIC = 2
|
||||||
public:
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t _timefrom = 0; // Start time of the interval
|
||||||
|
uint32_t _timeout = 0; // Timeout duration in milliseconds
|
||||||
|
IntervalMode _mode = COMPATIBILITY; // Current mode of operation
|
||||||
|
bool _active = true; // Active flag for oneshot and compatibilitymode
|
||||||
|
public:
|
||||||
Interval() = default;
|
Interval() = default;
|
||||||
explicit Interval(uint32_t tmout)
|
|
||||||
: _timeout(tmout)
|
// Constructor for periodic mode with autostart
|
||||||
, _mode(2) // periodic mode autostart
|
explicit Interval(const uint32_t tmout)
|
||||||
{}
|
: _timefrom(millis()), _timeout(tmout), _mode(PERIODIC) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the interval has expired based on the current mode
|
||||||
bool expired();
|
bool expired();
|
||||||
|
|
||||||
|
// Sets the interval in compatibility mode
|
||||||
void set(uint32_t tmout);
|
void set(uint32_t tmout);
|
||||||
|
|
||||||
|
// Sets the interval in oneshot mode
|
||||||
void setOneshot(uint32_t tmout);
|
void setOneshot(uint32_t tmout);
|
||||||
|
|
||||||
|
// Sets the interval in periodic mode
|
||||||
void setPeriodic(uint32_t tmout);
|
void setPeriodic(uint32_t tmout);
|
||||||
|
|
||||||
|
// Reloads the interval timer without changing mode or timeout
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
|
// Returns the elapsed time since the interval started
|
||||||
[[nodiscard]] uint32_t elapsed() const;
|
[[nodiscard]] uint32_t elapsed() const;
|
||||||
|
|
||||||
|
// Returns the remaining time until the interval expires
|
||||||
[[nodiscard]] uint32_t remains() const;
|
[[nodiscard]] uint32_t remains() const;
|
||||||
};
|
};
|
||||||
|
148
test/test_interval/main.cpp
Normal file
148
test/test_interval/main.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#include <ArduinoFake.h>
|
||||||
|
#include <unity.h>
|
||||||
|
#include "interval.h"
|
||||||
|
|
||||||
|
using namespace fakeit;
|
||||||
|
|
||||||
|
// Global variable to simulate time in milliseconds
|
||||||
|
static uint32_t fakeMillis = 0;
|
||||||
|
|
||||||
|
// Fake function to override millis()
|
||||||
|
uint32_t fakeMillisFunc() {
|
||||||
|
return fakeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setUp() is called before each test
|
||||||
|
void setUp(void) {
|
||||||
|
// Reset fake time
|
||||||
|
fakeMillis = 0;
|
||||||
|
ArduinoFakeReset();
|
||||||
|
// Override millis() to use our fakeMillisFunc()
|
||||||
|
When(Method(ArduinoFake(), millis)).AlwaysDo(fakeMillisFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tearDown() is called after each test (if needed)
|
||||||
|
void tearDown(void) {
|
||||||
|
// No teardown actions required
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for Compatibility Mode (using set() method)
|
||||||
|
void test_CompatibilityMode(void) {
|
||||||
|
Interval interval;
|
||||||
|
interval.set(1000); // Set timeout to 1000ms in compatibility mode
|
||||||
|
|
||||||
|
fakeMillis = 500;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired()); // Should not be expired at 500ms
|
||||||
|
|
||||||
|
fakeMillis = 1000;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // Should be expired at 1000ms
|
||||||
|
|
||||||
|
// In compatibility mode, subsequent calls should still return true once expired
|
||||||
|
TEST_ASSERT_TRUE(interval.expired());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for One-shot Mode (using setOneshot() method)
|
||||||
|
void test_OneshotMode(void) {
|
||||||
|
Interval interval;
|
||||||
|
interval.setOneshot(1000); // Set timeout to 1000ms in oneshot mode
|
||||||
|
|
||||||
|
fakeMillis = 500;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||||
|
|
||||||
|
fakeMillis = 1000;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // Expired at 1000ms
|
||||||
|
|
||||||
|
// Subsequent calls should return false because oneshot mode triggers only once
|
||||||
|
TEST_ASSERT_FALSE(interval.expired());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for Periodic Mode (using setPeriodic() method)
|
||||||
|
void test_PeriodicMode(void) {
|
||||||
|
Interval interval;
|
||||||
|
interval.setPeriodic(1000); // Set periodic mode with 1000ms timeout
|
||||||
|
|
||||||
|
fakeMillis = 500;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||||
|
|
||||||
|
fakeMillis = 1000;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // First period expired
|
||||||
|
|
||||||
|
// After expiration, timer is reset; simulate next period
|
||||||
|
fakeMillis = 1500;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired()); // Still within new period
|
||||||
|
|
||||||
|
fakeMillis = 2000;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // Second period expired
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for elapsed() and remains() functions
|
||||||
|
void test_elapsed_and_remains(void) {
|
||||||
|
Interval interval;
|
||||||
|
interval.set(1000); // Using compatibility mode, timeout set to 1000ms
|
||||||
|
|
||||||
|
fakeMillis = 0;
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(0, interval.elapsed());
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1000, interval.remains());
|
||||||
|
|
||||||
|
fakeMillis = 500;
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(500, interval.elapsed());
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(500, interval.remains());
|
||||||
|
|
||||||
|
fakeMillis = 1100;
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1100, interval.elapsed());
|
||||||
|
// remains() uses unsigned arithmetic, so it will underflow if elapsed > timeout
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1000 - 1100, interval.remains());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for handling overflow of millis() value
|
||||||
|
void test_overflow(void) {
|
||||||
|
Interval interval;
|
||||||
|
// Set fakeMillis to a value near the maximum of 32-bit unsigned int
|
||||||
|
fakeMillis = 0xFFFFFFF0;
|
||||||
|
interval.set(100); // Set timeout to 100ms in compatibility mode; _timefrom = 0xFFFFFFF0
|
||||||
|
|
||||||
|
// Simulate time just before expiration after overflow.
|
||||||
|
// After overflow, fakeMillis is low. For example, fakeMillis = 0x00000050.
|
||||||
|
// Expected elapsed time = 0x00000050 + (0x100000000 - 0xFFFFFFF0) = 0x50 + 0x10 = 96ms.
|
||||||
|
fakeMillis = 0x00000050;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired()); // Not yet expired
|
||||||
|
|
||||||
|
// Now simulate time after expiration:
|
||||||
|
// Set fakeMillis = 0x00000070. Expected elapsed time = 0x70 + 0x10 = 128ms, which is >= 100ms.
|
||||||
|
fakeMillis = 0x00000070;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // Should be expired due to overflow handling
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for reload() functionality in oneshot mode
|
||||||
|
void test_reload(void) {
|
||||||
|
Interval interval;
|
||||||
|
interval.setOneshot(1000); // Set oneshot mode with 1000ms timeout
|
||||||
|
|
||||||
|
fakeMillis = 0;
|
||||||
|
// Simulate expiration
|
||||||
|
fakeMillis = 1100;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired()); // Expired at 1100ms
|
||||||
|
|
||||||
|
// Reload the interval to reset the timer and reactivate oneshot mode
|
||||||
|
fakeMillis = 1100;
|
||||||
|
interval.reload();
|
||||||
|
|
||||||
|
// Immediately after reload, should not be expired
|
||||||
|
fakeMillis = 1200;
|
||||||
|
TEST_ASSERT_FALSE(interval.expired());
|
||||||
|
|
||||||
|
// After passing the timeout from reload, it should expire again
|
||||||
|
fakeMillis = 2200;
|
||||||
|
TEST_ASSERT_TRUE(interval.expired());
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_CompatibilityMode);
|
||||||
|
RUN_TEST(test_OneshotMode);
|
||||||
|
RUN_TEST(test_PeriodicMode);
|
||||||
|
RUN_TEST(test_elapsed_and_remains);
|
||||||
|
RUN_TEST(test_overflow);
|
||||||
|
RUN_TEST(test_reload);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user