initial commit

This commit is contained in:
2026-02-12 00:45:31 -08:00
commit 5f168f370b
3024 changed files with 804889 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
#include "fl/stdint.h"
#include "fl/memory.h"
#include "fl/ui.h"
#include "fl/assert.h"
#include "fl/namespace.h"
#include "sensors/button.h"
#include "sensors/digital_pin.h"
namespace fl {
ButtonLowLevel::ButtonLowLevel(int pin, ButtonStrategy strategy)
: mPin(pin) {
setStrategy(strategy);
}
ButtonLowLevel::~ButtonLowLevel() {}
bool ButtonLowLevel::highLowFloating() {
// FastLED doesn't have reliable support for pullups/pulldowns.
// So we instead use a strategy where the pin is set to high, then
// checked if it's high, then set to low, and then checked if it's low
// if this is the case, then the pin is floating and thefore the button is
// not being pressed.
mPin.setPinMode(DigitalPin::kOutput);
mPin.write(true); // set pin to high
mPin.setPinMode(DigitalPin::kInput);
const bool was_high = mPin.high(); // check if pin is high
mPin.setPinMode(DigitalPin::kOutput);
mPin.write(false); // set pin to low
mPin.setPinMode(DigitalPin::kInput);
const bool was_low = !mPin.high(); // check if pin is low
const bool floating =
was_high && was_low; // if both are true, then the pin is floating
const bool pressed =
!floating; // if the pin is floating, then the button is not pressed
return pressed;
}
bool ButtonLowLevel::isPressed() {
// FastLED doesn't have reliable support for pullups/pulldowns.
// So we instead use a strategy where the pin is set to high, then
// checked if it's high, then set to low, and then checked if it's low
// if this is the case, then the pin is floating and thefore the button is
// not being pressed. return (mStrategy == kHighLowFloating) ?
// highLowFloating() :
// (mStrategy == kPullUp) ? mPin.high() : // not implemented yet
// (mStrategy == kPullDown) ? !mPin.high() : // not implemented yet
// false; // unknown strategy, return false
switch (mStrategy) {
case kHighLowFloating:
return highLowFloating();
case kPullUp:
return mPin.high(); // not implemented yet
default:
FASTLED_ASSERT(false, "Unknown ButtonLowLevel strategy");
return false; // unknown strategy, return false
}
}
Button::Button(int pin, ButtonStrategy strategy)
: mButton(pin, strategy), mListener(this) {}
void Button::Listener::onEndFrame() {
const bool pressed_curr_frame = mOwner->mButton.isPressed();
const bool pressed_last_frame = mOwner->mPressedLastFrame;
const bool changed_this_frame = pressed_curr_frame != pressed_last_frame;
mOwner->mPressedLastFrame = pressed_curr_frame;
if (changed_this_frame && pressed_curr_frame) {
mOwner->mClickedThisFrame = true;
mOwner->mOnClickCallbacks.invoke();
}
}
Button::Listener::Listener(Button *owner) : mOwner(owner) {
addToEngineEventsOnce();
}
Button::Listener::~Listener() {
if (added) {
EngineEvents::removeListener(this);
}
}
void Button::Listener::addToEngineEventsOnce() {
if (added) {
return;
}
EngineEvents::addListener(this, 1); // One high priority so that it runs before UI elements.
added = true;
}
int Button::onClick(function<void()> callback) {
int id = mOnClickCallbacks.add(callback);
return id;
}
void ButtonLowLevel::setStrategy(ButtonStrategy strategy) {
mStrategy = strategy;
switch (mStrategy) {
case kHighLowFloating:
mPin.setPinMode(DigitalPin::kInput); // Set pin to input mode
break;
case kPullUp:
mPin.setPinMode(
DigitalPin::kInputPullup); // Set pin to input pullup mode
break;
default:
// Unknown strategy, do nothing
FASTLED_ASSERT(false, "Unknown ButtonLowLevel strategy");
break;
}
}
} // namespace fl

View File

@@ -0,0 +1,102 @@
#pragma once
#include "fl/stdint.h"
#include "fl/function_list.h"
#include "fl/namespace.h"
#include "fl/memory.h"
#include "fl/ui.h"
#include "sensors/digital_pin.h"
namespace fl {
enum ButtonStrategy {
// FastLED doesn't have reliable support for pullups/pulldowns.
// So we instead use a strategy where the pin is set to high, then
// checked if it's high, then set to low, and then checked if it's low
// if this is the case, then the pin is floating and thefore the button
// is not
// being pressed.
kHighLowFloating,
kPullUp,
};
// A simple digital pin. If we are compiling in an Arduino environment, then
// this class will bind to that. Otherwise it will fall back to the platform
// api. Note that this class does not support analog mode nor pullups/pulldowns.
class ButtonLowLevel {
public:
ButtonLowLevel(int pin, ButtonStrategy strategy = kHighLowFloating);
~ButtonLowLevel();
ButtonLowLevel(const ButtonLowLevel &other) = default;
ButtonLowLevel &operator=(const ButtonLowLevel &other) = delete;
ButtonLowLevel(ButtonLowLevel &&other) = delete;
bool isPressed();
bool highLowFloating();
void setStrategy(ButtonStrategy strategy);
private:
fl::DigitalPin mPin;
ButtonStrategy mStrategy = kHighLowFloating;
};
// The default button type hooks into the FastLED EngineEvents to monitor
// whether the button is pressed or not. You do not need to run an update
// function. If you need more control, use ButtonLowLevel directly.
class Button {
public:
Button(int pin,
ButtonStrategy strategy = ButtonStrategy::kHighLowFloating);
int onClick(fl::function<void()> callback);
void removeOnClick(int id) {
mOnClickCallbacks.remove(id);
}
void setStrategy(ButtonStrategy strategy) {
mButton.setStrategy(strategy);
}
bool isPressed() {
return mButton.isPressed();
}
bool clicked() const {
// If we have a real button, check if it's pressed
return mClickedThisFrame;
}
protected:
struct Listener : public EngineEvents::Listener {
Listener(Button *owner);
~Listener();
void addToEngineEventsOnce();
// We do an experiment here, what about listening to the end frame event
// instea do of the begin frame event? This will put the activation of
// this button **before** the next frame. I think this pattern should be
// used for all UI elements, so that the button state is updated before
// the next frame is drawn. This seems like the only way to do this, or
// by using platform pre loop, but not all platforms support that.
void onEndFrame() override;
private:
Button *mOwner;
bool added = false;
};
private:
ButtonLowLevel mButton;
Listener mListener;
bool mPressedLastFrame = false; // Don't read this variale, it's used internally.
bool mClickedThisFrame = false; // This is true if clicked this frame.
fl::FunctionList<void> mOnClickCallbacks;
// fl::FunctionList<void(Button&)> mOnChangedCallbacks;
};
} // namespace fl

View File

@@ -0,0 +1,106 @@
#include "fl/stdint.h"
#include "fl/ui.h"
#include "fl/memory.h"
#include "fl/namespace.h"
#include "digital_pin.h"
#if !defined(USE_ARDUINO) && __has_include(<Arduino.h>)
#define USE_ARDUINO 1
#else
#define USE_ARDUINO 0
#endif
#if USE_ARDUINO
#include <Arduino.h> // ok include
#else
// Fallback
#define FASTLED_INTERNAL
#include "FastLED.h"
#include "fastpin.h"
#endif
namespace fl {
#if USE_ARDUINO
class DigitalPinImpl : public Referent {
public:
DigitalPinImpl(int DigitalPin) : mDigitalPin(DigitalPin) {}
~DigitalPinImpl() = default;
void setPinMode(DigitalPin::Mode mode) {
switch (mode) {
case DigitalPin::kInput:
::pinMode(mDigitalPin, INPUT);
break;
case DigitalPin::kOutput:
::pinMode(mDigitalPin, OUTPUT);
break;
case DigitalPin::kInputPullup:
::pinMode(mDigitalPin, INPUT_PULLUP);
break;
}
}
bool high() { return HIGH == ::digitalRead(mDigitalPin); }
void write(bool value) { ::digitalWrite(mDigitalPin, value ? HIGH : LOW); }
private:
int mDigitalPin;
};
#else
class DigitalPinImpl : public Referent {
public:
DigitalPinImpl(int pin) : mPin(pin) {}
~DigitalPinImpl() = default;
void setPinMode(DigitalPin::Mode mode) {
switch (mode) {
case DigitalPin::kInput:
mPin.setInput();
break;
case DigitalPin::kOutput:
mPin.setOutput();
break;
case DigitalPin::kInputPullup:
mPin.setInputPullup();
break;
}
}
bool high() { return mPin.hival(); }
void write(bool value) { value ? mPin.hi(): mPin.lo(); }
// define pin
Pin mPin;
};
#endif
DigitalPin::DigitalPin(int DigitalPin) {
mImpl = fl::make_shared<DigitalPinImpl>(DigitalPin);
}
DigitalPin::~DigitalPin() = default;
DigitalPin::DigitalPin(const DigitalPin &other) = default;
DigitalPin& DigitalPin::operator=(const DigitalPin &other) = default;
void DigitalPin::setPinMode(Mode mode) {
mImpl->setPinMode(mode);
}
bool DigitalPin::high() const {
return mImpl->high();
}
void DigitalPin::write(bool is_high) {
mImpl->write(is_high);
}
} // namespace fl

View File

@@ -0,0 +1,39 @@
#pragma once
#include "fl/stdint.h"
#include "fl/memory.h"
namespace fl {
FASTLED_SMART_PTR(DigitalPinImpl);
// A simple digital pin. If we are compiling in an Arduino environment, then
// this class will bind to that. Otherwise it will fall back to the platform api.
// Note that this class does not support analog mode nor pullups/pulldowns.
class DigitalPin {
public:
enum Mode {
kInput = 0,
kOutput,
kInputPullup,
// kInputPulldown, Not implemented in Arduino.h
};
DigitalPin(int pin);
~DigitalPin();
DigitalPin(const DigitalPin &other);
DigitalPin &operator=(const DigitalPin &other);
DigitalPin(DigitalPin &&other) = delete;
void setPinMode(Mode mode);
bool high() const; // true if high, false if low
void write(bool is_high);
private:
DigitalPinImplPtr mImpl;
};
} // namespace fl

View File

@@ -0,0 +1,63 @@
#ifndef FASTLED_INTERNAL
#define FASTLED_INTERNAL
#endif
#include "FastLED.h"
#include "fastpin.h"
#include "fl/strstream.h"
#include "fl/warn.h"
#include "fl/assert.h"
#include "sensors/pir.h"
namespace fl {
namespace {
int g_counter = 0;
Str getButtonName(const char *button_name) {
if (button_name) {
return Str(button_name);
}
int count = g_counter++;
if (count == 0) {
return Str("PIR");
}
StrStream s;
s << "PirLowLevel " << g_counter++;
return s.str();
}
} // namespace
PirLowLevel::PirLowLevel(int pin): mPin(pin) {
mPin.setPinMode(DigitalPin::kInput);
}
bool PirLowLevel::detect() {
return mPin.high();
}
Pir::Pir(int pin, uint32_t latchMs, uint32_t risingTime,
uint32_t fallingTime, const char* button_name)
: mPir(pin), mRamp(risingTime, latchMs, fallingTime), mButton(getButtonName(button_name).c_str()) {
mButton.onChanged([this](UIButton&) {
this->mRamp.trigger(millis());
});
}
bool Pir::detect(uint32_t now) {
bool currentState = mPir.detect();
if (currentState && !mLastState) {
mRamp.trigger(now);
}
mLastState = currentState;
return mRamp.isActive(now);
}
uint8_t Pir::transition(uint32_t now) {
// ensure detect() logic runs so we trigger on edges
detect(now);
return mRamp.update8(now);
}
} // namespace fl

View File

@@ -0,0 +1,75 @@
#pragma once
#include "fl/stdint.h"
#include "digital_pin.h"
#include "fl/memory.h"
#include "fl/ui.h"
#include "fl/time_alpha.h"
#include "fl/namespace.h"
namespace fl {
// A passive infrared sensor common on amazon.
// For best results set the PIR to maximum sensitive and minimum delay time before retrigger.
// Instantiating this class will create a ui UIButton when
// compiling using the FastLED web compiler.
class PirLowLevel {
public:
PirLowLevel(int pin);
bool detect();
operator bool() { return detect(); }
private:
DigitalPin mPin;
};
// An passive infrared sensor that incorporates time to allow for latching and transitions fx. This is useful
// for detecting motion and the increasing the brightness in response to motion.
// Example:
// #define PIR_LATCH_MS 15000 // how long to keep the PIR sensor active after a trigger
// #define PIR_RISING_TIME 1000 // how long to fade in the PIR sensor
// #define PIR_FALLING_TIME 1000 // how long to fade out the PIR sensor
// Pir pir(PIN_PIR, PIR_LATCH_MS, PIR_RISING_TIME, PIR_FALLING_TIME);
// void loop() {
// uint8_t bri = pir.transition(millis());
// FastLED.setBrightness(bri * brightness.as<float>());
// }
class Pir {
public:
/// @param pin GPIO pin for PIR sensor
/// @param latchMs total active time (ms)
/// @param risingTime rampup duration (ms)
/// @param fallingTime rampdown duration (ms)
Pir(int pin,
uint32_t latchMs = 5000,
uint32_t risingTime = 1000,
uint32_t fallingTime = 1000,
const char* button_name = nullptr);
/// Returns true if the PIR is “latched on” (within latchMs of last trigger).
bool detect(uint32_t now);
/// Returns a 0255 ramp value:
/// • ramps 0→255 over risingTime
/// • holds 255 until latchMsfallingTime
/// • ramps 255→0 over fallingTime
/// Outside latch period returns 0.
uint8_t transition(uint32_t now);
/// Manually start the latch cycle (e.g. on startup)
void activate(uint32_t now) { mRamp.trigger(now); }
private:
PirLowLevel mPir;
TimeRamp mRamp;
bool mLastState = false;
UIButton mButton;
};
} // namespace fl