GithubHelp home page GithubHelp logo

bxparks / acebutton Goto Github PK

View Code? Open in Web Editor NEW
364.0 364.0 37.0 3.2 MB

An adjustable, compact, event-driven button library for Arduino that debounces and dispatches events to a user-defined event handler.

License: MIT License

C++ 98.27% C 0.98% Makefile 0.75%
arduino arduino-library button debounce-button

acebutton's People

Contributors

bxparks avatar salvagione avatar thijstriemstra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

acebutton's Issues

[Question] How to pass object method as an event handler?

Hi there!

Many thanks for this library! Feature-rich, great OOP design, and docs are awesome!

I'm trying to isolate all buttons' logic inside another "Facade" class (FrontPanel in my case). All buttons and their configs are instantiated as class properties. I have several private methods for handling buttons' events, with proper signature (as recommended for plain global functions here).

The question is: how to pass this private method as an event handler?

#include <AceButton.h>

using namespace ace_button;

class FrontPanel
{
  public:
    void init();

  private:
    ButtonConfig shiftButtonConfig;
    AceButton shiftButton;

    void handleShiftButtonEvent(AceButton* /* button */, uint8_t /* eventType */, uint8_t /* buttonState */);

    void initButtons();
    void onShiftButtonPressed();
    void onShiftButtonReleased();
};


// Event handler for ace_button::ButtonConfig
void FrontPanel::handleShiftButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t /* buttonState */) {
    switch (eventType) {
      case AceButton::kEventPressed:
        onShiftButtonPressed();
        break;

      case AceButton::kEventReleased:
        onShiftButtonReleased();
        break;
    }
}

void FrontPanel::initButtons() {
  // How to pass object method as an event handler here???
  shiftButtonConfig.setEventHandler(handleShiftButtonEvent);
}

Example Request

Hi There,

I love your library and it's interface.

I haven't written C++ in years, and I would love an analog button read example.

Roughly following the capacitive example, I think it has something to do with overriding ace_button::ButtonConfig::readButton. I have attempted it but I'm doing something wrong.

I think I'm doing all of the subclassing properly, but as I said I haven't written C++ in a loooong time.

Definitely feel free to close as won't-fix. Just thought I'd throw it out as something that might be useful to users.

Thanks!

Fetching button-state after reboot

Hey,
first I'd like to thank you for this wonderful piece of code.

In some places (doc, sourcecode) you mention the intention of not trigger on events that already occured before the button is created. What would be your proposal for getting these events, e.g. for behaviour like
"if you press the Start button and switch the device on, you will get into a special configuration menu"

Currently I do it the following way:

void setup() {
...
if (!once_after_reboot)
{
    if (button.getLastButtonState()) 
    { 
       showSpecialSetup();
    }
    once_after_reboot = true;
}
...
}

Two button both pressed (sample LongPress)

Hi, congratulations on a very nice project.

What I want to ask is, is it possible to have a feature that I think would be very nice if added?

I need to use the long-press method of two keys to turn a device off and on safely.

I am able to check and catch the isPressedRaw() property on both keys in the kEventLongPressed event. But the event throwing occurs on the second depressed key.

I wonder if it's possible to do two-key presses with the existing library?

code;

#include <AceButton.h>
using namespace ace_button;

#define PIN_BUTTON1 6
#define PIN_BUTTON2 7

AceButton button1(PIN_BUTTON1);
AceButton button2(PIN_BUTTON2);

void handleEvent(AceButton *, uint8_t, uint8_t);

void setup()
{
  pinMode(PIN_BUTTON1, INPUT_PULLUP);
  pinMode(PIN_BUTTON2, INPUT_PULLUP);

  ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig();
  buttonConfig->setEventHandler(handleEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  buttonConfig->setLongPressDelay(3000);
}

void handleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState)
{
  switch (eventType)
  {
  case AceButton::kEventLongPressed:
    if (button1.isPressedRaw() && button2.isPressedRaw())
    {
      Serial.println("LongPressed: Both");
    }
    break;
  }
}

terminal;

LongPressed: Both
LongPressed: Both

Capacitive button returns release while button is pressed

I am using the capacitive button as a switch to control a smart light. The problem is that the released occurs while not expected. I don't understand why and whether this is AceButton related or CapacitiveSensor related.

Hardware

  • Wemos D1 mini
  • D2 (GPIO 4) connected to wire and metal strip
  • D1 (GPIO 5) connected with resistor (680k) to D2

Problem
When touching and holding the metal strip I expect that the buttonevent is pressed. To see what event occurs, I addds a Serial.Println with millis() and the eventType. The monitor shows:

368761 Pressed
368792 Clicked
368792 Released
368821 Pressed
368852 DoubleClicked
368852 Released
368881 Pressed
368912 Clicked
368913 Released
368941 Pressed
368972 DoubleClicked
368972 Released
369001 Pressed
369032 Clicked
369032 Released
369062 Pressed
369094 DoubleClicked
369094 Released

So while I expected to only see a pressed, I also see a released, and because of that also clicked and doubleClicked.

I am not sure why this occurs:

  • bad hardware setup
  • wrong capacitiveSensor config
  • something in the code

Code
The code is used to function as a smart (led) light, which is controlled by diyHue https://diyhue.github.io/. This light has a touch button to switch the light on or off.

The full code is below (based upon https://github.com/diyhue/Lights/tree/master/Arduino/Generic_SK6812_Strip, by Marius Motea).

// diyHue switch 
// AceButton with capacitiveSensor
// ESP 8266 (Wemos D1 min)
// WS2812 RGBW led on D4

// diyHue must be installed (on a RPi for example)
// after first start, ESP functions as a WIFI access point (AP). Connect to the access point (for example with tablet or phone) and use your browser to go to 192.168.4.1 
// to set wifi credentials


#include <FS.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#include <NeoPixelBus.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>

#include <CapacitiveSensor.h>
#include <AceButton.h>
using namespace ace_button;

/**
 * A subclass of ButtonConfig that allows a CapacitiveSensor to emulate a
 * mechanical switch connected to a pull-up resistor on the input pin. A "touch"
 * sends a LOW signal, just like a mechnical switch.
 */
class CapacitiveConfig: public ButtonConfig {
  public:
    CapacitiveConfig(CapacitiveSensor& sensor):
      mSensor(sensor) {}

  protected:
    // Number of iterations to sample the capacitive switch. Higher number
    // provides better smoothing but increases the time taken for a single read.
    static const uint8_t kSamples = 30;

    // The threshold value which is considered to be a "touch" on the switch.
    static const long kTouchThreshold = 100;

    int readButton(uint8_t /*pin*/) override {
      long total =  mSensor.capacitiveSensor(kSamples);
      return (total > kTouchThreshold) ? LOW : HIGH;
    }

  private:
    CapacitiveSensor& mSensor;
};

// Timeout for a single read of the capacitive switch.
static const unsigned long TIMEOUT_MILLIS = 10;

// I used a 1M resistor between pins 4 (send) & metal plate, and a 1K resistor
// between the plate and pin 5 (receive). Try adjusting the
// CapacitiveConfig::kTouchThreshold value for other resistor values.
CapacitiveSensor capSensor(5, 4);

CapacitiveConfig buttonConfig(capSensor);
AceButton button(&buttonConfig);


// All variables must be changed from light web interface. Change them here only if you need different defaults

IPAddress address ( 192,  168,   0,  95); // choose an unique IP Adress
IPAddress gateway ( 192,  168,   0,   1); // Router IP
IPAddress submask(255, 255, 255,   0);

struct state {
  uint8_t colors[4], bri = 100, sat = 254, colorMode = 2;
  bool lightState;
  int ct = 200, hue;
  float stepLevel[4], currentColors[4], x, y;
};

//core

#define entertainmentTimeout 1500 // millis

state lights[10];
bool inTransition, entertainmentRun, useDhcp = true;
byte mac[6], packetBuffer[46];
unsigned long lastEPMillis;

//settings
char *lightName = "Hue SK6812 strip";
uint8_t scene, startup, onPin = 4, offPin = 5;
bool hwSwitch = false;

uint8_t lightsCount = 3;
uint16_t pixelCount = 60, lightLedsCount;
uint8_t transitionLeds = 6; // must be even number



ESP8266WebServer server(80);
WiFiUDP Udp;
ESP8266HTTPUpdateServer httpUpdateServer;

RgbwColor red = RgbwColor(255, 0, 0, 0);
RgbwColor green = RgbwColor(0, 255, 0, 0);
RgbwColor white = RgbwColor(255);
RgbwColor black = RgbwColor(0);

NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart1Ws2812xMethod>* strip = NULL;

void handleEvent(AceButton* /* button */, uint8_t eventType,
    uint8_t /* buttonState */) {
  switch (eventType) {
    case AceButton::kEventPressed:
      Serial.print(millis());
      Serial.println(F(" Pressed"));
      break;
    case AceButton::kEventReleased:
      Serial.print(millis());
      Serial.println(F(" Released"));
      break;
    case AceButton::kEventClicked:
      Serial.print(millis());
      Serial.println(F(" Clicked"));
      for (int light = 0; light < lightsCount; light++) {
        lights[light].lightState = !lights[light].lightState; //change light from ON to OFF or from OFF to ON
      }
      break;
    case AceButton::kEventDoubleClicked:
      Serial.print(millis());
      Serial.println(F(" DoubleClicked"));
      //apply_scene(1);
      break;
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("START");
  delay(1000);

  //Serial.println("mounting FS...");

  if (!SPIFFS.begin()) {
    //Serial.println("Failed to mount file system");
    return;
  }

  if (!loadConfig()) {
    //Serial.println("Failed to load config");
  } else {
    ////Serial.println("Config loaded");
  }

  lightLedsCount = pixelCount / lightsCount;
  ChangeNeoPixels(pixelCount);


  if (startup == 1) {
    for (uint8_t i = 0; i < lightsCount; i++) {
      lights[i].lightState = true;
    }
  }
  if (startup == 0) {
    restoreState();
  } else {
    apply_scene(scene);
  }
  for (uint8_t i = 0; i < lightsCount; i++) {
    processLightdata(i, 4);
  }
  if (lights[0].lightState) {
    for (uint8_t i = 0; i < 200; i++) {
      lightEngine();
    }
  }
  
  WiFi.mode(WIFI_STA);
  WiFiManager wifiManager;

  if (!useDhcp) {
    wifiManager.setSTAStaticIPConfig(address, gateway, submask);
  }

  if (!wifiManager.autoConnect(lightName)) {
    delay(3000);
    ESP.reset();
    delay(5000);
  }

  if (useDhcp) {
    address = WiFi.localIP();
    gateway = WiFi.gatewayIP();
    submask = WiFi.subnetMask();
  }


  if (! lights[0].lightState) {
    infoLight(white);
    while (WiFi.status() != WL_CONNECTED) {
      infoLight(red);
    }
    // Show that we are connected
    infoLight(green);

  }

  WiFi.macAddress(mac);

  httpUpdateServer.setup(&server);

  Udp.begin(2100);

  if (hwSwitch == true) {
    pinMode(onPin, INPUT);
    pinMode(offPin, INPUT);
  }

  server.on("/state", HTTP_PUT, []() {
    bool stateSave = false;
    DynamicJsonDocument root(1024);
    DeserializationError error = deserializeJson(root, server.arg("plain"));
    if (error) {
      server.send(404, "text/plain", "FAIL. " + server.arg("plain"));
    } else {
      for (JsonPair state : root.as<JsonObject>()) {
        const char* key = state.key().c_str();
        int light = atoi(key) - 1;
        JsonObject values = state.value();
        int transitiontime = 4;

        if (values.containsKey("xy")) {
          lights[light].x = values["xy"][0];
          lights[light].y = values["xy"][1];
          lights[light].colorMode = 1;
        } else if (values.containsKey("ct")) {
          lights[light].ct = values["ct"];
          lights[light].colorMode = 2;
        } else {
          if (values.containsKey("hue")) {
            lights[light].hue = values["hue"];
            lights[light].colorMode = 3;
          }
          if (values.containsKey("sat")) {
            lights[light].sat = values["sat"];
            lights[light].colorMode = 3;
          }
        }

        if (values.containsKey("on")) {
          if (values["on"]) {
            lights[light].lightState = true;
          } else {
            lights[light].lightState = false;
          }
          if (startup == 0) {
            stateSave = true;
          }
        }

        if (values.containsKey("bri")) {
          lights[light].bri = values["bri"];
        }

        if (values.containsKey("bri_inc")) {
          lights[light].bri += (int) values["bri_inc"];
          if (lights[light].bri > 255) lights[light].bri = 255;
          else if (lights[light].bri < 1) lights[light].bri = 1;
        }

        if (values.containsKey("transitiontime")) {
          transitiontime = values["transitiontime"];
        }

        if (values.containsKey("alert") && values["alert"] == "select") {
          if (lights[light].lightState) {
            lights[light].currentColors[0] = 0; lights[light].currentColors[1] = 0; lights[light].currentColors[2] = 0; lights[light].currentColors[3] = 0;
          } else {
            lights[light].currentColors[2] = 126; lights[light].currentColors[3] = 126;
          }
        }
        processLightdata(light, transitiontime);
      }
      String output;
      serializeJson(root, output);
      server.send(200, "text/plain", output);
      if (stateSave) {
        saveState();
      }
    }
  });

  server.on("/state", HTTP_GET, []() {
    uint8_t light = server.arg("light").toInt() - 1;
    DynamicJsonDocument root(1024);
    root["on"] = lights[light].lightState;
    root["bri"] = lights[light].bri;
    JsonArray xy = root.createNestedArray("xy");
    xy.add(lights[light].x);
    xy.add(lights[light].y);
    root["ct"] = lights[light].ct;
    root["hue"] = lights[light].hue;
    root["sat"] = lights[light].sat;
    if (lights[light].colorMode == 1)
      root["colormode"] = "xy";
    else if (lights[light].colorMode == 2)
      root["colormode"] = "ct";
    else if (lights[light].colorMode == 3)
      root["colormode"] = "hs";
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });

  server.on("/detect", []() {
    char macString[32] = {0};
    sprintf(macString, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    DynamicJsonDocument root(1024);
    root["name"] = lightName;
    root["lights"] = lightsCount;
    root["protocol"] = "native_multi";
    root["modelid"] = "LST002";
    root["type"] = "sk6812_strip";
    root["mac"] = String(macString);
    root["version"] = 2.0;
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });

  server.on("/config", []() {
    DynamicJsonDocument root(1024);
    root["name"] = lightName;
    root["scene"] = scene;
    root["startup"] = startup;
    root["hw"] = hwSwitch;
    root["on"] = onPin;
    root["off"] = offPin;
    root["hwswitch"] = (int)hwSwitch;
    root["lightscount"] = lightsCount;
    root["pixelcount"] = pixelCount;
    root["transitionleds"] = transitionLeds;
    root["dhcp"] = (int)useDhcp;
    root["addr"] = (String)address[0] + "." + (String)address[1] + "." + (String)address[2] + "." + (String)address[3];
    root["gw"] = (String)gateway[0] + "." + (String)gateway[1] + "." + (String)gateway[2] + "." + (String)gateway[3];
    root["sm"] = (String)submask[0] + "." + (String)submask[1] + "." + (String)submask[2] + "." + (String)submask[3];
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });

  server.on("/", []() {
    if (server.hasArg("scene")) {
      server.arg("name").toCharArray(lightName, server.arg("name").length() + 1);
      startup = server.arg("startup").toInt();
      scene = server.arg("scene").toInt();
      lightsCount = server.arg("lightscount").toInt();
      pixelCount = server.arg("pixelcount").toInt();
      transitionLeds = server.arg("transitionleds").toInt();
      hwSwitch = server.arg("hwswitch").toInt();
      onPin = server.arg("on").toInt();
      offPin = server.arg("off").toInt();
      saveConfig();
    } else if (server.hasArg("dhcp")) {
      useDhcp = server.arg("dhcp").toInt();
      address.fromString(server.arg("addr"));
      gateway.fromString(server.arg("gw"));
      submask.fromString(server.arg("sm"));
      saveConfig();
    }

      String htmlContent = "<!DOCTYPE html> <html> <head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> <title>" + String(lightName) + " - DiyHue</title> <link rel=\"icon\" type=\"image/png\" href=\"https://diyhue.org/wp-content/uploads/2019/11/cropped-Zeichenfl%C3%A4che-4-1-32x32.png\" sizes=\"32x32\"> <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"> <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css\"> <link rel=\"stylesheet\" href=\"https://diyhue.org/cdn/nouislider.css\" /> </head> <body> <div class=\"wrapper\"> <nav class=\"nav-extended row\" style=\"background-color: #26a69a !important;\"> <div class=\"nav-wrapper col s12\"> <a href=\"#\" class=\"brand-logo\">DiyHue</a> <ul id=\"nav-mobile\" class=\"right hide-on-med-and-down\" style=\"position: relative;z-index: 10;\"> <li><a target=\"_blank\" href=\"https://github.com/diyhue\"><i class=\"material-icons left\">language</i>GitHub</a></li> <li><a target=\"_blank\" href=\"https://diyhue.readthedocs.io/en/latest/\"><i class=\"material-icons left\">description</i>Documentation</a></li> <li><a target=\"_blank\" href=\"https://diyhue.slack.com/\"><i class=\"material-icons left\">question_answer</i>Slack channel</a></li> </ul> </div> <div class=\"nav-content\"> <ul class=\"tabs tabs-transparent\"> <li class=\"tab\" title=\"#home\"><a class=\"active\" href=\"#home\">Home</a></li> <li class=\"tab\" title=\"#preferences\"><a href=\"#preferences\">Preferences</a></li> <li class=\"tab\" title=\"#network\"><a href=\"#network\">Network settings</a></li> <li class=\"tab\" title=\"/update\"><a href=\"/update\">Updater</a></li> </ul> </div> </nav> <ul class=\"sidenav\" id=\"mobile-demo\"> <li><a target=\"_blank\" href=\"https://github.com/diyhue\">GitHub</a></li> <li><a target=\"_blank\" href=\"https://diyhue.readthedocs.io/en/latest/\">Documentation</a></li> <li><a target=\"_blank\" href=\"https://diyhue.slack.com/\">Slack channel</a></li> </ul> <div class=\"container\"> <div class=\"section\"> <div id=\"home\" class=\"col s12\"> <form> <input type=\"hidden\" name=\"section\" value=\"1\"> <div class=\"row\"> <div class=\"col s10\"> <label for=\"power\">Power</label> <div id=\"power\" class=\"switch section\"> <label> Off <input type=\"checkbox\" name=\"pow\" id=\"pow\" value=\"1\"> <span class=\"lever\"></span> On </label> </div> </div> </div> <div class=\"row\"> <div class=\"col s12 m10\"> <label for=\"bri\">Brightness</label> <input type=\"text\" id=\"bri\" class=\"js-range-slider\" name=\"bri\" value=\"\" /> </div> </div> <div class=\"row\"> <div class=\"col s12\"> <label for=\"hue\">Color</label> <div> <canvas id=\"hue\" width=\"320px\" height=\"320px\" style=\"border:1px solid #d3d3d3;\"></canvas> </div> </div> </div> <div class=\"row\"> <div class=\"col s12\"> <label for=\"ct\">Color Temp</label> <div> <canvas id=\"ct\" width=\"320px\" height=\"50px\" style=\"border:1px solid #d3d3d3;\"></canvas> </div> </div> </div> </form> </div> <div id=\"preferences\" class=\"col s12\"> <form method=\"POST\" action=\"/\"> <input type=\"hidden\" name=\"section\" value=\"1\"> <div class=\"row\"> <div class=\"col s12\"> <label for=\"name\">Light Name</label> <input type=\"text\" id=\"name\" name=\"name\"> </div> </div> <div class=\"row\"> <div class=\"col s12 m6\"> <label for=\"startup\">Default Power:</label> <select name=\"startup\" id=\"startup\"> <option value=\"0\">Last State</option> <option value=\"1\">On</option> <option value=\"2\">Off</option> </select> </div> </div> <div class=\"row\"> <div class=\"col s12 m6\"> <label for=\"scene\">Default Scene:</label> <select name=\"scene\" id=\"scene\"> <option value=\"0\">Relax</option> <option value=\"1\">Read</option> <option value=\"2\">Concentrate</option> <option value=\"3\">Energize</option> <option value=\"4\">Bright</option> <option value=\"5\">Dimmed</option> <option value=\"6\">Nightlight</option> <option value=\"7\">Savanna sunset</option> <option value=\"8\">Tropical twilight</option> <option value=\"9\">Arctic aurora</option> <option value=\"10\">Spring blossom</option> </select> </div> </div> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"pixelcount\" class=\"col-form-label\">Pixel count</label> <input type=\"number\" id=\"pixelcount\" name=\"pixelcount\"> </div> </div> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"lightscount\" class=\"col-form-label\">Lights count</label> <input type=\"number\" id=\"lightscount\" name=\"lightscount\"> </div> </div> <label class=\"form-label\">Light division</label> </br> <label>Available Pixels:</label> <label class=\"availablepixels\"><b>null</b></label> <div class=\"row dividedLights\"> </div> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"transitionleds\">Transition leds:</label> <select name=\"transitionleds\" id=\"transitionleds\"> <option value=\"0\">0</option> <option value=\"2\">2</option> <option value=\"4\">4</option> <option value=\"6\">6</option> <option value=\"8\">8</option> <option value=\"10\">10</option> </select> </div> </div> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"rpct\" class=\"form-label\">Red multiplier</label> <input type=\"number\" id=\"rpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"rpct\" value=\"\" /> </div> <div class=\"col s4 m3\"> <label for=\"gpct\" class=\"form-label\">Green multiplier</label> <input type=\"number\" id=\"gpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"gpct\" value=\"\" /> </div> <div class=\"col s4 m3\"> <label for=\"bpct\" class=\"form-label\">Blue multiplier</label> <input type=\"number\" id=\"bpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"bpct\" value=\"\" /> </div> </div> <div class=\"row\"> <label class=\"control-label col s10\">HW buttons:</label> <div class=\"col s10\"> <div class=\"switch section\"> <label> Disable <input type=\"checkbox\" name=\"hwswitch\" id=\"hwswitch\" value=\"1\"> <span class=\"lever\"></span> Enable </label> </div> </div> </div> <div class=\"switchable\"> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"on\">On Pin</label> <input type=\"number\" id=\"on\" name=\"on\"> </div> <div class=\"col s4 m3\"> <label for=\"off\">Off Pin</label> <input type=\"number\" id=\"off\" name=\"off\"> </div> </div> </div> <div class=\"row\"> <div class=\"col s10\"> <button type=\"submit\" class=\"waves-effect waves-light btn teal\">Save</button> <!--<button type=\"submit\" name=\"reboot\" class=\"waves-effect waves-light btn grey lighten-1\">Reboot</button>--> </div> </div> </form> </div> <div id=\"network\" class=\"col s12\"> <form method=\"POST\" action=\"/\"> <input type=\"hidden\" name=\"section\" value=\"2\"> <div class=\"row\"> <div class=\"col s12\"> <label class=\"control-label\">Manual IP assignment:</label> <div class=\"switch section\"> <label> Disable <input type=\"checkbox\" name=\"disdhcp\" id=\"disdhcp\" value=\"0\"> <span class=\"lever\"></span> Enable </label> </div> </div> </div> <div class=\"switchable\"> <div class=\"row\"> <div class=\"col s12 m3\"> <label for=\"addr\">Ip</label> <input type=\"text\" id=\"addr\" name=\"addr\"> </div> <div class=\"col s12 m3\"> <label for=\"sm\">Submask</label> <input type=\"text\" id=\"sm\" name=\"sm\"> </div> <div class=\"col s12 m3\"> <label for=\"gw\">Gateway</label> <input type=\"text\" id=\"gw\" name=\"gw\"> </div> </div> </div> <div class=\"row\"> <div class=\"col s10\"> <button type=\"submit\" class=\"waves-effect waves-light btn teal\">Save</button> <!--<button type=\"submit\" name=\"reboot\" class=\"waves-effect waves-light btn grey lighten-1\">Reboot</button>--> <!--<button type=\"submit\" name=\"reboot\" class=\"waves-effect waves-light btn grey lighten-1\">Reboot</button>--> </div> </div> </form> </div> </div> </div> </div> <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script> <script src=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js\"></script> <script src=\"https://diyhue.org/cdn/nouislider.js\"></script> <script src=\"https://diyhue.org/cdn/diyhue.js\"></script> </body> </html>";
      server.send(200, "text/html", htmlContent);
    if (server.args()) {
      delay(100);
      ESP.reset();
    }

  });

  server.on("/reset", []() {
    server.send(200, "text/html", "reset");
    delay(100);
    ESP.reset();
  });

  server.onNotFound(handleNotFound);

  server.begin();

  // Set the timeout to 10 millisecond so that AceButton::check()
  // can have about 4-5 iterations during the 50 millisecond debouncing time.
  capSensor.set_CS_Timeout_Millis(TIMEOUT_MILLIS);

  // Configure the button using CapacitiveConfig.
  buttonConfig.setFeature(ButtonConfig::kFeatureClick);
  buttonConfig.setFeature(ButtonConfig::kFeatureDoubleClick);
  buttonConfig.setEventHandler(handleEvent);

}



void loop() {
  server.handleClient();
  button.check();
  if (!entertainmentRun) {
    lightEngine();
  } else {
    if ((millis() - lastEPMillis) >= entertainmentTimeout) {
      entertainmentRun = false;
      for (uint8_t i = 0; i < lightsCount; i++) {
        processLightdata(i, 4);
      }
    }
  }
  entertainment();

}

void convertHue(uint8_t light)
{
  lights[light].colors[3] = 0;

  double      hh, p, q, t, ff, s, v;
  long        i;

  s = lights[light].sat / 255.0;
  v = lights[light].bri / 255.0;

  if (s <= 0.0) {      // < is bogus, just shuts up warnings
    lights[light].colors[0] = v;
    lights[light].colors[1] = v;
    lights[light].colors[2] = v;
    return;
  }
  hh = lights[light].hue;
  if (hh >= 65535.0) hh = 0.0;
  hh /= 11850, 0;
  i = (long)hh;
  ff = hh - i;
  p = v * (1.0 - s);
  q = v * (1.0 - (s * ff));
  t = v * (1.0 - (s * (1.0 - ff)));

  switch (i) {
    case 0:
      lights[light].colors[0] = v * 255.0;
      lights[light].colors[1] = t * 255.0;
      lights[light].colors[2] = p * 255.0;
      break;
    case 1:
      lights[light].colors[0] = q * 255.0;
      lights[light].colors[1] = v * 255.0;
      lights[light].colors[2] = p * 255.0;
      break;
    case 2:
      lights[light].colors[0] = p * 255.0;
      lights[light].colors[1] = v * 255.0;
      lights[light].colors[2] = t * 255.0;
      break;

    case 3:
      lights[light].colors[0] = p * 255.0;
      lights[light].colors[1] = q * 255.0;
      lights[light].colors[2] = v * 255.0;
      break;
    case 4:
      lights[light].colors[0] = t * 255.0;
      lights[light].colors[1] = p * 255.0;
      lights[light].colors[2] = v * 255.0;
      break;
    case 5:
    default:
      lights[light].colors[0] = v * 255.0;
      lights[light].colors[1] = p * 255.0;
      lights[light].colors[2] = q * 255.0;
      break;
  }

}

void convertXy(uint8_t light)
{
  lights[light].colors[3] = 0;

  int optimal_bri = lights[light].bri;
  if (optimal_bri < 5) {
    optimal_bri = 5;
  }
  float Y = lights[light].y;
  float X = lights[light].x;
  float Z = 1.0f - lights[light].x - lights[light].y;

  // sRGB D65 conversion
  float r =  X * 3.2406f - Y * 1.5372f - Z * 0.4986f;
  float g = -X * 0.9689f + Y * 1.8758f + Z * 0.0415f;
  float b =  X * 0.0557f - Y * 0.2040f + Z * 1.0570f;


  // Apply gamma correction
  r = r <= 0.04045f ? r / 12.92f : pow((r + 0.055f) / (1.0f + 0.055f), 2.4f);
  g = g <= 0.04045f ? g / 12.92f : pow((g + 0.055f) / (1.0f + 0.055f), 2.4f);
  b = b <= 0.04045f ? b / 12.92f : pow((b + 0.055f) / (1.0f + 0.055f), 2.4f);

  float maxv = 0;// calc the maximum value of r g and b
  if (r > maxv) maxv = r;
  if (g > maxv) maxv = g;
  if (b > maxv) maxv = b;

  if (maxv > 0) {// only if maximum value is greater than zero, otherwise there would be division by zero
    r /= maxv;   // scale to maximum so the brightest light is always 1.0
    g /= maxv;
    b /= maxv;
  }

  r = r < 0 ? 0 : r;
  g = g < 0 ? 0 : g;
  b = b < 0 ? 0 : b;

  lights[light].colors[0] = (int) (r * optimal_bri); lights[light].colors[1] = (int) (g * optimal_bri); lights[light].colors[2] = (int) (b * optimal_bri);
}

void convertCt(uint8_t light) {
  lights[light].colors[3] = lights[light].bri;
  int hectemp = 10000 / lights[light].ct;
  int r, g, b;
  if (hectemp <= 66) {
    r = 255;
    g = 99.4708025861 * log(hectemp) - 161.1195681661;
    b = hectemp <= 19 ? 0 : (138.5177312231 * log(hectemp - 10) - 305.0447927307);
  } else {
    r = 329.698727446 * pow(hectemp - 60, -0.1332047592);
    g = 288.1221695283 * pow(hectemp - 60, -0.0755148492);
    b = 255;
  }
  r = r > 255 ? 255 : r;
  g = g > 255 ? 255 : g;
  b = b > 255 ? 255 : b;
  lights[light].colors[0] = r * (lights[light].bri / 255.0f); lights[light].colors[1] = g * (lights[light].bri / 255.0f); lights[light].colors[2] = b * (lights[light].bri / 255.0f);
}

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void infoLight(RgbwColor color) {
  // Flash the strip in the selected color. White = booted, green = WLAN connected, red = WLAN could not connect
  for (int i = 0; i < pixelCount; i++)
  {
    strip->SetPixelColor(i, color);
    strip->Show();
    delay(10);
    strip->SetPixelColor(i, black);
    strip->Show();
  }
}


void apply_scene(uint8_t new_scene) {
  for (uint8_t light = 0; light < lightsCount; light++) {
    if ( new_scene == 1) {
      lights[light].bri = 254; lights[light].ct = 346; lights[light].colorMode = 2; convertCt(light);
    } else if ( new_scene == 2) {
      lights[light].bri = 254; lights[light].ct = 233; lights[light].colorMode = 2; convertCt(light);
    }  else if ( new_scene == 3) {
      lights[light].bri = 254; lights[light].ct = 156; lights[light].colorMode = 2; convertCt(light);
    }  else if ( new_scene == 4) {
      lights[light].bri = 77; lights[light].ct = 367; lights[light].colorMode = 2; convertCt(light);
    }  else if ( new_scene == 5) {
      lights[light].bri = 254; lights[light].ct = 447; lights[light].colorMode = 2; convertCt(light);
    }  else if ( new_scene == 6) {
      lights[light].bri = 1; lights[light].x = 0.561; lights[light].y = 0.4042; lights[light].colorMode = 1; convertXy(light);
    }  else if ( new_scene == 7) {
      lights[light].bri = 203; lights[light].x = 0.380328; lights[light].y = 0.39986; lights[light].colorMode = 1; convertXy(light);
    }  else if ( new_scene == 8) {
      lights[light].bri = 112; lights[light].x = 0.359168; lights[light].y = 0.28807; lights[light].colorMode = 1; convertXy(light);
    }  else if ( new_scene == 9) {
      lights[light].bri = 142; lights[light].x = 0.267102; lights[light].y = 0.23755; lights[light].colorMode = 1; convertXy(light);
    }  else if ( new_scene == 10) {
      lights[light].bri = 216; lights[light].x = 0.393209; lights[light].y = 0.29961; lights[light].colorMode = 1; convertXy(light);
    } else {
      lights[light].bri = 144; lights[light].ct = 447; lights[light].colorMode = 2; convertCt(light);
    }
  }
}

void processLightdata(uint8_t light, float transitiontime) {
  transitiontime *= 17 - (pixelCount / 40); //every extra led add a small delay that need to be counted
  if (lights[light].colorMode == 1 && lights[light].lightState == true) {
    convertXy(light);
  } else if (lights[light].colorMode == 2 && lights[light].lightState == true) {
    convertCt(light);
  } else if (lights[light].colorMode == 3 && lights[light].lightState == true) {
    convertHue(light);
  }
  for (uint8_t i = 0; i < 4; i++) {
    if (lights[light].lightState) {
      lights[light].stepLevel[i] = ((float)lights[light].colors[i] - lights[light].currentColors[i]) / transitiontime;
    } else {
      lights[light].stepLevel[i] = lights[light].currentColors[i] / transitiontime;
    }
  }
}

RgbwColor blending(float left[4], float right[4], uint8_t pixel) {
  uint8_t result[4];
  for (uint8_t i = 0; i < 4; i++) {
    float percent = (float) pixel / (float) (transitionLeds + 1);
    result[i] = (left[i] * (1.0f - percent) + right[i] * percent) / 2;
  }
  return RgbwColor((uint8_t)result[0], (uint8_t)result[1], (uint8_t)result[2], (uint8_t)result[3]);
}

RgbwColor convInt(float color[4]) {
  return RgbwColor((uint8_t)color[0], (uint8_t)color[1], (uint8_t)color[2], (uint8_t)color[3]);
}

RgbwColor convFloat(float color[4]) {
  return RgbwColor((uint8_t)color[0], (uint8_t)color[1], (uint8_t)color[2], (uint8_t)color[3]);
}

void lightEngine() {
  for (int light = 0; light < lightsCount; light++) {
    if (lights[light].lightState) {
      if (lights[light].colors[0] != lights[light].currentColors[0] || lights[light].colors[1] != lights[light].currentColors[1] || lights[light].colors[2] != lights[light].currentColors[2]  || lights[light].colors[3] != lights[light].currentColors[3]) {
        inTransition = true;
        for (uint8_t k = 0; k < 4; k++) {
          if (lights[light].colors[k] != lights[light].currentColors[k]) lights[light].currentColors[k] += lights[light].stepLevel[k];
          if ((lights[light].stepLevel[k] > 0.0 && lights[light].currentColors[k] > lights[light].colors[k]) || (lights[light].stepLevel[k] < 0.0 && lights[light].currentColors[k] < lights[light].colors[k])) lights[light].currentColors[k] = lights[light].colors[k];
        }
        if (lightsCount > 1) {
          if (light == 0) {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds / 2; pixel++) {
              if (pixel < lightLedsCount - transitionLeds / 2) {
                strip->SetPixelColor(pixel, convFloat(lights[light].currentColors));
              } else {
                strip->SetPixelColor(pixel, blending(lights[0].currentColors, lights[1].currentColors, pixel + 1 - (lightLedsCount - transitionLeds / 2 )));
              }
            }
          } else if (light == lightsCount - 1) {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds / 2 ; pixel++) {
              if (pixel < transitionLeds) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light, blending( lights[light - 1].currentColors, lights[light].currentColors, pixel + 1));
              } else {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light, convFloat(lights[light].currentColors));
              }
            }
          } else {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds; pixel++) {
              if (pixel < transitionLeds) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light,  blending( lights[light - 1].currentColors, lights[light].currentColors, pixel + 1));
              } else if (pixel > lightLedsCount - 1) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light,  blending( lights[light].currentColors, lights[light + 1].currentColors, pixel + 1 - lightLedsCount));
              } else  {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light, convFloat(lights[light].currentColors));
              }
            }
          }
        } else {
          strip->ClearTo(convFloat(lights[light].currentColors), 0, pixelCount - 1);
        }
        strip->Show();
      }
    } else {
      if (lights[light].currentColors[0] != 0 || lights[light].currentColors[1] != 0 || lights[light].currentColors[2] != 0  || lights[light].currentColors[3] != 0) {
        inTransition = true;
        for (uint8_t k = 0; k < 4; k++) {
          if (lights[light].currentColors[k] != 0) lights[light].currentColors[k] -= lights[light].stepLevel[k];
          if (lights[light].currentColors[k] < 0) lights[light].currentColors[k] = 0;
        }
        if (lightsCount > 1) {
          if (light == 0) {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds / 2; pixel++) {
              if (pixel < lightLedsCount - transitionLeds / 2) {
                strip->SetPixelColor(pixel, convFloat(lights[light].currentColors));
              } else {
                strip->SetPixelColor(pixel,  blending( lights[light].currentColors, lights[light + 1].currentColors, pixel + 1 - (lightLedsCount - transitionLeds / 2 )));
              }
            }
          } else if (light == lightsCount - 1) {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds / 2 ; pixel++) {
              if (pixel < transitionLeds) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light,  blending( lights[light - 1].currentColors, lights[light].currentColors, pixel + 1));
              } else {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light, convFloat(lights[light].currentColors));
              }
            }
          } else {
            for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds; pixel++) {
              if (pixel < transitionLeds) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light,  blending( lights[light - 1].currentColors, lights[light].currentColors, pixel + 1));
              } else if (pixel > lightLedsCount - 1) {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light,  blending( lights[light].currentColors, lights[light + 1].currentColors, pixel + 1 - lightLedsCount));
              } else  {
                strip->SetPixelColor(pixel - transitionLeds / 2 + lightLedsCount * light, convFloat(lights[light].currentColors));
              }
            }
          }
        } else {
          strip->ClearTo(convFloat(lights[light].currentColors), 0, pixelCount - 1);
        }
        strip->Show();
      }
    }
  }
  if (inTransition) {
    delay(6);
    inTransition = false;
  } 
    //read switch "ON"
    else if (hwSwitch == true) { 
    if (digitalRead(onPin) == HIGH) {
      int i = 0;
      while (digitalRead(onPin) == HIGH && i < 30) {
        delay(20); //20*30=600ms
        i++;
      }
    //end of read switch "ON"
    //i<30 means short press
    //i=30 means long press
      for (int light = 0; light < lightsCount; light++) {
        if (i < 30) { 
          // there was a short press
          lights[light].lightState = true;
        }
        else {
          // there was a long press
          lights[light].bri += 56;
          if (lights[light].bri > 255) {
            // don't increase the brightness more then maximum value
            lights[light].bri = 255;
          }
        }
      }
    } 
    //read switch "OFF"
    else if (digitalRead(offPin) == HIGH) {
      int i = 0;
      while (digitalRead(offPin) == HIGH && i < 30) {
        delay(20);
        i++;
      }
    //end of read switch "OFF"
    //i<30 means short press
    //i=30 means long press
        for (int light = 0; light < lightsCount; light++) {
        if (i < 30) {
          // there was a short press
          lights[light].lightState = false;
        }
        else {
          // there was a long press
          lights[light].bri -= 56;
          if (lights[light].bri < 1) {
            // don't decrease the brightness less than minimum value.
            lights[light].bri = 1;
          }
        }
      }
    }
  }
}

void saveState() {
  DynamicJsonDocument json(1024);
  for (uint8_t i = 0; i < lightsCount; i++) {
    JsonObject light = json.createNestedObject((String)i);
    light["on"] = lights[i].lightState;
    light["bri"] = lights[i].bri;
    if (lights[i].colorMode == 1) {
      light["x"] = lights[i].x;
      light["y"] = lights[i].y;
    } else if (lights[i].colorMode == 2) {
      light["ct"] = lights[i].ct;
    } else if (lights[i].colorMode == 3) {
      light["hue"] = lights[i].hue;
      light["sat"] = lights[i].sat;
    }
  }
  File stateFile = SPIFFS.open("/state.json", "w");
  serializeJson(json, stateFile);

}


void restoreState() {
  File stateFile = SPIFFS.open("/state.json", "r");
  if (!stateFile) {
    saveState();
    return;
  }

  DynamicJsonDocument json(1024);
  DeserializationError error = deserializeJson(json, stateFile.readString());
  if (error) {
    //Serial.println("Failed to parse config file");
    return;
  }
  for (JsonPair state : json.as<JsonObject>()) {
    const char* key = state.key().c_str();
    int lightId = atoi(key);
    JsonObject values = state.value();
    lights[lightId].lightState = values["on"];
    lights[lightId].bri = (uint8_t)values["bri"];
    if (values.containsKey("x")) {
      lights[lightId].x = values["x"];
      lights[lightId].y = values["y"];
      lights[lightId].colorMode = 1;
    } else if (values.containsKey("ct")) {
      lights[lightId].ct = values["ct"];
      lights[lightId].colorMode = 2;
    } else {
      if (values.containsKey("hue")) {
        lights[lightId].hue = values["hue"];
        lights[lightId].colorMode = 3;
      }
      if (values.containsKey("sat")) {
        lights[lightId].sat = (uint8_t) values["sat"];
        lights[lightId].colorMode = 3;
      }
    }
  }
}


bool saveConfig() {
  DynamicJsonDocument json(1024);
  json["name"] = lightName;
  json["startup"] = startup;
  json["scene"] = scene;
  json["on"] = onPin;
  json["off"] = offPin;
  json["hw"] = hwSwitch;
  json["dhcp"] = useDhcp;
  json["lightsCount"] = lightsCount;
  json["pixelCount"] = pixelCount;
  json["transLeds"] = transitionLeds;
  JsonArray addr = json.createNestedArray("addr");
  addr.add(address[0]);
  addr.add(address[1]);
  addr.add(address[2]);
  addr.add(address[3]);
  JsonArray gw = json.createNestedArray("gw");
  gw.add(gateway[0]);
  gw.add(gateway[1]);
  gw.add(gateway[2]);
  gw.add(gateway[3]);
  JsonArray mask = json.createNestedArray("mask");
  mask.add(submask[0]);
  mask.add(submask[1]);
  mask.add(submask[2]);
  mask.add(submask[3]);
  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    //Serial.println("Failed to open config file for writing");
    return false;
  }

  serializeJson(json, configFile);
  return true;
}

bool loadConfig() {
  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    //Serial.println("Create new file with default values");
    return saveConfig();
  }

  if (configFile.size() > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  DynamicJsonDocument json(1024);
  DeserializationError error = deserializeJson(json, configFile.readString());
  if (error) {
    //Serial.println("Failed to parse config file");
    return false;
  }

  strcpy(lightName, json["name"]);
  startup = (uint8_t) json["startup"];
  scene  = (uint8_t) json["scene"];
  onPin = (uint8_t) json["on"];
  offPin = (uint8_t) json["off"];
  hwSwitch = json["hw"];
  lightsCount = (uint16_t) json["lightsCount"];
  pixelCount = (uint16_t) json["pixelCount"];
  transitionLeds = (uint8_t) json["transLeds"];
  useDhcp = json["dhcp"];
  address = {json["addr"][0], json["addr"][1], json["addr"][2], json["addr"][3]};
  submask = {json["mask"][0], json["mask"][1], json["mask"][2], json["mask"][3]};
  gateway = {json["gw"][0], json["gw"][1], json["gw"][2], json["gw"][3]};
  return true;
}

void ChangeNeoPixels(uint16_t newCount)
{
  if (strip != NULL) {
    delete strip; // delete the previous dynamically created strip
  }
  strip = new NeoPixelBus<NeoGrbwFeature, NeoEsp8266Uart1Ws2812xMethod>(newCount); // and recreate with new count
  strip->Begin();
}
RgbwColor blendingEntert(float left[4], float right[4], float pixel) {
  uint8_t result[4];
  for (uint8_t i = 0; i < 3; i++) {
    float percent = (float) pixel / (float) (transitionLeds + 1);
    result[i] = (left[i] * (1.0f - percent) + right[i] * percent) / 2;
  }
  return RgbwColor((uint8_t)result[0], (uint8_t)result[1], (uint8_t)result[2], 0);
}

void entertainment() {
  uint8_t packetSize = Udp.parsePacket();
  if (packetSize) {
    if (!entertainmentRun) {
      entertainmentRun = true;
    }
    lastEPMillis = millis();
    Udp.read(packetBuffer, packetSize);
    for (uint8_t i = 0; i < packetSize / 4; i++) {
      lights[packetBuffer[i * 4]].currentColors[0] = packetBuffer[i * 4 + 1];
      lights[packetBuffer[i * 4]].currentColors[1] = packetBuffer[i * 4 + 2];
      lights[packetBuffer[i * 4]].currentColors[2] = packetBuffer[i * 4 + 3];
    }
    for (uint8_t light = 0; light < lightsCount; light++) {
      if (lightsCount > 1) {
        if (light == 0) {
          for (uint8_t pixel = 0; pixel < lightLedsCount + transitionLeds / 2; pixel++) {
            if (pixel < lightLedsCount - transitionLeds / 2) {
              strip->SetPixelColor(pixel, convInt(lights[light].currentColors));
            } else {
              strip->SetPixelColor(pixel, blendingEntert(lights[0].currentColors, lights[1].currentColors, pixel + 1 - (lightLedsCount - transitionLeds / 2 )));
            }
          }
        } else if (light == lightsCount - 1) {
          for (uint8_t pixel = 0; pixel < lightLedsCount - transitionLeds / 2 ; pixel++) {
            strip->SetPixelColor(pixel + transitionLeds / 2 + lightLedsCount * light, convInt(lights[light].currentColors));
          }
        } else {
          for (uint8_t pixel = 0; pixel < lightLedsCount; pixel++) {
            if (pixel < lightLedsCount - transitionLeds) {
              strip->SetPixelColor(pixel + transitionLeds / 2 + lightLedsCount * light, convInt(lights[light].currentColors));
            } else {
              strip->SetPixelColor(pixel + transitionLeds / 2 + lightLedsCount * light, blendingEntert(lights[light].currentColors, lights[light + 1].currentColors, pixel - (lightLedsCount - transitionLeds ) + 1));
            }
          }
        }
      } else {
        strip->ClearTo(RgbwColor(lights[0].colors[0], lights[0].colors[1], lights[0].colors[2], 0), 0, lightLedsCount - 1);
      }
    }
    strip->Show();
  }
}

Examples depend on "using namespace ace_button" in the global namespace

I found that https://github.com/bxparks/AceButton#IncludeHeader explained how to import the ace_button namespace into global, but the examples seem to all demonstrate dependency on the ace_button:: members all being in the global namespace as being best practice.

Shouldn't the recommended uses be more encapsulated? Like:

/*
 * A demo of a simplest AceButton that has a visible effect. One button is
 * connected to the digital pin BUTTON_PIN. It uses the internal pull-up
 * resistor (INPUT_PULLUP). Pressing the button turns on the built-in LED.
 * Releasing the button turns off the LED.
 */

#include <AceButton.h>

// Some ESP32 boards have multiple builtin LEDs so don't define LED_BUILTIN.
#if defined(ESP32)
  const int LED_PIN = 2;
#else
  const int LED_PIN = LED_BUILTIN;
#endif

const int BUTTON_PIN = 2;
const int LED_ON = HIGH;
const int LED_OFF = LOW;

ace_button::AceButton button(BUTTON_PIN);

// Forward reference to prevent Arduino compiler becoming confused.
void handleEvent(ace_button::AceButton*, uint8_t, uint8_t);

void setup() {
  delay(2000);

#if defined(ARDUINO_AVR_LEONARDO)
  RXLED0; // LED off
  TXLED0; // LED off
#endif

  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  button.setEventHandler(handleEvent);
}

void loop() {
  button.check();
}

void handleEvent(ace_button::AceButton* /* button */, uint8_t eventType,
    uint8_t /* buttonState */) {
  using namespace ace_button;
  switch (eventType) {
    case AceButton::kEventPressed:
      digitalWrite(LED_PIN, LED_ON);
      break;
    case AceButton::kEventReleased:
      digitalWrite(LED_PIN, LED_OFF);
      break;
  }
}

... or with a specific name import instead of the wildcard:

/*
 * A demo of a simplest AceButton that has a visible effect. One button is
 * connected to the digital pin BUTTON_PIN. It uses the internal pull-up
 * resistor (INPUT_PULLUP). Pressing the button turns on the built-in LED.
 * Releasing the button turns off the LED.
 */

#include <AceButton.h>
using ace_button::AceButton;

// Some ESP32 boards have multiple builtin LEDs so don't define LED_BUILTIN.
#if defined(ESP32)
  const int LED_PIN = 2;
#else
  const int LED_PIN = LED_BUILTIN;
#endif

const int BUTTON_PIN = 2;
const int LED_ON = HIGH;
const int LED_OFF = LOW;

AceButton button(BUTTON_PIN);

// Forward reference to prevent Arduino compiler becoming confused.
void handleEvent(AceButton*, uint8_t, uint8_t);

void setup() {
  delay(2000);

#if defined(ARDUINO_AVR_LEONARDO)
  RXLED0; // LED off
  TXLED0; // LED off
#endif

  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  button.setEventHandler(handleEvent);
}

void loop() {
  button.check();
}

void handleEvent(AceButton* /* button */, uint8_t eventType,
    uint8_t /* buttonState */) {
      using namespace ace_button;

  switch (eventType) {
    case AceButton::kEventPressed:
      digitalWrite(LED_PIN, LED_ON);
      break;
    case AceButton::kEventReleased:
      digitalWrite(LED_PIN, LED_OFF);
      break;
  }
}

Tri-state button example

First of all, thanks for making and sharing this library 👍

I have a use case (car cruise control) where a single momentary push button serves three functions.

  1. when pressed and released (click) it bumps the current target speed by 1
  2. when pressed and held it commands an acceleration event until released
  3. when released after a long press it sets the current speed as the target speed

Detecting short press / long press on release is no issue.
Just wondering if there's a minimal way to handle the "long press while held" event.

[OT] featuring AceButton

hi Brian

I have no other means to get in touch with you (no social media or contacts are in your profile), so here is the only place I can post a message to you.
I wanted to let you know I have used AceButton in our content update video on Arduino CLI 0.14.0
https://www.youtube.com/watch?v=vmTYh9jDoQ8

you can remove this issue now :)
regards,

ubi [ Arduino Tooling Team ]

Multiple buttons not working

I tested a bit around with this and figured out that I can't use more buttons than one?
The following code will print "button2" if I press button1 or button2. It doesn't matter.

#include <AceButton.h>
using namespace ace_button;

AceButton button1(6);
AceButton button2(7);

void button1Handler(AceButton* button, uint8_t eventType, uint8_t buttonState) {
  Serial.println("button1");
}

void button2Handler(AceButton* button, uint8_t eventType, uint8_t buttonState) {
  Serial.println("button2");
}

void setup() {
  Serial.begin(9600);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  button1.setEventHandler(button1Handler);
  button2.setEventHandler(button2Handler);
}

void loop() {
  button1.check();
  button2.check();
}

Public method to supress long kEventLongPressed

Wonderful Library! I'm using AceButton to detect button presses on a rotary encoder that also has press functionality, because of this I consider rotations of the encoder to cancel the fact the button is counting up to being 'long pressed' exposing a public:

void AceButton::cancelLongPress() {
  clearPressed();
}

Would seem to be a reasonable modification unless you can see some horrible pitfall or better way of expressing it?

Does not build on Arduino Nano Every

Hi,

building AceButton on the brand new Arduino Nano Every (megaAVR architecture) fails with this error message:

/libraries/AceButton/src/ace_button/AceButton.cpp:33:4: error: #error HIGH must be defined to be 1
   #error HIGH must be defined to be 1
    ^~~~~
exit status 1

It's obviously this piece of code:

// Check that the Arduino constants HIGH and LOW are defined to be 1 and 0,
// respectively. Otherwise, this library won't work.
#if HIGH != 1
  #error HIGH must be defined to be 1
#endif
#if LOW != 0
  #error LOW must be defined to be 0
#endif

I tracked down where HIGH and LOW are defined (hardware/arduino/avr/cores/arduino/Arduino.h):

#define HIGH 0x1
#define LOW  0x0

This looks fine to me. I removed the check, which clears the error message and the builds but the library does not work. So as advertised in the comment above the check haha. Any idea how to resolve this?

If you need more information, let me know.

Compiling on Nano Every gives errors

On trying to compile, for example, HelloButton for one of the new Nano Every modules, the Arduino IDE spits out a load of errors (see below). These seem to be related to the testing code in your library. I am using Version 1.5.0 of your AceButton library and Arduino IDE 1.8.13. Every other board type I've tried seems to compile without any issues.

Arduino: 1.8.13 (Windows 10), Board: "Arduino Nano Every, ATMEGA328"

In file included from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:2:0:

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:62:18: error: reference to 'Print' is ambiguous

 void printTo(Print& printer) const;

              ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:30:7: note: candidates are: class Print

class Print;

   ^~~~~

In file included from C:\Users\Philip\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino/api/deprecated/Print.h:23:0,

             from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:1:

c:\users\philip\appdata\local\arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino\api\print.h:34:7: note: class arduino::Print

class Print

   ^~~~~

In file included from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:2:0:

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:62:18: error: 'Print' has not been declared

 void printTo(Print& printer) const;

              ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:98:18: error: reference to 'Print' is ambiguous

 void printTo(Print& printer) const;

              ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:30:7: note: candidates are: class Print

class Print;

   ^~~~~

In file included from C:\Users\Philip\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino/api/deprecated/Print.h:23:0,

             from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:1:

c:\users\philip\appdata\local\arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino\api\print.h:34:7: note: class arduino::Print

class Print

   ^~~~~

In file included from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:2:0:

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:98:18: error: 'Print' has not been declared

 void printTo(Print& printer) const;

              ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:7:27: error: variable or field 'printTo' declared void

void EventRecord::printTo(Print& printer) const {

                       ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:7:27: error: reference to 'Print' is ambiguous

In file included from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:2:0:

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:30:7: note: candidates are: class Print

class Print;

   ^~~~~

In file included from C:\Users\Philip\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino/api/deprecated/Print.h:23:0,

             from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:1:

c:\users\philip\appdata\local\arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino\api\print.h:34:7: note: class arduino::Print

class Print

   ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:7:34: error: 'printer' was not declared in this scope

void EventRecord::printTo(Print& printer) const {

                              ^~~~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:7:34: note: suggested alternative: 'printf'

void EventRecord::printTo(Print& printer) const {

                              ^~~~~~~

                              printf

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:17:28: error: variable or field 'printTo' declared void

void EventTracker::printTo(Print& printer) const {

                        ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:17:28: error: reference to 'Print' is ambiguous

In file included from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:2:0:

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.h:30:7: note: candidates are: class Print

class Print;

   ^~~~~

In file included from C:\Users\Philip\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino/api/deprecated/Print.h:23:0,

             from F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:1:

c:\users\philip\appdata\local\arduino15\packages\arduino\hardware\megaavr\1.8.6\cores\arduino\api\print.h:34:7: note: class arduino::Print

class Print

   ^~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:17:35: error: 'printer' was not declared in this scope

void EventTracker::printTo(Print& printer) const {

                               ^~~~~~~

F:\Arduino\libraries\AceButton\src\ace_button\testing\EventTracker.cpp:17:35: note: suggested alternative: 'printf'

void EventTracker::printTo(Print& printer) const {

                               ^~~~~~~

                               printf

exit status 1

Error compiling for board Arduino Nano Every.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

GPIO pin 0 cannot be used?

Trying to use AceButton on pin 0 gives me this error:

exit status 1
call of overloaded 'AceButton(int)' is ambiguous

All other pins are okay, except pin 0. What can be done about this?

How to make AceButton work with multiplexed wiring scheme

Hi
I have 3 GPIO pins available but want to use 5 buttons using diode multiplexing. Pressing button 1-5 will pull pin 1-3 to ground following this order:
Button 1 - pin 1
Button 2 - pin 2
Button 3 - pin 3
Button 4 - pin 1 + 2
Button 5 - pin 2 + 3
Will this work with AceButton ? If yes can you provide some example code if possible ?
Thanks

Typo in documentation

Hi Brian,

while reading through the new documentation parts of v1.4 I found below typo in this README.md:

When each button is connected to a single button

One button should be pin here I’d assume.

Nice new feature though! Really appreciate the addition and the way this evolved from a GitHub issue into a feature!

Cheers
Stephan

Analog Pins without GPIO

Hi,

I had some trouble with one of the multiple buttons of my project not working. It took me a while until I found out why. I am using almost all PINs on my Arduino Nano and ended up using A6, which does not have the GPIO functionality. It would be nice if this issue and possibly the fix were mentioned somewhere in the README.md.
I fixed it with adding a pull-up resistor (10k) and replacing following code in ButtonConfig.h:

    virtual int readButton(uint8_t pin) {
      if (pin == A6 || pin == A7)
        return (analogRead(pin) >= 512 ? HIGH : LOW);
      return digitalRead(pin);
    }

It works reliably for me, even with the standard setup pinMode(A6, INPUT_PULLUP);. So with an external pull-up, the above code is the only change needed and those pins behave like any other. Are there any issues with this solution? Except of course you have to match the pins to your hardware if you do not use a Nano.

Regards,
Ben

reduce default debounce delay from 50 ms to 20 ms

I'm going to change the default debouncing delay kDebounceDelay from 50 ms to 20 ms.

I have noticed that at 50 milliseconds, a button can feel sluggish if I push it up and down fast enough. With the current delay of 50 ms, a Pressed event is delayed by 50 ms, then a Released event is delayed by 50 ms, for a total delay of at least 100 ms, and it is actually possible to push a button up and down fast enough for the library to miss real button events. At 20 ms, a button feels far more responsive.

This extensive article (http://www.ganssle.com/debouncing-pt2.htm) says that most switches will settle down within 10 ms, and recommends a debounce time of 20-50 ms. I'm going with the lower end of that.

This change means that the AceButton::check() must be called a little more frequently than before. So instead of calling it every 10-20 ms, it should be called every 5 ms or lower.

End-users are, of course, always free to override the default values of any of the timing parameters.

Singleton system button config can be an issue

Why is the default config implemented as a Singleton? If you have more than one button and they both point to the same config, then they also all point to the same handler. So you get several buttons that provide a single behavior. I'm just failing to see a use case that would benefit from this.

Wouldn't a factory method be more appropriate? ...or perhaps just making the handler a member of the button class? For now, I can simply assign my own instantiations. So, this is more of a comment than an issue (although, it could be an issue for unsuspecting users).

There's some nice code here. Thanks for sharing. It's refreshing to see some OOP in this world of microcontrollers.

I want to set setDefaultReleasedState to LOW

I need to set setDefaultReleasedState to LOW so how do i do it ?
as well as why are using pull down resistor than pull up resistor?
ie why are initiating or expecting all the pins to HIGH by default ? doesnt it cause more current ?

add internal stats variable to track amount of CPU time taken for certain code paths

The StopWatch.ino example code gives the CPU time taken by AceButton.check() when nothing is happening. It'd be nice to get information about how much CPU time it takes to handle certain events,
like Click and DoubleClick. I think the only way to get that information is to insert stats variables into the AceButton class itself. I'd want something like average, min, max and the ability to reset the stats periodically, say every 5 seconds. I think one way to reduce the runtime cost of the stats gathering, and reduce static memory is to inject the "stats" object into AceButton and only do the calculation if the stats object is not null. So 2 more extra bytes on an 8-bit AVR.

invalid use of non-static member function 'void Interface::handleEvent(ace_button::AceButton*, uint8_t, uint8_t)'

Hello,

I try to instantiate a ButtonConfig in a member class function but I get the following error.

It is possible to do this pattern ?

class Test {
    private:
        AceButton button1 = AceButton(1);
        void configure();
        void handleEvent(AceButton *, uint8_t, uint8_t);
};
#include <Test.h>

void Test::configure(){
     pinMode(1, INPUT_PULLUP);
     ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig();
     buttonConfig->setEventHandler(handleEvent);
     buttonConfig->setFeature(ButtonConfig::kFeatureClick);
     buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
     buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
     buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress);
}

void Test::handleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState){
     switch (button->getPin())
    {
    case 1:
        Serial.println("Hourrray");
        break;
    }
}

Thank you

Button as switch

Is it possible to use the same button once to turn on an output and once to turn off the output? like a switch.. with the same funtion (keventpressed or clicked)

Button presses not recognized at all or random

AceButton version: 1.3.2
Arduino IDE version: 1.8.5
Arduino: Arduino Micro (A clone, not the original one. The IDE recognizes the Arduino as an "Arduino Leonardo". )

The click and long press events aren't triggered at all no matter if I press the button for only a few milliseconds or for a long time. Sometimes instead of not reacting at all it just thinks it is constantly receiving presses. It works just fine and as expected when I simply use digitalRead instead.

I minimized the code to a minimum and even took the button and anything else connected to the Arduino out of the equation, I only used a wire to "short" GND to a digital pin (I tried many different pins including 2, 3, 4 and 9). Just to make sure I opened up another brand new Arduino Micro clone, but I had the exact same issues. I also tried instantiating with AceButton leftPedal(LEFT_PEDAL, HIGH); and AceButton leftPedal(LEFT_PEDAL, LOW);. But still it didn't behave correctly. (I'm not using an external pull-down resistor btw, just a wire.)

This works perfectly fine:

const int buttonPin = 2;
int buttonState = 0;

void setup() {
  delay(1000);
  Serial.begin(115200);
  while (! Serial); // Wait until Serial is ready - Leonardo/Micro
  Serial.println(F("setup(): begin"));
  digitalWrite(buttonPin, HIGH);
}

void loop() {
  buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH) {
    Serial.println(F("HIGH"));
  } else {
    Serial.println(F("LOW"));
  }
}

This code misbehaves as described above:

#include <AceButton.h>
using namespace ace_button;

//uint16_t clickDelay = 100;
//uint16_t debounceDelay = 50;
//uint16_t repeatPressDelay = 500;
//uint16_t repeatPressInterval = 500;

const int LEFT_PEDAL = 2;

AceButton leftPedal(LEFT_PEDAL);

void scrollDownEvent(AceButton*, uint8_t, uint8_t);

void setup() {
  delay(1000); // some microcontrollers reboot twice
  Serial.begin(115200);
  while (! Serial); // Wait until Serial is ready - Leonardo/Micro
  Serial.println(F("setup(): begin"));

  ButtonConfig* leftPedalConfig = leftPedal.getButtonConfig();
  leftPedalConfig->setEventHandler(scrollDownEvent);
  leftPedalConfig->setFeature(ButtonConfig::kFeatureClick);
  //leftPedalConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
  //leftPedalConfig->setFeature(ButtonConfig::kFeatureLongPress);
  leftPedalConfig->setFeature(ButtonConfig::kFeatureRepeatPress);
  //leftPedalConfig->setDebounceDelay(clickDelay);
  //leftPedalConfig->setDebounceDelay(debounceDelay);
  //leftPedalConfig->setRepeatPressDelay(repeatPressDelay);
  //leftPedalConfig->setRepeatPressInterval(repeatPressInterval);
  
  Serial.println(F("setup(): ready"));
}

void loop() {
  leftPedal.check();
}

void scrollDownEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {
    case AceButton::kEventPressed:
      Serial.println(F("kEventPressed: SCROLLDOWN"));
      break;
    case AceButton::kEventRepeatPressed:
      Serial.println(F("kEventRepeatPressed: SCROLLDOWN"));
      break;
  }
}

I spent the last 4 hours desperately trying to get this to work. I would really appreciate it if you could check this out and tell me what the problem is.

Could we make `isLongPressed()` public?

Hi,
I'm overhauling my indicator system in my scooter. It has two buttons for indication.

I cycle through modes by tapping both indicators at the same time. However, I need to process combination taps but ignore it if either is long pressed. Rather than keep my own state tracker, why not make isLongPressed() public?

      if (eventType == AceButton::kEventPressed) {
        if (left.isPressedRaw() && right.isPressedRaw()) { // If both bottons are pressed
          if (!left.isLongPressed() && !right.isLongPressed()) { // Skip if either button is long pressed, taps only
            Serial.println("Both");
          }
        }
      }

Initial button state

Hi,

first: thanks for your AceButton lib,it provides a lot of possibilities and makes button handling quite easy. However, I have an issue with the initial state of the buttons: the initial state is always 'pressed'. I tried it with and without external and internal pull-down, even with no wiring to the button-pins at all. Finally, I tried the following: pressed the button on restart and voila, this time it was not recognized as being pressed at startup. Could it be the case, that the initial state is negated?
Thanks for investigating and kind regards, Tom

Interrupts

How can I use interrupts with this libruary, like in EasyButton?

call of overloaded 'init(int)' is ambiguous

This did work OK in v1.3.5, fails after upgrading to v1.8.0 with call of overloaded 'init(int)' is ambiguous compiler error:

#define PIN 0
#include <AceButton.h>
using namespace ace_button;
void handleButton(AceButton* /*button*/, uint8_t eventType, uint8_t /*buttonState*/); // forward declaration
ButtonConfig buttonConfig;
AceButton button(&buttonConfig);

void setup()
{
    buttonConfig.setEventHandler(handleButton);
    buttonConfig.setFeature(ButtonConfig::kFeatureClick);
    tlacitkoConfig.setFeature(ButtonConfig::kFeatureDoubleClick);
    tlacitkoConfig.setFeature(ButtonConfig::kFeatureLongPress);
    button.init(PIN);

Build AceButton with AUniter

Hi!

I'm experimenting with AceButton, and trying to build it using the AUniter cli for AUnit... I'm sure this is a Path problems, but don't know how to solve it:

`~/development/AceButton % auniter verify esp32 $(find . -name '*.ino') [0]
======== Processing environment 'esp32'
-------- Processing file './tests/EncodedButtonConfigTest/EncodedButtonConfigTest.ino'
$ /Applications/Arduino.app/Contents/MacOS/Arduino --verify --board esp32:esp32:esp32:PartitionScheme=default,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none --pref 'compiler.cpp.extra_flags=-DAUNITER -DAUNITER_ESP32 -DAUNITER_SSID="wonderland2G_EXT" -DAUNITER_PASSWORD="a1b2c3d4e5"' ./tests/EncodedButtonConfigTest/EncodedButtonConfigTest.ino
2020-08-20 22:36:33.970 Arduino[57775:1921892] Loading Application 'Arduino'
2020-08-20 22:36:33.971 Arduino[57775:1921892] JVMRuntime=jre8u252-b09.jre
2020-08-20 22:36:33.971 Arduino[57775:1921892] CFBundleName=Arduino
2020-08-20 22:36:33.971 Arduino[57775:1921892] WorkingDirectory=(null)
2020-08-20 22:36:33.971 Arduino[57775:1921892] JVMMainClassName=processing.app.Base
2020-08-20 22:36:33.971 Arduino[57775:1921892] JVMOptions=(
"-Dapple.awt.application.name=Arduino",
"-Dcom.apple.macos.use-file-dialog-packages=true",
"-Dcom.apple.smallTabs=true",
"-DAPP_DIR=$APP_ROOT/Contents/Java",
"-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:$JVM_RUNTIME/Contents/Home/jre/lib/ext/",
"-Djava.net.preferIPv4Stack=true",
"-Xdock:name=Arduino",
"-Dcom.apple.mrj.application.apple.menu.about.name=Arduino",
"-Dfile.encoding=UTF-8",
"-Xms128M",
"-Xmx512M",
"-splash:$APP_ROOT/Contents/Java/lib/splash.png"
)
2020-08-20 22:36:33.972 Arduino[57775:1921892] JVMArguments=(
)
2020-08-20 22:36:33.972 Arduino[57775:1921892] JVMClasspath=(null)
2020-08-20 22:36:33.972 Arduino[57775:1921892] JVMDefaultOptions={
}
2020-08-20 22:36:33.972 Arduino[57775:1921892] -> Bundle path: /Applications/Arduino.app
2020-08-20 22:36:33.972 Arduino[57775:1921892] -> Working Directory: '/Users/victorhg/development/AceButton'
2020-08-20 22:36:33.972 Arduino[57775:1921892] -> JVM Runtime path: /Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre
2020-08-20 22:36:33.972 Arduino[57775:1921892] Searching for a Java 8 virtual machine
2020-08-20 22:36:33.972 Arduino[57775:1921892] Search for java VM in '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home'
2020-08-20 22:36:33.972 Arduino[57775:1921892] KO - error: 'launch path not accessible'
2020-08-20 22:36:34.041 Arduino[57775:1921892] No matching JDK found.
2020-08-20 22:36:34.042 Arduino[57775:1921892] -> Java Runtime Dylib Path: '/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/lib/jli/libjli.dylib'
2020-08-20 22:36:34.056 Arduino[57775:1921892] Command line passed to application argc=27:
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 0: '/Applications/Arduino.app/Contents/MacOS/Arduino'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 1: '-Djava.class.path=/Applications/Arduino.app/Contents/Java/log4j-core-2.12.0.jar:/Applications/Arduino.app/Contents/Java/rsyntaxtextarea-3.0.3-SNAPSHOT.jar:/Applications/Arduino.app/Contents/Java/batik-ext-1.8.jar:/Applications/Arduino.app/Contents/Java/slf4j-api-1.7.22.jar:/Applications/Arduino.app/Contents/Java/arduino-core.jar:/Applications/Arduino.app/Contents/Java/batik-xml-1.8.jar:/Applications/Arduino.app/Contents/Java/slf4j-simple-1.7.22.jar:/Applications/Arduino.app/Contents/Java/batik-dom-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-compress-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-httpclient-3.1.jar:/Applications/Arduino.app/Contents/Java/bcprov-jdk15on-152.jar:/Applications/Arduino.app/Contents/Java/commons-logging-1.0.4.jar:/Applications/Arduino.app/Contents/Java/log4j-api-2.12.0.jar:/Applications/Arduino.app/Contents/Java/batik-script-1.8.jar:/Applications/Arduino.app/Contents/Java/jackson-annotations-2.9.5.jar:/Applications/Arduino.app/Contents/Java/batik-parser-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-squiggle-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-awt-util-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-gvt-1.8.jar:/Applications/Arduino.app/Contents/Java/bcpg-jdk15on-152.jar:/Applications/Arduino.app/Contents/Java/xml-apis-1.3.04.jar:/Applications/Arduino.app/Contents/Java/batik-anim-1.8.jar:/Applications/Arduino.app/Contents/Java/jtouchbar-1.0.0.jar:/Applications/Arduino.app/Contents/Java/batik-bridge-1.8.jar:/Applications/Arduino.app/Contents/Java/jssc-2.8.0-arduino4.jar:/Applications/Arduino.app/Contents/Java/batik-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-transcoder-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-svg-dom-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-rasterizer-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-codec-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-io-2.6.jar:/Applications/Arduino.app/Contents/Java/commons-codec-1.7.jar:/Applications/Arduino.app/Contents/Java/xmlgraphics-commons-2.0.jar:/Applications/Arduino.app/Contents/Java/apple.jar:/Applications/Arduino.app/Contents/Java/commons-exec-1.1.jar:/Applications/Arduino.app/Contents/Java/batik-css-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-util-1.8.jar:/Applications/Arduino.app/Contents/Java/jna-platform-4.2.2.jar:/Applications/Arduino.app/Contents/Java/commons-net-3.3.jar:/Applications/Arduino.app/Contents/Java/batik-svgpp-1.8.jar:/Applications/Arduino.app/Contents/Java/xml-apis-ext-1.3.04.jar:/Applications/Arduino.app/Contents/Java/jmdns-3.5.5.jar:/Applications/Arduino.app/Contents/Java/java-semver-0.8.0.jar:/Applications/Arduino.app/Contents/Java/pde.jar:/Applications/Arduino.app/Contents/Java/jna-4.2.2.jar:/Applications/Arduino.app/Contents/Java/commons-lang3-3.8.1.jar:/Applications/Arduino.app/Contents/Java/jackson-core-2.9.5.jar:/Applications/Arduino.app/Contents/Java/jsch-0.1.50.jar:/Applications/Arduino.app/Contents/Java/jackson-databind-2.9.5.jar'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 2: '-Djava.library.path=/Applications/Arduino.app/Contents/MacOS'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 3: '-DLibraryDirectory=/Users/victorhg/Library'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 4: '-DDocumentsDirectory=/Users/victorhg/Documents'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 5: '-DApplicationSupportDirectory=/Users/victorhg/Library/Application Support'
2020-08-20 22:36:34.057 Arduino[57775:1921892] Arg 6: '-DCachesDirectory=/Users/victorhg/Library/Caches'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 7: '-DSandboxEnabled=true'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 8: '-Dapple.awt.application.name=Arduino'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 9: '-Dcom.apple.macos.use-file-dialog-packages=true'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 10: '-Dcom.apple.smallTabs=true'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 11: '-DAPP_DIR=/Applications/Arduino.app/Contents/Java'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 12: '-Djava.ext.dirs=/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/lib/ext/:/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/jre/lib/ext/'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 13: '-Djava.net.preferIPv4Stack=true'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 14: '-Xdock:name=Arduino'
2020-08-20 22:36:34.058 Arduino[57775:1921892] Arg 15: '-Dcom.apple.mrj.application.apple.menu.about.name=Arduino'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 16: '-Dfile.encoding=UTF-8'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 17: '-Xms128M'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 18: '-Xmx512M'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 19: '-splash:/Applications/Arduino.app/Contents/Java/lib/splash.png'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 20: 'processing.app.Base'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 21: '--verify'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 22: '--board'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 23: 'esp32:esp32:esp32:PartitionScheme=default,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 24: '--pref'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 25: 'compiler.cpp.extra_flags=-DAUNITER -DAUNITER_ESP32 -DAUNITER_SSID="wonderland2G_EXT" -DAUNITER_PASSWORD="a1b2c3d4e5"'
2020-08-20 22:36:34.059 Arduino[57775:1921892] Arg 26: './tests/EncodedButtonConfigTest/EncodedButtonConfigTest.ino'
2020-08-20 22:36:34.061 Arduino[57775:1921902] Loading Application 'Arduino'
2020-08-20 22:36:34.061 Arduino[57775:1921902] JVMRuntime=jre8u252-b09.jre
2020-08-20 22:36:34.061 Arduino[57775:1921902] CFBundleName=Arduino
2020-08-20 22:36:34.061 Arduino[57775:1921902] WorkingDirectory=(null)
2020-08-20 22:36:34.061 Arduino[57775:1921902] JVMMainClassName=processing.app.Base
2020-08-20 22:36:34.061 Arduino[57775:1921902] JVMOptions=(
"-Dapple.awt.application.name=Arduino",
"-Dcom.apple.macos.use-file-dialog-packages=true",
"-Dcom.apple.smallTabs=true",
"-DAPP_DIR=$APP_ROOT/Contents/Java",
"-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:$JVM_RUNTIME/Contents/Home/jre/lib/ext/",
"-Djava.net.preferIPv4Stack=true",
"-Xdock:name=Arduino",
"-Dcom.apple.mrj.application.apple.menu.about.name=Arduino",
"-Dfile.encoding=UTF-8",
"-Xms128M",
"-Xmx512M",
"-splash:$APP_ROOT/Contents/Java/lib/splash.png"
)
2020-08-20 22:36:34.061 Arduino[57775:1921902] JVMArguments=(
)
2020-08-20 22:36:34.062 Arduino[57775:1921902] JVMClasspath=(null)
2020-08-20 22:36:34.062 Arduino[57775:1921902] JVMDefaultOptions={
}
2020-08-20 22:36:34.062 Arduino[57775:1921902] -> Bundle path: /Applications/Arduino.app
2020-08-20 22:36:34.062 Arduino[57775:1921902] -> Working Directory: '/Users/victorhg/development/AceButton'
2020-08-20 22:36:34.062 Arduino[57775:1921902] -> JVM Runtime path: /Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre
2020-08-20 22:36:34.062 Arduino[57775:1921902] Searching for a Java 8 virtual machine
2020-08-20 22:36:34.062 Arduino[57775:1921902] Search for java VM in '/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home'
2020-08-20 22:36:34.062 Arduino[57775:1921902] KO - error: 'launch path not accessible'
2020-08-20 22:36:34.079 Arduino[57775:1921902] No matching JDK found.
2020-08-20 22:36:34.079 Arduino[57775:1921902] -> Java Runtime Dylib Path: '/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/lib/jli/libjli.dylib'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Command line passed to application argc=27:
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 0: '/Applications/Arduino.app/Contents/MacOS/Arduino'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 1: '-Djava.class.path=/Applications/Arduino.app/Contents/Java/log4j-core-2.12.0.jar:/Applications/Arduino.app/Contents/Java/rsyntaxtextarea-3.0.3-SNAPSHOT.jar:/Applications/Arduino.app/Contents/Java/batik-ext-1.8.jar:/Applications/Arduino.app/Contents/Java/slf4j-api-1.7.22.jar:/Applications/Arduino.app/Contents/Java/arduino-core.jar:/Applications/Arduino.app/Contents/Java/batik-xml-1.8.jar:/Applications/Arduino.app/Contents/Java/slf4j-simple-1.7.22.jar:/Applications/Arduino.app/Contents/Java/batik-dom-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-compress-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-httpclient-3.1.jar:/Applications/Arduino.app/Contents/Java/bcprov-jdk15on-152.jar:/Applications/Arduino.app/Contents/Java/commons-logging-1.0.4.jar:/Applications/Arduino.app/Contents/Java/log4j-api-2.12.0.jar:/Applications/Arduino.app/Contents/Java/batik-script-1.8.jar:/Applications/Arduino.app/Contents/Java/jackson-annotations-2.9.5.jar:/Applications/Arduino.app/Contents/Java/batik-parser-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-squiggle-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-awt-util-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-gvt-1.8.jar:/Applications/Arduino.app/Contents/Java/bcpg-jdk15on-152.jar:/Applications/Arduino.app/Contents/Java/xml-apis-1.3.04.jar:/Applications/Arduino.app/Contents/Java/batik-anim-1.8.jar:/Applications/Arduino.app/Contents/Java/jtouchbar-1.0.0.jar:/Applications/Arduino.app/Contents/Java/batik-bridge-1.8.jar:/Applications/Arduino.app/Contents/Java/jssc-2.8.0-arduino4.jar:/Applications/Arduino.app/Contents/Java/batik-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-transcoder-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-svg-dom-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-rasterizer-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-codec-1.8.jar:/Applications/Arduino.app/Contents/Java/commons-io-2.6.jar:/Applications/Arduino.app/Contents/Java/commons-codec-1.7.jar:/Applications/Arduino.app/Contents/Java/xmlgraphics-commons-2.0.jar:/Applications/Arduino.app/Contents/Java/apple.jar:/Applications/Arduino.app/Contents/Java/commons-exec-1.1.jar:/Applications/Arduino.app/Contents/Java/batik-css-1.8.jar:/Applications/Arduino.app/Contents/Java/batik-util-1.8.jar:/Applications/Arduino.app/Contents/Java/jna-platform-4.2.2.jar:/Applications/Arduino.app/Contents/Java/commons-net-3.3.jar:/Applications/Arduino.app/Contents/Java/batik-svgpp-1.8.jar:/Applications/Arduino.app/Contents/Java/xml-apis-ext-1.3.04.jar:/Applications/Arduino.app/Contents/Java/jmdns-3.5.5.jar:/Applications/Arduino.app/Contents/Java/java-semver-0.8.0.jar:/Applications/Arduino.app/Contents/Java/pde.jar:/Applications/Arduino.app/Contents/Java/jna-4.2.2.jar:/Applications/Arduino.app/Contents/Java/commons-lang3-3.8.1.jar:/Applications/Arduino.app/Contents/Java/jackson-core-2.9.5.jar:/Applications/Arduino.app/Contents/Java/jsch-0.1.50.jar:/Applications/Arduino.app/Contents/Java/jackson-databind-2.9.5.jar'

2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 2: '-Djava.library.path=/Applications/Arduino.app/Contents/MacOS'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 3: '-DLibraryDirectory=/Users/victorhg/Library'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 4: '-DDocumentsDirectory=/Users/victorhg/Documents'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 5: '-DApplicationSupportDirectory=/Users/victorhg/Library/Application Support'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 6: '-DCachesDirectory=/Users/victorhg/Library/Caches'
2020-08-20 22:36:34.080 Arduino[57775:1921902] Arg 7: '-DSandboxEnabled=true'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 8: '-Dapple.awt.application.name=Arduino'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 9: '-Dcom.apple.macos.use-file-dialog-packages=true'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 10: '-Dcom.apple.smallTabs=true'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 11: '-DAPP_DIR=/Applications/Arduino.app/Contents/Java'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 12: '-Djava.ext.dirs=/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/lib/ext/:/Applications/Arduino.app/Contents/PlugIns/jre8u252-b09.jre/Contents/Home/jre/lib/ext/'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 13: '-Djava.net.preferIPv4Stack=true'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 14: '-Xdock:name=Arduino'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 15: '-Dcom.apple.mrj.application.apple.menu.about.name=Arduino'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 16: '-Dfile.encoding=UTF-8'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 17: '-Xms128M'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 18: '-Xmx512M'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 19: '-splash:/Applications/Arduino.app/Contents/Java/lib/splash.png'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 20: 'processing.app.Base'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 21: '--verify'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 22: '--board'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 23:

'esp32:esp32:esp32:PartitionScheme=default,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none'

2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 24: '--pref'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 25: 'compiler.cpp.extra_flags=-DAUNITER -DAUNITER_ESP32 -DAUNITER_SSID="wonderland2G_EXT" -DAUNITER_PASSWORD="a1b2c3d4e5"'
2020-08-20 22:36:34.081 Arduino[57775:1921902] Arg 26: './tests/EncodedButtonConfigTest/EncodedButtonConfigTest.ino'

Loading configuration...
Initializing packages...
Preparing boards...
Verifying...

EncodedButtonConfigTest:4:23: fatal error: AceButton.h: No such file or directory
compilation terminated.
exit status 1

FAILED verify: esp32 ./tests/EncodedButtonConfigTest/EncodedButtonConfigTest.ino`

Should be able to work out of the box, right? But it seems not to find the headers contained on src folders.

Thanks

use of __FlashStringHelper is ambiguous

Hello,

Love your AceButton library!

One minor issue with the 1.10.0 release: In AceButton.h you define class __FlashStringHelper in the global namespace, but in AceButton.cpp you define it in the ace_button namespace.

Depending on how restrictive your compiler is, this may result in a compiler error. In my case I'm seeing this:
.pio/libdeps/debug/AceButton/src/ace_button/AceButton.h:127:12: error: reference to '__FlashStringHelper' is ambiguous

The issue can be fixed by moving __FlashStringHelper into the ace_button namespace in AceButton.h

Run tests on travis

Would be nice to run the tests on travis-ci.com, using platformio, every time a new commit is added.

ESP32 Support?

I'm wanting to use this lib with an ESP32, but I'm not using the Arduino IDE or Arduino library. Will it still work?

No longer responding to single click

I have been using AceButton on an ESP32's 'Boot' button (GPIO 0) and it worked really well with Click, Doubleclick, and Longpress. But suddenly I get no longer a 'Clicked' response, but I still get Double and Long.

I was using version 1.6, and after detecting the problem, I upgraded to 1.8.3, but no change to my problem.

When pressing a button I see a kEventPressed. Pressing again after some 10+ seconds, I see another kEventPressed followed by a kEventDoubleClicked. Doing a doubleclick as fast as I can also gives a correct kEventDoubleClicked.

Longpress still coming correctly after 2 sec (programmed setLongPressDelay(2000);).

Setting 'setClickDelay()' down to 100 or up to 1000 does not make a difference.

How do I get my click back?

Suggestion for implementing 'setLongReleasedDelay'

Hello, @bxparks

Thank you so much for this GREAT library!

I started using the Acebutton library in my projects and I'm really enjoying it.

I used the EasyButton library in my projects.
With EasyButton it was possible to use the function to identify how long a button was released [ex: button.releasedFor(releasedForTime) ]; .
However, I haven't found a way to do this with Acebutton.
See here: https://easybtn.earias.me/docs/released-for-api/

This function is especially important when we want to identify the state of a water float . The problem is that water waves/ripples can change the float state from HIGHT to LOW quickly (see image below).

I'm trying the following approach:

ButtonConfig* config_buoy = buoy.getButtonConfig();
config_buoy ->setEventHandler(handleEvent_boia_superior);
config_buoy ->setDebounceDelay(50);
config_buoy ->setFeature(ButtonConfig::kFeatureLongPress);
config_buoy ->setLongPressDelay(5000);
config_buoy ->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);

The problem is that kEventLongReleased is quickly triggered when water waves/ripples change the float state from HIGHT to LOW.

Therefore, it would be important to assign a delay to kEventLongReleased, something like a setLongReleasedDelay method:

ButtonConfig* config_buoy = buoy.getButtonConfig();
config_buoy ->setEventHandler(handleEvent_boia_superior);
config_buoy ->setDebounceDelay(50);
config_buoy ->setFeature(ButtonConfig::kFeatureLongPress);
config_buoy ->setLongPressDelay(5000);
config_buoy ->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);
config_buoy ->setLongReleasedDelay(10000);

With setLongReleasedDelay(10000); the kEventLongReleased event would not be triggered on the ripples of the water, but only when it stabilizes for a period of 10 seconds (in the example above).

Would this implementation in Acebutton be viable? This was the only EasyButton functionality that I didn't find in Acebutton.

image
image

proposed deprecation of AdjustableButtonConfig into ButtonConfig

Hello everyone,

Thanks for using the AceButton library. I am thinking of making a change to the ButtonConfig class of the library, and was wondering if anyone has any strong opinions.

Background

When I first created the AceButton library, I was aiming to have a low RAM requirement for the library. Coming from the cloud computing world (where I have access to GiB and TiB of RAM), it was difficult to wrap my head around a programming environment where I have only 2kB of RAM (ATmega328P processor on an UNO or Nano). I made ButtonConfig mostly immutable, containing compile-time constants that seemed reasonable for various timing parameters, and used virtual functions to dispatch down to the AdjustableButtonConfig subclass to allow people to override those parameters if they wanted to.

Proposal

For the next revision of the AceButton library, I am proposing to merge the AdjustableButtonConfig class into the ButtonConfig class, and remove most of those virtual methods that retrieve those timing parameters. The AdjustableButtonConfig class will be kept for backwards compatibility, but it will become just a empty shell subclass of ButtonConfig with no additional features. I can remove that class in later versions.

Reasons

  1. In theory, making ButtonConfig adjustable should increase the RAM usage by 12 bytes (because there are 6 uint16_t timing parameters in AdjustableButtonConfig which are not present in ButtonConfig). But in practice, I have found that the compiler optimizes the code in strange ways, and the overal RAM usage does not actually increase in many cases.

  2. Removing the virtual method on the various getters allows the compiler to inline those methods, instead of using a virtual dispatch, and makes the AceButton.check() method run faster. Of course, we are talking about a difference between, say, 17 microseconds versus 14 microseconds, so maybe this is micro-optimizing, but I think a simple button library should have as little overhead as possible.

  3. Removing AdjustableButtonConfig makes the code simpler, easier to maintain, easier to document, and hopefully easier to use.

Benchmarks

Here are some benchmarks. I used Arduino IDE version 1.8.6, using the arduino:avr:nano:cpu=atmega328old fully-qualified board identifier. Other processors have so much more RAM than the ATMega328P, (e.g. 64kB for Teensy 3.2, 80kB for ESP8266, ~300kB for ESP32), that we will only worry about RAM size for the ATMega328P family of boards.

  • before means the current code,
  • after means the new code in the adjustable branch where I have merged AdjustableButtonConfig into ButtonConfig.

CPU Time

AutoBenchmark

before:

------------------------+-------------+---------+
button event            | min/avg/max | samples |
------------------------+-------------+---------+
idle                    |  16/ 16/ 24 | 1925    |
press/release           |   8/ 18/ 28 | 1920    |
click                   |   8/ 17/ 28 | 1922    |
double click            |   8/ 16/ 32 | 1921    |
long press/repeat press |   8/ 19/ 28 | 1919    |
------------------------+-------------+---------+

after:

------------------------+-------------+---------+
button event            | min/avg/max | samples |
------------------------+-------------+---------+
idle                    |  12/ 13/ 20 | 1934    |
press/release           |   8/ 14/ 20 | 1925    |
click                   |   8/ 14/ 24 | 1925    |
double click            |   8/ 13/ 24 | 1925    |
long press/repeat press |   8/ 15/ 24 | 1927    |
------------------------+-------------+---------+

Program Size and RAM Usage

HelloWorld

  • before (flash/ram): 2406/55
  • after (flash/ram): 2282/55

SingleButton

  • before (flash/ram): 3812/234
  • after (flash/ram): 3692/234

StopWatch

  • before (flash/ram): 5962/268
  • after (flash/ram): 5686/256

TunerButtons

  • before (flash/ram): 4348/306
  • after (flash/ram): 4364/318

ClickVersusDoubleClickUsingBoth

  • before: 2342/55
  • after: 2218/55

CapacitiveButton

  • before: 5488/266
  • after: 5498/298

Summary

For the most part, the new code consumes less flash memory, but surprisingly, it often uses the same amount of static RAM.

Possible conflict with TC3 library on SAMD21 architecture ?

Hi,
I'm trying to work with the DAC and your library on seeduino XIAO controller.
Events are not handled when uncommenting these lines, but work when commented.

    TIMER.initialize(TIMER_TIME);
    TIMER.attachInterrupt(GenerateAudioStream);
    pinMode(DAC_PIN, OUTPUT);
    analogReadResolution(5);
    analogWriteResolution(8);

Full code

#include <Arduino.h>
#include <AceButton.h>
using namespace ace_button;

#include <TimerTC3.h>
#define USE_TIMER_TC3  // use TimerTc3
#define TIMER_TIME 125 // 1/8000 Hz = 125 micro seconds
#define TIMER TimerTc3
#define DAC_PIN 0 // pin of the XIAO DAC

#define PIN_ANALOG_1 A1

// Create 4 AceButton objects, with their virtual pin number 0 to 3. The number
// of buttons does not need to be identical to the number of analog levels. You
// can choose to define only a subset of buttons here.
//
// Use the 4-parameter `AceButton()` constructor with the `buttonConfig`
// parameter explicitly to `nullptr` to prevent the automatic creation of the
// default SystemButtonConfig, saving about 30 bytes of flash and 26 bytes of
// RAM on an AVR processor.
static const uint8_t NUM_BUTTONS = 7;
static AceButton b0(nullptr, 0);
static AceButton b1(nullptr, 1);
static AceButton b2(nullptr, 2);
static AceButton b3(nullptr, 3);
static AceButton b4(nullptr, 4);
static AceButton b5(nullptr, 5);
static AceButton b6(nullptr, 6);
static AceButton *const BUTTONS[NUM_BUTTONS] = {
    &b0, &b1, &b2, &b3, &b4, &b5, &b6};

// Define the ADC voltage levels for each button.
// For 4 buttons, we need 5 levels.
static const uint8_t NUM_LEVELS = NUM_BUTTONS + 1;
static const uint16_t LEVELS[NUM_LEVELS] = {
    0,
    132,
    264,
    363,
    429,
    528,
    594,
    1023};

// The LadderButtonConfig constructor binds the AceButton to the
// LadderButtonConfig.
static LadderButtonConfig buttonConfig(
    PIN_ANALOG_1, NUM_LEVELS, LEVELS, NUM_BUTTONS, BUTTONS);

// The event handler for the button.
void handleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState)
{
    // Print out a message for all events.
    Serial.print(F("handleEvent(): "));
    Serial.print(F("virtualPin: "));
    Serial.print(button->getPin());
    Serial.print(F("; eventType: "));
    Serial.print(eventType);
    Serial.print(F("; buttonState: "));
    Serial.println(buttonState);
}

// The buttonConfig.checkButtons() should be called every 4-5ms or faster, if
// the debouncing time is ~20ms. On ESP8266, analogRead() must be called *no*
// faster than 4-5ms to avoid a bug which disconnects the WiFi connection.
void checkButtons()
{
    static unsigned long prev = millis();

    // DO NOT USE delay(5) to do this.
    unsigned long now = millis();
    if (now - prev > 5)
    {
        buttonConfig.checkButtons();
        prev = now;
    }
}

void GenerateAudioStream()
{
    static int t = 0;
    t++;

    analogWrite(DAC_PIN, (uint32_t)t / 16);
}

void compareTinyExprAndNativeEvaluation();

void setup()
{
    Serial.begin(115200);
    while (!Serial)
        ;

    pinMode(PIN_ANALOG_1, INPUT);
    interrupts();
    // Configure the ButtonConfig with the event handler, and enable all higher
    // level events.
    buttonConfig.setEventHandler(handleEvent);
    buttonConfig.setFeature(ButtonConfig::kFeatureClick);
    buttonConfig.setFeature(ButtonConfig::kFeatureDoubleClick);
    buttonConfig.setFeature(ButtonConfig::kFeatureLongPress);
    buttonConfig.setFeature(ButtonConfig::kFeatureRepeatPress);

    TIMER.initialize(TIMER_TIME);
    TIMER.attachInterrupt(GenerateAudioStream);
    pinMode(DAC_PIN, OUTPUT);
    analogReadResolution(5);
    analogWriteResolution(8);
}

void loop()
{
    checkButtons();
}

More over, when trying to do analogRead without the library and the DAC, the analogRead works

kEventClicked comes always with kEventDoubleClicked

Hi, I see an issue here when testing the library. I registered all events (just following/modifying your examples) as I'm looking for a library that can easily distinguish between single and double clicks, which is not that easy to find. I can easily send out a click on the button and I get the kEventClicked as expected. But when I do a double click I always get first the kEventClicked which is then immeditely followed by the kEventDoubleClicked. This is a nogo for me as this is not presize enough.
In other words a kEventClicked is only allowed to be fired, when there is no kEventDoubleClicked following. I do not know, whether this is already today possible, but I did not get it managed to behave like that.
Simple test is, turn your LED on with one click and turn it off with double click. You will recognize, when it's off and you double click again, it will briefly flash, right after the first click. As I want to switch a relais, this is not good enough for me. Any suggestions?

warning: "DEPRECATED" redefined

I observe the following:

"/home/dns/.arduino15/packages/esp8266/tools/xtensa-lx106-elf-gcc/2.5.0-4-b40a506/bin/xtensa-lx106-elf-g++" -D__ets__ -DICACHE_FLASH -U__STRICT_ANSI__ "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/tools/sdk/include" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/tools/sdk/lwip2/include" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/tools/sdk/libc/xtensa-lx106-elf/include" "-I/tmp/arduino_build_36749/core" -c -Wall -Wextra -Os -g -mlongcalls -mtext-section-literals -fno-rtti -falign-functions=4 -std=gnu++11 -MMD -ffunction-sections -fdata-sections -fno-exceptions  -DNONOSDK22x_190703=1 -DF_CPU=80000000L -DLWIP_OPEN_SRC -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0   -DARDUINO=10813 -DARDUINO_ESP8266_GENERIC -DARDUINO_ARCH_ESP8266 -DARDUINO_BOARD="ESP8266_GENERIC" -DLED_BUILTIN=2 -DFLASHMODE_DOUT  -DESP8266 "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/cores/esp8266" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/variants/generic" "-I/home/dns/Arduino/libraries/JLed/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/ESP8266WebServer/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/ESP8266WiFi/src" "-I/home/dns/Arduino/libraries/AceButton/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/Ticker/src" "-I/home/dns/Arduino/libraries/LinkedList" "-I/home/dns/Arduino/libraries/UniversalTelegramBot/src" "-I/home/dns/Arduino/libraries/ArduinoJson/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/ESP8266HTTPClient/src" "-I/home/dns/Arduino/libraries/Uptime_Library/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/LittleFS/src" "-I/home/dns/Arduino/libraries/WiFiManager" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/DNSServer/src" "-I/home/dns/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/libraries/ESP8266mDNS/src" "/tmp/arduino_build_36749/sketch/MailBox.cpp" -o "/tmp/arduino_build_36749/sketch/MailBox.cpp.o"
In file included from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/../JsonVariantCasts.hpp:8:0,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/../JsonVariantBase.hpp:7,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/../JsonVariant.hpp:13,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/../JsonBuffer.hpp:12,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/JsonParser.hpp:7,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/JsonBufferBase.hpp:7,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/DynamicJsonBuffer.hpp:7,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson.hpp:9,
                 from /home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson.h:9,
                 from /home/dns/Arduino/libraries/UniversalTelegramBot/src/UniversalTelegramBot.h:26,
                 from /tmp/arduino_build_36749/sketch/Telegram.h:12,
                 from /tmp/arduino_build_36749/sketch/MailBox.cpp:10:
/home/dns/Arduino/libraries/ArduinoJson/src/ArduinoJson/Deserialization/../Polyfills/attributes.hpp:18:0: warning: "DEPRECATED" redefined [enabled by default]
 #define DEPRECATED(msg) __attribute__((deprecated(msg)))
 ^
In file included from /home/dns/Arduino/libraries/AceButton/src/AceButton.h:36:0,
                 from /tmp/arduino_build_36749/sketch/System.h:75,
                 from /tmp/arduino_build_36749/sketch/MySystem.h:42,
                 from /tmp/arduino_build_36749/sketch/MailBox.cpp:9:
/home/dns/Arduino/libraries/AceButton/src/ace_button/ButtonConfig.h:33:0: note: this is the location of the previous definition
   #define DEPRECATED __attribute__((deprecated))
 ^

ArduinoJson 5.13.5
AceButton 1.6.1

Even though in this case it is ArduinoJson who's guilty for DEPRECATED redefinition, the AceButton code does not seem to be protected from this either.

Remote inputs

Hi, In my sketch I want to be able to receive input information from remote inputs

I still don't know how to achieve the communication, if RS485 or other method, but let's consider it as a Port expander, is this library able to work?

Multiple buttons, issue with none release button....

Hello,

1st of all, thank you for Acebutton! - I've been using it for a while on ESP32s and mostly loving it. I'm having a bit of a problem (conceptually) with code for a held button (stuck button event) that I'd like to cater for.

Essentially I'd like to be able to use kEventPressed and kEventReleased in combination with another button to take a different action when one is held down.

I've attached separate handlers for each button, and use Pressed and Released but get no other buttons when the button is held down.

Doubt this is an issue with the library but would appreciate your advice.

Cheers!

How to correctly create array of buttons from existing buttons?

I can create an array of buttons by doing this:

AceButtons buttons[4];

but what if I want to create individual buttons and then add those to an array? The reason I want to do this is so that I can add specifiy configs to each button in an array. My best guess is this:

AceButton button1;
AceButton button2;
AceButton button3;
AceButton arrayOfButtons[] = {button1, button2, button3};

but I get errors that butto1, button2 etc. has no type but if I type them {AceButton button1 etc.) I get a redefinition error.

What's the correct way to do this?

Thanks and thanks for this amazing button library - I've tried quite a few and this is by far the best and most confgurable (and incredibly documented!)

Neil

Multiple Buttons

Hi there, just a quick question. Do you have a good example using AceButton for multiple independent buttons that are floating that go low when pressed. All are connected to individual pins on a teensy.

I've tried a number of the examples but they dont seem to work as described.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.