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
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
dist
|
||||
# C Lion
|
||||
.idea
|
||||
cmake-build-*
|
||||
CMakeLists.txt
|
||||
CMakeListsPrivate.txt
|
||||
|
@ -1,5 +1,7 @@
|
||||
# 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ěží.
|
||||
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"
|
||||
|
||||
// Public Methods //////////////////////////////////////////////////////////////
|
||||
uint32_t Interval::remains() const
|
||||
{
|
||||
|
||||
return _timeout - (millis() - _timefrom);
|
||||
// Returns the remaining time until expiration, correctly handling overflow
|
||||
uint32_t Interval::remains() const {
|
||||
uint32_t currentTime = millis();
|
||||
return _timeout - (currentTime - _timefrom);
|
||||
}
|
||||
|
||||
uint32_t Interval::elapsed() const
|
||||
{
|
||||
|
||||
// Returns the elapsed time since the interval started
|
||||
uint32_t Interval::elapsed() const {
|
||||
return millis() - _timefrom;
|
||||
}
|
||||
|
||||
bool Interval::expired()
|
||||
{
|
||||
bool result = false;
|
||||
// Checks if the interval has expired based on the mode
|
||||
bool Interval::expired() {
|
||||
uint32_t currentTime = millis();
|
||||
uint32_t elapsedTime = currentTime - _timefrom;
|
||||
|
||||
if (_done) {
|
||||
if (1 == _mode) {
|
||||
// oneshot mode
|
||||
if ((millis() - _timefrom) >= _timeout) {
|
||||
_done = 0;
|
||||
result = true;
|
||||
switch (_mode) {
|
||||
case ONESHOT:
|
||||
// In oneshot mode, return true only once upon expiration
|
||||
if (elapsedTime >= _timeout) {
|
||||
if (_active) {
|
||||
_active = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (2 == _mode) {
|
||||
// periodic mode
|
||||
if ((millis() - _timefrom) >= _timeout) {
|
||||
result = true;
|
||||
_timefrom = millis();
|
||||
break;
|
||||
case PERIODIC:
|
||||
// In periodic mode, return true and reset the timer on expiration
|
||||
if (elapsedTime >= _timeout) {
|
||||
_timefrom += _timeout;
|
||||
if (currentTime - _timefrom >= _timeout) {
|
||||
_timefrom = currentTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// compatibility mode
|
||||
if ((millis() - _timefrom) >= _timeout)
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case COMPATIBILITY:
|
||||
default:
|
||||
// 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();
|
||||
_timeout = tmout;
|
||||
_mode = 0;
|
||||
_done = 0xff;
|
||||
_mode = COMPATIBILITY;
|
||||
_active = true;
|
||||
}
|
||||
|
||||
void Interval::setOneshot(uint32_t tmout)
|
||||
{
|
||||
|
||||
// Sets the interval in oneshot mode
|
||||
void Interval::setOneshot(const uint32_t tmout) {
|
||||
_timefrom = millis();
|
||||
_timeout = tmout;
|
||||
_mode = 1;
|
||||
_done = 0xff;
|
||||
_mode = ONESHOT;
|
||||
_active = true;
|
||||
}
|
||||
|
||||
void Interval::setPeriodic(uint32_t tmout)
|
||||
{
|
||||
|
||||
// Sets the interval in periodic mode
|
||||
void Interval::setPeriodic(const uint32_t tmout) {
|
||||
_timefrom = millis();
|
||||
_timeout = tmout;
|
||||
_mode = 2;
|
||||
_done = 0xff;
|
||||
_mode = PERIODIC;
|
||||
}
|
||||
|
||||
void Interval::reload()
|
||||
{
|
||||
|
||||
// Reloads the interval timer (resets the starting time)
|
||||
void Interval::reload() {
|
||||
_timefrom = millis();
|
||||
_done = 0xff;
|
||||
_active = true;
|
||||
}
|
||||
|
@ -1,31 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
/* Interval
|
||||
* Copyright (C) 2014, 2016, 2018, 2019, 2023 Pavel Brychta http://www.xpablo.cz
|
||||
/*
|
||||
* Interval
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class Interval
|
||||
{
|
||||
protected:
|
||||
uint32_t _timefrom = 0;
|
||||
uint32_t _timeout = 0;
|
||||
uint8_t _mode = 0; // mode of actual operation (compatibility, oneshot, periodic)
|
||||
uint8_t _done = 0xff; // compatibility mode autostart
|
||||
public:
|
||||
class Interval {
|
||||
public:
|
||||
// Enumeration for interval modes
|
||||
enum IntervalMode {
|
||||
COMPATIBILITY = 0,
|
||||
ONESHOT = 1,
|
||||
PERIODIC = 2
|
||||
};
|
||||
|
||||
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;
|
||||
explicit Interval(uint32_t tmout)
|
||||
: _timeout(tmout)
|
||||
, _mode(2) // periodic mode autostart
|
||||
{}
|
||||
|
||||
// Constructor for periodic mode with 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();
|
||||
|
||||
// Sets the interval in compatibility mode
|
||||
void set(uint32_t tmout);
|
||||
|
||||
// Sets the interval in oneshot mode
|
||||
void setOneshot(uint32_t tmout);
|
||||
|
||||
// Sets the interval in periodic mode
|
||||
void setPeriodic(uint32_t tmout);
|
||||
|
||||
// Reloads the interval timer without changing mode or timeout
|
||||
void reload();
|
||||
|
||||
// Returns the elapsed time since the interval started
|
||||
[[nodiscard]] uint32_t elapsed() const;
|
||||
|
||||
// Returns the remaining time until the interval expires
|
||||
[[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