Conversation
- 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.
There was a problem hiding this comment.
Pull request overview
Releases Countimer 1.1.0 by improving timer accuracy and robustness (elapsed-time based ticking, pause/restart semantics, missing method implementation), adding millisecond support + calibration, and modernizing the project with updated docs, examples, and CI for linting/compilation.
Changes:
- Fixes long-run drift by advancing the counter using measured
millis()deltas and resetting the time reference before callbacks. - Adds millisecond precision APIs (
setCounter(..., milliseconds),getCurrentMilliseconds(),getCurrentTimeWithMillis()) plussetCalibration(). - Adds/updates packaging and contributor tooling: README rewrite, examples, build scripts, changelog/license, 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 | Public API updates (ms precision, calibration, reset), const-correct getters, member initialization. |
| src/Countimer.cpp | Implements new APIs and fixes timing logic (elapsed-based ticks, pause/resume/restart behavior). |
| scripts/build-examples.sh | Bash helper to compile examples with arduino-cli. |
| scripts/build-examples.ps1 | PowerShell helper to compile examples with arduino-cli. |
| README.md | Modernized documentation + API reference + new feature descriptions. |
| LICENSE | Adds MIT license text. |
| library.properties | Bumps version to 1.1.0 and improves metadata (paragraph/includes). |
| keywords.txt | Updates Arduino IDE keyword highlighting for new API surface. |
| examples/CountimerTest/wokwi.toml | Wokwi configuration for simulator runs. |
| examples/CountimerTest/diagram.json | Wokwi diagram for simulator runs. |
| examples/CountimerTest/CountimerTest.ino | Updates example to new API + ESP32 compatibility + sub-second demo. |
| examples/Basic/wokwi.toml | Wokwi configuration for simulator runs. |
| examples/Basic/diagram.json | Wokwi diagram for simulator runs. |
| examples/Basic/Basic.ino | New minimal countdown example aligned with README quick start. |
| examples/Advanced/wokwi.toml | Wokwi configuration for simulator runs. |
| examples/Advanced/diagram.json | Wokwi diagram for simulator runs. |
| examples/Advanced/Advanced.ino | New advanced serial-controlled example covering state queries + calibration. |
| CONTRIBUTING.md | Adds contribution guidelines, API sync checklist, and build/verify instructions. |
| CLAUDE.md | Adds agent-oriented repo guidance and architecture notes. |
| CHANGELOG.md | Adds changelog with 1.1.0 notes and prior release entry. |
| .gitignore | Ignores example build outputs and common embedded tooling dirs. |
| .github/workflows/ci.yml | Adds CI: arduino-lint and example compilation for AVR + ESP32. |
| .gitattributes | Uses export-ignore to keep Library Manager archives minimal. |
| .claude/skills/verify-examples/SKILL.md | Documents example compilation verification procedure. |
| .claude/skills/release/SKILL.md | Documents release preconditions/tagging procedure. |
| .claude/skills/check-api-sync/SKILL.md | Documents API consistency audit across header/cpp/README/keywords. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| char _formatted_time[10]; | ||
| CountType _countType; | ||
| char _formatted_time_ms[13]; | ||
| CountType _countType = COUNT_NONE; |
| // timer is running only if is not completed or not stopped. | ||
| if (_isCounterCompleted || _isStopped) | ||
| { |
| void Countimer::countUp(uint32_t elapsed) | ||
| { | ||
| if (_currentCountTime < _countTime) | ||
| if (_currentCountTime + elapsed < _countTime) | ||
| { |
|
|
||
| ## What this is | ||
|
|
||
| Countimer is a single-class Arduino library (`category=Timing`) for non-blocking timers and counters. The entire implementation is `src/Countimer.h` / `src/Countimer.cpp`. There is no host-side build, test runner, or CI — it is consumed as an Arduino IDE library. |
There was a problem hiding this comment.
💡 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".
| bool _isStopped = true; | ||
| char _formatted_time[10]; | ||
| CountType _countType; | ||
| char _formatted_time_ms[13]; |
There was a problem hiding this comment.
Resize the millisecond time buffer
When the timer is configured for 100 hours or more, getCurrentTimeWithMillis() needs 13 visible characters plus the NUL terminator (for example, 100:00:00.123), but this buffer only has 13 bytes. Because hours are allowed up to 999, the new millisecond formatter truncates valid values such as 100:00:00.123 to 100:00:00.12; make the buffer at least 14 bytes.
Useful? React with 👍 / 👎.
[1.1.0] - 2026-07-05
Fixed
real measured
millis()delta instead of the nominal interval, and the time referencepoint 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.printat 9600 baud) were silently lost on every tick, so the error grewlinearly with the count duration — easily tens of seconds per hour on a busy
loop().Accuracy is now independent of count length and
loop()load.start()now resets the time reference pointwhen resuming, so time spent paused is no longer counted. Calling
start()on everyloop()iteration (the documented usage pattern) remains safe — the reset only happenson an actual stopped-to-running transition.
restart()while running. The counter now restarts from a fresh time referencepoint instead of inheriting the previous tick's remainder.
isCounterRunning()missing implementation. The method was declared in the headerand documented in the README, but never defined — calling it failed at link time.
run()was called withoutsetInterval()configured.The interval and completion callback pointers were uninitialized; they now default
to
NULLand are guarded before invocation.Added
setCounter()now has overloads accepting amilliseconds(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)
getCurrentMilliseconds()andgetCurrentTimeWithMillis()(formattedHH:MM:SS.mmm) read it back. Existing overloads andgetCurrentTime()are unchanged.(How to show millisecond #15)
setCalibration(float factor)— optional one-time, per-board correction of hardwaremillis()drift (e.g. ceramic resonator tolerance, up to ~0.5% on many Uno/Nanoclones). Measure
factor = real_elapsed_time / timer_indicated_timeagainst areference clock; default
1.0means no correction. Fractional milliseconds arecarried between ticks, so the correction itself introduces no rounding drift.
LICENSEfile (MIT).arduino-lint(strict, library-manager mode) plus examplecompilation for
arduino:avr:unoandesp32:esp32:esp32.keywords.txt:CountType(KEYWORD1) andCOUNT_NONE/COUNT_UP/COUNT_DOWN(LITERAL1) for Arduino IDE highlighting.
library.properties: real library description inparagraphand theincludesfield.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/skills/:verify-examples(bootstraparduino-cliand compile all examples locally),check-api-sync(audit public APIconsistency across the header, cpp, README and
keywords.txt) andrelease(release preconditions and tagging procedure).
Changed
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 insetup()instead of conditionallyon every
loop()iteration.keywords.txtupdated withsetIntervalandsetCalibrationfor Arduino IDEhighlighting.
const,nullptrinstead ofNULL,snprintfinstead ofsprintfingetCurrentTime(), simplifiedgetCurrentSeconds()arithmetic, in-class member initialization (empty constructorand destructor removed), single include guard instead of guard +
#pragma once,removed redundant
Arduino.hinclude from the.cpp.<Countimer.h>, usesLED_BUILTINandconfigures it with
pinMode()insetup().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.