Skip to content

Release [1.1.0]#34

Merged
inflop merged 12 commits into
masterfrom
develop
Jul 5, 2026
Merged

Release [1.1.0]#34
inflop merged 12 commits into
masterfrom
develop

Conversation

@inflop

@inflop inflop commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Fixed

  • Counting drift accumulating over long count times. The counter now advances by the
    real measured millis() delta instead of the nominal interval, and the time reference
    point is moved before user callbacks run. Previously, both the check overshoot
    (the loop rarely hits the interval boundary exactly) and the callback execution time
    (e.g. Serial.print at 9600 baud) were silently lost on every tick, so the error grew
    linearly with the count duration — easily tens of seconds per hour on a busy loop().
    Accuracy is now independent of count length and loop() load.
  • Paused time leaking into the count. start() now resets the time reference point
    when resuming, so time spent paused is no longer counted. Calling start() on every
    loop() iteration (the documented usage pattern) remains safe — the reset only happens
    on an actual stopped-to-running transition.
  • restart() while running. The counter now restarts from a fresh time reference
    point instead of inheriting the previous tick's remainder.
  • isCounterRunning() missing implementation. The method was declared in the header
    and documented in the README, but never defined — calling it failed at link time.
  • Potential crash when run() was called without setInterval() configured.
    The interval and completion callback pointers were uninitialized; they now default
    to NULL and are guarded before invocation.

Added

  • Millisecond precision. setCounter() now has overloads accepting a milliseconds
    (0-999) argument, so counts can be configured with sub-second precision (e.g. a 1.5 s
    countdown).
    (How to use 0.1 second #22)
  • New getCurrentMilliseconds() and getCurrentTimeWithMillis() (formatted
    HH:MM:SS.mmm) read it back. Existing overloads and getCurrentTime() are unchanged.
    (How to show millisecond #15)
  • reset() — restore the counter to its initial time and leave the timer stopped
    (not completed), ready to be started again with start(). Fills the gap between
    restart() (reset + start) and stop() (reset + mark completed); behaves uniformly
    across COUNT_UP / COUNT_DOWN / COUNT_NONE. restart() is now built on it.
    (Is it Possible to reset the timer without restarting it? #17)
  • setCalibration(float factor) — optional one-time, per-board correction of hardware
    millis() drift (e.g. ceramic resonator tolerance, up to ~0.5% on many Uno/Nano
    clones). Measure factor = real_elapsed_time / timer_indicated_time against a
    reference clock; default 1.0 means no correction. Fractional milliseconds are
    carried between ticks, so the correction itself introduces no rounding drift.
  • "Accuracy and calibration" section in the README.
  • LICENSE file (MIT).
  • GitHub Actions CI: arduino-lint (strict, library-manager mode) plus example
    compilation for arduino:avr:uno and esp32:esp32:esp32.
  • "Installation" and "License" sections in the README.
  • keywords.txt: CountType (KEYWORD1) and COUNT_NONE/COUNT_UP/COUNT_DOWN
    (LITERAL1) for Arduino IDE highlighting.
  • library.properties: real library description in paragraph and the includes field.
  • examples/Basic — minimal count-down sketch for getting started.
  • examples/Advanced — count-up with pause/resume over serial, state queries,
    re-programming the count time and calibration.
  • CONTRIBUTING.md — bug report / pull request guidelines, API sync checklist,
    build-and-test instructions and release steps.
  • Claude Code project skills in .claude/skills/: verify-examples (bootstrap
    arduino-cli and compile all examples locally), check-api-sync (audit public API
    consistency across the header, cpp, README and keywords.txt) and release
    (release preconditions and tagging procedure).

Changed

  • README rewritten to current library standards: badges (release, license, Library
    Manager), feature list, quick start, per-mode usage snippets, a full API reference
    table matching the header signatures, examples/compatibility/contributing sections.
    The quick-start sketch now calls start() once in setup() instead of conditionally
    on every loop() iteration.
  • keywords.txt updated with setInterval and setCalibration for Arduino IDE
    highlighting.
  • Non-breaking code cleanups: getters are now const, nullptr instead of NULL,
    snprintf instead of sprintf in getCurrentTime(), simplified
    getCurrentSeconds() arithmetic, in-class member initialization (empty constructor
    and destructor removed), single include guard instead of guard + #pragma once,
    removed redundant Arduino.h include from the .cpp.
  • Example sketch: includes the library with <Countimer.h>, uses LED_BUILTIN and
    configures it with pinMode() in setup().
  • Completion timing semantics: a countdown now completes on the tick where the remaining
    time no longer exceeds the measured delta (up to one interval early), instead of one
    full tick after reaching zero (up to one interval late). Either way the boundary error
    is below one interval and does not accumulate.

- advance the counter by the real measured millis() delta and move the
  time reference before callbacks run, so neither check overshoot nor
  callback duration accumulates as drift (accuracy is now independent
  of count length and loop() load)
- stop paused time from leaking into the count: start() resets the time
  reference only on a stopped-to-running transition
- restart() no longer inherits the previous tick's remainder
- define isCounterRunning() (was declared and documented, but failed at
  link time when called)
- guard uninitialized interval/callback pointers in run()
- add setCalibration(float): optional per-board correction of hardware
  millis() drift, with fractional milliseconds carried between ticks
- sync keywords.txt with the public API
- README brought to current library standards: badges, feature list,
  quick start, per-mode usage snippets, full API reference table,
  examples/compatibility/contributing sections
- add CONTRIBUTING.md, CHANGELOG.md (Keep a Changelog format), MIT
  LICENSE and CLAUDE.md
- add examples/Basic (minimal count-down) and examples/Advanced
  (count-up, pause/resume over serial, state queries, re-programming
  the count time, calibration); clean up CountimerTest
- GitHub Actions: arduino-lint (strict, library-manager mode) plus
  compilation of all examples for arduino:avr:uno and esp32:esp32:esp32
- Claude Code project skills: verify-examples, check-api-sync, release
- library.properties: bump version to 1.1.0, real description in
  paragraph and the includes field
PR #30 (master) added a non-const isCounterRunning() independently of
the const version added on dev in 1d6daaa. Merging master into dev
(b64b41c) kept both as non-conflicting additions, leaving a stray
definition that doesn't match the const-qualified declaration in
Countimer.h and breaks compilation. Keep the const version, which
also correctly excludes completed counters.
The skill only documented installing arduino-cli on Windows. Add the
official install script for Linux, noting that BINDIR must be set
explicitly or the binary lands in ./bin instead of on PATH. Verified
end-to-end in WSL (Debian): install, AVR core install, and compiling
all three examples all succeed with the same output sizes as on
Windows.
Generic ESP32 boards (esp32:esp32:esp32 FQBN) don't define LED_BUILTIN
in the core, unlike AVR, causing the compile-sketches CI job to fail.
setCounter() gains overloads accepting a milliseconds (0-999)
argument, and getCurrentMilliseconds()/getCurrentTimeWithMillis()
read the sub-second remainder back. Existing overloads and
getCurrentTime() are unchanged, so no existing call site needs to
change.

Closes #22 (sub-second count time), closes #15 (displaying
milliseconds).
Resolves #17. reset() restores the counter to its initial time and leaves
the timer stopped (not completed), ready to be started again with start() —
filling the gap between restart() (reset + start) and stop() (reset + mark
completed). Behaves uniformly across COUNT_UP / COUNT_DOWN / COUNT_NONE.

restart() is now expressed as reset() + start(), removing the duplicated
"return to start time" logic.

Sync the public API across all four surfaces (header, cpp, README,
keywords.txt) and add an 'E' key to the CountimerTest example.
Copilot AI review requested due to automatic review settings July 5, 2026 14:07
@inflop inflop merged commit 3087d0b into master Jul 5, 2026
11 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prepares the 1.1.0 release of Countimer by improving long-run timing accuracy (elapsed-delta based counting + corrected reference point), adding millisecond-level counter configuration/formatting and optional calibration, and bringing the project up to Arduino Library Manager/CI/documentation standards.

Changes:

  • Refactors core timing logic to advance by measured millis() deltas (with optional calibration) and fixes isCounterRunning() implementation.
  • Adds millisecond-precision APIs (setCounter(..., milliseconds), getCurrentMilliseconds(), getCurrentTimeWithMillis()) and a new reset() control method.
  • Adds/updates release collateral: rewritten README, changelog, license, keywords/library metadata, examples, scripts, and GitHub Actions CI.

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Countimer.h Adds new public APIs (ms precision, calibration, reset) and in-class member initialization.
src/Countimer.cpp Implements drift fix via measured elapsed time + calibration; adds new API implementations and state semantics.
scripts/build-examples.sh Adds a bash helper to compile examples with arduino-cli.
scripts/build-examples.ps1 Adds a PowerShell helper to compile examples with arduino-cli.
README.md Rewrites documentation to modern Arduino library standards and documents new APIs/semantics.
LICENSE Adds MIT license file.
library.properties Bumps version to 1.1.0 and improves metadata (paragraph/includes).
keywords.txt Updates Arduino IDE keyword highlighting for new API and enums.
examples/CountimerTest/wokwi.toml Adds Wokwi simulator config for CountimerTest example.
examples/CountimerTest/diagram.json Adds Wokwi wiring diagram for CountimerTest example.
examples/CountimerTest/CountimerTest.ino Updates example to use <Countimer.h>, LED portability, reset key, and sub-second demo.
examples/Basic/wokwi.toml Adds Wokwi simulator config for Basic example.
examples/Basic/diagram.json Adds Wokwi wiring diagram for Basic example.
examples/Basic/Basic.ino Adds minimal countdown “Basic” example sketch.
examples/Advanced/wokwi.toml Adds Wokwi simulator config for Advanced example.
examples/Advanced/diagram.json Adds Wokwi wiring diagram for Advanced example.
examples/Advanced/Advanced.ino Adds an “Advanced” interactive serial-controlled example sketch.
CONTRIBUTING.md Adds contribution guidelines, API sync checklist, and build/verification instructions.
CLAUDE.md Adds repository guidance documenting timing architecture and contribution notes.
CHANGELOG.md Adds Keep-a-Changelog formatted changelog including 1.1.0 entry.
.gitignore Ignores PlatformIO artifacts and example build outputs.
.github/workflows/ci.yml Adds CI to run arduino-lint and compile examples for AVR Uno and ESP32.
.gitattributes Excludes CI/scripts/docs/skill files from Arduino Library Manager release archives.
.claude/skills/verify-examples/SKILL.md Adds documented Claude Code skill for compiling examples locally.
.claude/skills/release/SKILL.md Adds documented Claude Code skill for release/tagging procedure.
.claude/skills/check-api-sync/SKILL.md Adds documented Claude Code skill for API consistency auditing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Countimer.h
bool _isStopped = true;
char _formatted_time[10];
CountType _countType;
char _formatted_time_ms[13];
Comment thread src/Countimer.cpp
Comment on lines 51 to 55
void Countimer::setInterval(timer_callback callback, uint32_t interval)
{
_interval = interval;
_callback = callback;
}
Comment thread src/Countimer.cpp
Comment on lines +218 to +222
if (_currentCountTime + elapsed < _countTime)
{
// Add the real measured time, not the nominal interval,
// so the error does not accumulate over long counts.
_currentCountTime += elapsed;
Comment thread src/Countimer.cpp

void Countimer::run()
{
// timer is running only if is not completed or not stopped.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f62fdd34aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Countimer.cpp
Comment on lines +113 to +119
if(_isStopped)
{
// Reset the reference point only on a real resume, so paused time
// is not counted. start() may be called on every loop() iteration.
_isStopped = false;
_previousMillis = millis();
_calibrationRemainder = 0.0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep completed timers from auto-restarting

With the new guidance that callers may invoke start() on every loop() iteration, a timer that has just completed is also stopped because completion calls stop(). The next unconditional start() therefore enters this branch, then clears _isCounterCompleted, so the timer starts over and isCounterCompleted() is only observable during the completion callback; either make start() a no-op for completed timers or keep the documented loop pattern guarded by the completion state.

Useful? React with 👍 / 👎.

Comment thread src/Countimer.cpp
void Countimer::countUp(uint32_t elapsed)
{
if (_currentCountTime < _countTime)
if (_currentCountTime + elapsed < _countTime)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid wrapping the count-up completion check

For long count-up timers near the supported 999-hour maximum, if one run() call is delayed by a large measured delta (for example after sleep or a blocked loop), _currentCountTime + elapsed can overflow uint32_t before it is compared with _countTime. When that happens the condition remains true, the counter wraps back to a small value, and the completion callback is skipped instead of firing; compare elapsed to the remaining time (_countTime - _currentCountTime) before adding.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants