New button template
This commit is contained in:
35
templates/button/__name__/__name__.ino.tmpl
Normal file
35
templates/button/__name__/__name__.ino.tmpl
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* {{PROJECT_NAME}}.ino -- Pushbutton input with edge detection
|
||||
*
|
||||
* Detects button presses (rising edge only) and prints to Serial:
|
||||
*
|
||||
* Button pressed! (count: 1)
|
||||
* Button pressed! (count: 2)
|
||||
*
|
||||
* All logic lives in lib/app/{{PROJECT_NAME}}_app.h which depends
|
||||
* on the HAL and Button interfaces, making it fully testable
|
||||
* on the host without hardware.
|
||||
*
|
||||
* Wiring (active-low, no external resistor needed):
|
||||
* Pin 2 -> one leg of the button
|
||||
* GND -> other leg of the button
|
||||
* (uses INPUT_PULLUP internally)
|
||||
*
|
||||
* Serial: 115200 baud
|
||||
*/
|
||||
|
||||
#include <hal_arduino.h>
|
||||
#include <{{PROJECT_NAME}}_app.h>
|
||||
#include <button_digital.h>
|
||||
|
||||
static ArduinoHal hw;
|
||||
static ButtonDigital btn(&hw, 2); // pin 2, active-low (default)
|
||||
static ButtonApp app(&hw, &btn);
|
||||
|
||||
void setup() {
|
||||
app.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
app.update();
|
||||
}
|
||||
77
templates/button/lib/app/__name___app.h.tmpl
Normal file
77
templates/button/lib/app/__name___app.h.tmpl
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include <hal.h>
|
||||
#include "button.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* ButtonApp -- Detects button presses and reports to Serial.
|
||||
*
|
||||
* Uses polling with edge detection: only triggers on the transition
|
||||
* from released to pressed (rising edge). Holding the button does
|
||||
* not generate repeated messages.
|
||||
*
|
||||
* Each press prints:
|
||||
* Button pressed! (count: 1)
|
||||
*
|
||||
* The button is injected through the Button interface, so this
|
||||
* class works with real hardware, mocks, or simulations.
|
||||
*
|
||||
* Wiring (active-low, no external resistor needed):
|
||||
* Digital pin 2 -> one leg of the button
|
||||
* GND -> other leg of the button
|
||||
* Set pin mode to INPUT_PULLUP in begin().
|
||||
*/
|
||||
class ButtonApp {
|
||||
public:
|
||||
static constexpr uint8_t BUTTON_PIN = 2;
|
||||
|
||||
ButtonApp(Hal* hal, Button* button)
|
||||
: hal_(hal)
|
||||
, button_(button)
|
||||
, was_pressed_(false)
|
||||
, press_count_(0)
|
||||
{}
|
||||
|
||||
// Call once from setup()
|
||||
void begin() {
|
||||
hal_->serialBegin(115200);
|
||||
hal_->pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
hal_->serialPrintln("ButtonApp ready -- press the button!");
|
||||
// Read initial state so we don't false-trigger on startup
|
||||
was_pressed_ = button_->isPressed();
|
||||
}
|
||||
|
||||
// Call repeatedly from loop()
|
||||
void update() {
|
||||
bool pressed_now = button_->isPressed();
|
||||
|
||||
// Rising edge: was released, now pressed
|
||||
if (pressed_now && !was_pressed_) {
|
||||
press_count_++;
|
||||
onPress();
|
||||
}
|
||||
|
||||
was_pressed_ = pressed_now;
|
||||
}
|
||||
|
||||
// -- Accessors for testing ------------------------------------------------
|
||||
int pressCount() const { return press_count_; }
|
||||
bool wasPressed() const { return was_pressed_; }
|
||||
|
||||
private:
|
||||
void onPress() {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "Button pressed! (count: %d)", press_count_);
|
||||
hal_->serialPrintln(buf);
|
||||
}
|
||||
|
||||
Hal* hal_;
|
||||
Button* button_;
|
||||
bool was_pressed_;
|
||||
int press_count_;
|
||||
};
|
||||
|
||||
#endif // APP_H
|
||||
25
templates/button/template.toml
Normal file
25
templates/button/template.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[template]
|
||||
name = "button"
|
||||
base = "basic"
|
||||
description = "Pushbutton input with edge detection and serial output"
|
||||
|
||||
[requires]
|
||||
libraries = ["button"]
|
||||
board_capabilities = ["digital"]
|
||||
|
||||
# Default pin assignments per board.
|
||||
# Pin 2 supports interrupts on most boards, good default for buttons.
|
||||
[pins.default]
|
||||
button_signal = { pin = "2", mode = "input" }
|
||||
|
||||
[pins.uno]
|
||||
button_signal = { pin = "2", mode = "input" }
|
||||
|
||||
[pins.mega]
|
||||
button_signal = { pin = "2", mode = "input" }
|
||||
|
||||
[pins.nano]
|
||||
button_signal = { pin = "2", mode = "input" }
|
||||
|
||||
[pins.leonardo]
|
||||
button_signal = { pin = "2", mode = "input" }
|
||||
283
templates/button/test/test_button_app.cpp.tmpl
Normal file
283
templates/button/test/test_button_app.cpp.tmpl
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* test_button_app.cpp -- Button application example tests.
|
||||
*
|
||||
* THIS FILE IS MANAGED BY ANVIL and will be updated by `anvil refresh`.
|
||||
* Do not edit -- put your own tests in test_unit.cpp and test_system.cpp.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "mock_arduino.h"
|
||||
#include "hal.h"
|
||||
#include "mock_hal.h"
|
||||
#include "sim_hal.h"
|
||||
#include "button_mock.h"
|
||||
#include "button_sim.h"
|
||||
#include "{{PROJECT_NAME}}_app.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AnyNumber;
|
||||
using ::testing::Return;
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests -- verify ButtonApp behavior with mock button
|
||||
// ============================================================================
|
||||
|
||||
class ButtonUnitTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
||||
EXPECT_CALL(mock_, serialBegin(_)).Times(AnyNumber());
|
||||
EXPECT_CALL(mock_, serialPrint(_)).Times(AnyNumber());
|
||||
EXPECT_CALL(mock_, serialPrintln(_)).Times(AnyNumber());
|
||||
EXPECT_CALL(mock_, millis()).Times(AnyNumber());
|
||||
EXPECT_CALL(mock_, pinMode(_, _)).Times(AnyNumber());
|
||||
}
|
||||
|
||||
::testing::NiceMock<MockHal> mock_;
|
||||
ButtonMock btn_;
|
||||
};
|
||||
|
||||
TEST_F(ButtonUnitTest, BeginPrintsReadyMessage) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
|
||||
EXPECT_CALL(mock_, serialBegin(115200)).Times(1);
|
||||
EXPECT_CALL(mock_, serialPrintln(HasSubstr("ButtonApp ready"))).Times(1);
|
||||
|
||||
app.begin();
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, BeginSetsPinMode) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
|
||||
EXPECT_CALL(mock_, pinMode(2, INPUT_PULLUP)).Times(1);
|
||||
|
||||
app.begin();
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, NoPressNoCount) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
app.begin();
|
||||
|
||||
// Button not pressed, update should not increment count
|
||||
btn_.setPressed(false);
|
||||
app.update();
|
||||
app.update();
|
||||
app.update();
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 0);
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, SinglePressIncrementsCount) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, HoldDoesNotRepeat) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
btn_.setPressed(true);
|
||||
app.update(); // rising edge -> count = 1
|
||||
app.update(); // still pressed -> no increment
|
||||
app.update(); // still pressed -> no increment
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, ReleaseAndPressAgain) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
// First press
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
EXPECT_EQ(app.pressCount(), 1);
|
||||
|
||||
// Release
|
||||
btn_.setPressed(false);
|
||||
app.update();
|
||||
|
||||
// Second press
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
EXPECT_EQ(app.pressCount(), 2);
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, PrintsMessageOnPress) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
EXPECT_CALL(mock_, serialPrintln(HasSubstr("Button pressed!"))).Times(1);
|
||||
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, PrintsCountInMessage) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
EXPECT_CALL(mock_, serialPrintln(HasSubstr("count: 1"))).Times(1);
|
||||
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, DoesNotPrintOnRelease) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
|
||||
// Release should not trigger another print
|
||||
EXPECT_CALL(mock_, serialPrintln(HasSubstr("Button pressed!"))).Times(0);
|
||||
|
||||
btn_.setPressed(false);
|
||||
app.update();
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, TenPresses) {
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
btn_.setPressed(false);
|
||||
app.begin();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
btn_.setPressed(false);
|
||||
app.update();
|
||||
}
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 10);
|
||||
}
|
||||
|
||||
TEST_F(ButtonUnitTest, StartupWithButtonAlreadyPressed) {
|
||||
// If the button is held during startup, begin() reads it as pressed.
|
||||
// The first update() should NOT trigger a press (no rising edge).
|
||||
btn_.setPressed(true);
|
||||
|
||||
ButtonApp app(&mock_, &btn_);
|
||||
app.begin();
|
||||
|
||||
app.update(); // still pressed, no edge
|
||||
EXPECT_EQ(app.pressCount(), 0);
|
||||
|
||||
// Release and press again -- THIS should trigger
|
||||
btn_.setPressed(false);
|
||||
app.update();
|
||||
btn_.setPressed(true);
|
||||
app.update();
|
||||
EXPECT_EQ(app.pressCount(), 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// System Tests -- exercise ButtonApp with simulated button and hardware
|
||||
// ============================================================================
|
||||
|
||||
class ButtonSystemTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
mock_arduino_reset();
|
||||
sim_.setMillis(0);
|
||||
}
|
||||
|
||||
SimHal sim_;
|
||||
ButtonSim btn_{0}; // no bounce for predictable tests
|
||||
};
|
||||
|
||||
TEST_F(ButtonSystemTest, StartsAndPrintsToSerial) {
|
||||
ButtonApp app(&sim_, &btn_);
|
||||
app.begin();
|
||||
|
||||
std::string output = sim_.serialOutput();
|
||||
EXPECT_NE(output.find("ButtonApp ready"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ButtonSystemTest, PressShowsInSerialOutput) {
|
||||
ButtonApp app(&sim_, &btn_);
|
||||
app.begin();
|
||||
|
||||
btn_.press();
|
||||
app.update();
|
||||
|
||||
std::string output = sim_.serialOutput();
|
||||
EXPECT_NE(output.find("Button pressed!"), std::string::npos);
|
||||
EXPECT_NE(output.find("count: 1"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ButtonSystemTest, MultiplePressesCountCorrectly) {
|
||||
ButtonApp app(&sim_, &btn_);
|
||||
app.begin();
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
btn_.press();
|
||||
app.update();
|
||||
btn_.release();
|
||||
app.update();
|
||||
}
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 5);
|
||||
|
||||
std::string output = sim_.serialOutput();
|
||||
EXPECT_NE(output.find("count: 5"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ButtonSystemTest, HoldOnlyCountsOnce) {
|
||||
ButtonApp app(&sim_, &btn_);
|
||||
app.begin();
|
||||
|
||||
btn_.press();
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
app.update();
|
||||
}
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 1);
|
||||
}
|
||||
|
||||
TEST_F(ButtonSystemTest, RapidPressReleaseCycle) {
|
||||
ButtonApp app(&sim_, &btn_);
|
||||
app.begin();
|
||||
|
||||
// Simulate rapid button mashing
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
btn_.press();
|
||||
app.update();
|
||||
btn_.release();
|
||||
app.update();
|
||||
}
|
||||
|
||||
EXPECT_EQ(app.pressCount(), 50);
|
||||
}
|
||||
|
||||
TEST_F(ButtonSystemTest, BouncyButtonWithSettling) {
|
||||
ButtonSim bouncy_btn(5); // 5 reads of bounce
|
||||
bouncy_btn.setSeed(42);
|
||||
ButtonApp app(&sim_, &bouncy_btn);
|
||||
app.begin();
|
||||
|
||||
bouncy_btn.press();
|
||||
|
||||
// During bounce, we may get spurious edges.
|
||||
// After settling, one press should eventually register.
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
app.update();
|
||||
}
|
||||
|
||||
// At least one press should have registered
|
||||
EXPECT_GE(app.pressCount(), 1);
|
||||
}
|
||||
31
templates/button/test/test_system.cpp.tmpl
Normal file
31
templates/button/test/test_system.cpp.tmpl
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* test_system.cpp -- Your system tests go here.
|
||||
*
|
||||
* This file is YOURS. Anvil will never overwrite it.
|
||||
* The button example tests are in test_button_app.cpp.
|
||||
*
|
||||
* System tests use SimHal and ButtonSim to exercise real application
|
||||
* logic against simulated hardware. See test_button_app.cpp for examples.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "mock_arduino.h"
|
||||
#include "hal.h"
|
||||
#include "sim_hal.h"
|
||||
#include "button_sim.h"
|
||||
#include "{{PROJECT_NAME}}_app.h"
|
||||
|
||||
// Example: add your own system tests below
|
||||
// TEST(MySystemTests, DescribeWhatItTests) {
|
||||
// mock_arduino_reset();
|
||||
// SimHal sim;
|
||||
// ButtonSim btn(0); // 0 = no bounce
|
||||
//
|
||||
// ButtonApp app(&sim, &btn);
|
||||
// app.begin();
|
||||
//
|
||||
// btn.press();
|
||||
// app.update();
|
||||
// EXPECT_EQ(app.pressCount(), 1);
|
||||
// }
|
||||
34
templates/button/test/test_unit.cpp.tmpl
Normal file
34
templates/button/test/test_unit.cpp.tmpl
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* test_unit.cpp -- Your unit tests go here.
|
||||
*
|
||||
* This file is YOURS. Anvil will never overwrite it.
|
||||
* The button example tests are in test_button_app.cpp.
|
||||
*
|
||||
* Unit tests use MockHal and ButtonMock to verify exact behavior
|
||||
* without real hardware. See test_button_app.cpp for examples.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "mock_hal.h"
|
||||
#include "button_mock.h"
|
||||
#include "{{PROJECT_NAME}}_app.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AnyNumber;
|
||||
using ::testing::Return;
|
||||
|
||||
// Example: add your own tests below
|
||||
// TEST(MyTests, DescribeWhatItTests) {
|
||||
// ::testing::NiceMock<MockHal> mock;
|
||||
// ButtonMock btn;
|
||||
//
|
||||
// ButtonApp app(&mock, &btn);
|
||||
// app.begin();
|
||||
//
|
||||
// btn.setPressed(true);
|
||||
// app.update();
|
||||
// EXPECT_EQ(app.pressCount(), 1);
|
||||
// }
|
||||
Reference in New Issue
Block a user