Refactored code, unit tests added, GA activated.

This commit is contained in:
Pavel Brychta 2025-03-07 20:54:10 +01:00
parent 1c870374cd
commit a653f0e3d6
9 changed files with 344 additions and 95 deletions

31
.clang-format Normal file
View 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
View 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

View 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
View File

@ -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

View File

@ -1,5 +1,7 @@
# Interval - Arduino knihovna pro časování pomocí intervalů # Interval - Arduino knihovna pro časování pomocí intervalů
![Build](https://git.xpablo.cz/pablo2048/Interval/actions/workflows/pio_tests.yml/badge.svg)
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
View 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

View File

@ -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;
} }

View File

@ -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
View 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();
}