• Note: updated code in the replies

    Inspired by the great number of user input/output elements - BTN1 button
    and LEDs LED1, LED2, and LED3 - on the Espruino board right out-of-box,
    my first hands-on experience and fun was coding running led lights -
    red-green-blue-... - controlled by the BTN1 button.

    Light show control 'buttons' include:

    1. Start the light show.
    2. Stop the light show.
    3. Make the light show run faster.
    4. Make the light show run slower.

    For cycling through the LEDs using variable addressing vs. cascaded if then else-if,
    I found related forum entry at http://forum.espruino.com/conversations/­573 -
    What is LED1/A13? (addressing pins via variables). Below you find the code
    for the RLL Running LED Light singleton/class implementation with variable defined LED access and operations controlling buttons.

    The running led light RLL implementation began as a torso that just cycled through
    the LEDs on loading of the code.

    In order to control the light show, following methods got added to RLL singleton/class:

    1. .r(boolean) for run(true/false) aka start and stop: RLL.r(true); RLL.r(false);
    2. .t() for toggling start and stop the light show: RLL.t();
    3. .f() for making the light show run faster: RLL.f();
    4. .s() for making the light show run slower: RLL.s();

    Start/stop / toggling between start/stop was easy by just detecting any press -
    actually release (and stay released for some time) - of the BTN1 button.
    Detection of a transition from pressed to released with staying released for a
    defined debouncing time is managed by a timer with ongoing setTimeout and
    comparing previous versus current button BTN1 state / digitalRead(BTN1).

    Inspired by the lots of timers and setTimeouts that made the running led lights and
    the start/stop/toggle button work, I wanted to use them also for not just only noticing
    button presses and eventually counting them, but also for detecting the duration of
    the presses and sequences of such presses.

    Sequences of short and long presses would allow me to create with one hardware button many software button or commands to control multiple things with one single control. The same thing did Samuel B. F. Morse - see http://en.wikipedia.org/wiki/Morse_code - tocode letters, digits, and special characters into simple sequence of short and long "on"s, send those over a (telephone) line (wire), and create short and long dashes by a pen pushed down on a moving paper stripe by a solenoid.

    Mislead by an inaccurate, early publication stating that Espruino would not support the use of interrupts generated by pin state transitions, I built everything in
    software using just timers / setTimout() and testing pin states w/ digitalRead().

    @Gordon's gentle nudge - Have you seen setWatch? - made me refactor and extend the code in order to became a general purpose SWBtn software button component - which soon will be available as a module.

    function SWBtn(fnc,btn,d) { // SoftWare Butten - constructor args:
      // fnc function to call, btn (default: BTN1), disabled (default: false)
      this.f = (fnc) ? fnc : function(){};
      this.b = (btn) ? btn : BTN1;
      this.t = null; // timeout after last release
      this.k = "";   // key build-up (sequence of S and L aka Morse's dots and dashes)
      this.w = null; // set watch
      this.disable(d);
     }
    SWBtn.prototype.C = // config
      { B: 20    // debounce [ms]
      , L: 0.250 // min Long press [s]
      , P: 220   // min Pause [ms]
      , D: 10    // fnc exec delay [ms]
      };
    SWBtn.prototype.disable = function(b) { // disable
      if (b) { 
        if (this.w) { 
          clearWatch(this.w);
          this.w = null; 
        }
      } else {
        if (!this.w) {
          var _this = this;
          this.w = setWatch(function(e){ _this.c(e); }, this.b, { repeat:true, debounce:_this.C.B });
        }
      }
     };
    SWBtn.prototype.c = function(e){ // change of state - called by set watch
      // e = {time:float, lastTime:float, state:bool}.
      if (e.state) { // press
        if (this.t) clearTimeout(this.t);
      } else { // release
        this.k = this.k + ((e.time - e.lastTime < this.C.L) ? "S" :"L");
        var _this = this;
        this.t = setTimeout(function(){ _this.e(); }, this.C.P);
      }
     };
    SWBtn.prototype.e = function() { // ended - called by timeout > Pause
      this.t = null;
      var _k = this.k;
      if (_k.length > 0) {
        this.k = "";
        var _this = this;
        setTimeout(function(){ _this.f(_k); },this.C.D);
      }
     };
    

    (1 of 2) - to be continued...
    Software Buttons - Many buttons from just one hardware button

  • (2 of 2) continued. Software Buttons - Many buttons from just one hardware button

    SWBtn(fnc,btn,d) is the software button class or constructor method. It accepts three (3) parameters:

    1. fnc one parameter function to be invoked on press sequence detection.
    2. btn symbol identifying the button or input pin; default: BTN1 / B12.
    3. d boolean disabled; default: false; allows to create button in disabled state.

    .disable(b) method disables button according to passed b boolean interpreted value.

    .c(e) method is invoked on every button / pin state change - press or release.

    .e() method is invoked on every sequence detection and invokes (timer delayed) the function provided at construction or as set later.

    .C prototype object is the configuration object holding the timing values shared by all instantiated SWBtn software buttons.

    Basic usage example:

    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k);
      });
    

    The code above creates an enabled software button. On press sequence detection it invokes the passed anonymous function with the pressed sequence as parameter value, for exmple "S" for one short press, "SS" for two, and "SL" for one short and one long press, etc. The implementation of the function writes the press sequence to the console:

    1v64 Copyright 2014 G.Williams

    ``` =undefined```
    ``` BTN1 detected S```
    ``` BTN1 detected SS```
    ``` BTN1 detected SL```
    ``` > ```
    
    You may wonder why the button's returned key value for the press sequence is not a series of dots (.) and dashes (-). The reason is simple: the key as returned can be used as valid property or function name (of an object).
    
    For example, the following code - entered into the IDE command pane after sending the SWBnt class code to the Espruine - counts how many of the various sequences have been  pressed,
    
    

    var presses = {};
    var mySWBtn = new SWBtn(function(k){

    presses[k] = ((presses[k]) ? presses[k] : 0) + 1;
    

    });

    and entering the following code into the IDE command pane gives a report about the counted various press sequences:
    
    

    for (var v in presses) console.log(

    [v," was pressed ",presses[v]," time(s)."].join("")
    

    );

    ```S was pressed 6 time(s).```
    ```L was pressed 5 time(s).```
    ```LSL was pressed 1 time(s).```
    ```SLLS was pressed 2 time(s).```
    
    To print this report and reset the counters on 'pressing' the "SSSSS" software button - 5 short presses of Btn1 - set following function on existing mySWBtn (or pass it on in a new creation).
    
    

    mySWBtn.f = function(k) {

    presses[k] = ((presses[k]) ? presses[k] : 0) + 1;
    if (k == "SSSSS") { // 'key' for triggering the presses report
        console.log("*** Presses since creation/last report:");
        for (var v in presses) console.log(
            [v," was pressed ",presses[v]," time(s)."].join("")
        );
        presses = {}; // reset counters
    }
    

    };
    ```
    The above example shows that the function can be set or changed on a button after its creation, and so can you also change the C(onfiguration) parameters for the timings - but remember, you will change the timing for every SWBtn - because they shard the one and only C(onfiguration).

    To control the RLL running led light shown in forum comment at http://forum.espruino.com/comments/30838­/,
    replace the Btn1 code with the SWBtn code, and replace the last line with
    var rllBtn = new SWBtn(function(k){ muej.pub(k); });

    Do disable and re-enable the button, invoke the .disable() method:
    rllBtn.disable(true); // disable
    rllBtn.disable(false); // re-enable
    Latter is the same as:
    rllBtn.disable(); // re-enable - :-o - oops...
    Above is not very obvious, but still as designed: the missing boolean argument evaluates to false... ;-)

    A button can be created in disabled state:
    var rllBtn = new SWBtn(function(k){ muej.pub(k); },true);
    This is especially helpful when other initialization processes have to happen or complete before user input can be accepted. To enable the button at desired point in time, just invoke the .disable() method:
    rllBtn.disable(false); // enable

  • Thanks! It's really good to have broken this out into an Object of its own...

    Do you think you could try packaging it up into a module? Instructions here.

    I guess it might be handy to rename SWBtn.prototype.d to SWBtn.prototype.disable... It might make code using it a bit more readable...

  • .d() changed to - more readable - .disable().

    Certainly, I will package it up as a module.

  • SWBtn hardened and commented in preparation for publishing as a module.

    This is a Request For Comments (RFC). Feedback for code fixes and comment/wording and functional enhancements are very much appreciated.

    /* Copyright (c) 2014 allObjects. See the file LICENSE for copying permission. */
    
    /*
    Software Buttons - Many buttons from just one hardware button
    
    SWBtn class - instances of SWBtn makes many buttons out of 
    just one hardware button by detecting sequences of short and 
    long presses and passing them on to a user defined, dispatching 
    callback function.
    
    For example, one long press turns LED1 on, and one long and 
    one short turn it off. Two long presses and two long presses 
    followd by a short one do the same to LED2, etc.
    
    Each detected sequence is passed in form of a string of "S"s 
    and "L"s to the user provided callback function. The callback 
    function uses the string as a key to call assigned functions
    like dispatcher.
    
    Usage example:
    
    require("SWBtn");
    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k); // log and/or dispatch
      });
    
    */
    
    /**
    SWBtn - SoftWare Butten 'class'
    - constructor - accepts these arguments:
      - fnc - (optional) anonymous, one argument accepting dispatching callback function
      - btn - (optional) button/pin id - default is BTN1
      - d   - (optional) boolean interpreted disable flag - default false
              Allows button to be creted in disabled state in order 
              for (other) initializations to complete before being
              enabled with .disable(0) / .disable(false) / .disable()
              method invocation.
    - instance properties:
      - f - storex passed or set dispatching callback function
      - b - storex button / pin id
      - d - stores disabled (status) of button
      - t - xgofdx timeout for sequence end / pause detection
      - k - stores build-up key store holding on to the "S"s and "L"s of a squence
      - w - stores hold on to the watch set with setWatch()
    */
    function SWBtn(fnc,btn,d) {
      this.f = (fnc) ? fnc : function(){};
      this.b = (btn) ? btn : BTN1;
      this.t = null;
      this.k = null;
      this.w = null;
      this.disable(d);
     }
    
    /**
    .C - Constants / Configuration - defining the timings 
    - shared by all instances of SWBtn:
      - B - integer - debounce [ms]
      - L - float   - min Long press [s]
      - P - integer - min Pause [ms]
      - D - integer - delay of fnc function invocation [ms]
      Pressing a button / keeping a pin low for .C.L seconds or more is detected
      as a long press when unpressed / released / pin turns high and and adds an 
      "L" to .k key (press sequnce) - otherwise a short press is detected and an 
      "S" is adde - and the .t timeout is set with .C.P Pause time and .e() ended
      call back for press sequence end detection and processing (invocation of
      user defined - ,k key decoding dispatch - function).
    */
    SWBtn.prototype.C =
      { B: 20
      , L: 0.250
      , P: 220
      , D: 10
      };
    
    /**
    .disable(b) - disables/enables button
    - method - accepts one argument
      - b - boolean - (optional) boolean interpreted disable flag - default false
      Used to (temporarily) disable the button (also used in constructor).
      It clears/sets the hardware / pin watch using clearWatch() / setWatch() functions.
      NOTE1: When button is disabled while press sequence is going on, sequence end is
        not detected, but partial sequence is still stored in .k key property (but does 
        not include an ongoing press / pin low). 
      NOTE2: The .k key propery is cleared (set to "") when button is (re-)enabled.
      NOTE3: any passed parameter that evaluates to false in an 'if (parameter)' and 
        omission of parameter enable the button: .disable(false), .disable(0),
        .disable(""), .disable(''), .disable(null), .disable(undefined), and .disable(),
        all these invocations enable the button... ;)
    */
    SWBtn.prototype.disable = function(b) {
      if (b) {
        if (this.w) { 
          this.d = true;
          clearWatch(this.w);
          this.w = null; 
          if (this.t) {
            clearTimeout(this.t);
            this.t = null;
          }
        }
      } else {
        if (!this.w) {
          this.d = false;
          this.k = "";
          var _this = this;
          this.w = setWatch(function(e){ _this.c(e); }, this.b, { repeat:true, debounce:_this.C.B });
        }
      }
     };
    
    /**
    .c(e) - button/pin button/pin state change callback - invoked by Espruino
    - method - accepts one e event argument (object) 
      Espruino reference for .setWatch() defines e event object as:
      - time     - float   - time of this state change [s]
      - lastTime - float   - time of last such state change [s]
      - state    - boolean - current state of the button / pin
      Notes button/pin status and - on unpress/release state - 
      appends "L"(ong) or "S"(short) to .k key (sequence) and 
      sets .t timeout to .C.P Pause for sequence end detection
    */
    SWBtn.prototype.c = function(e){ // change of state - called by set watch
      if (e.state) {
        if (this.t) {
          clearTimeout(this.t);
          this.t = null;
        }
      } else {
        this.k = this.k + ((e.time - e.lastTime < this.C.L) ? "S" :"L");
        var _this = this;
        this.t = setTimeout(function(){ _this.e(); }, this.C.P);
      }
     };
    
    /**
    .e() - sequence ended timeout callback - invoked by .t timout set in .c(e)
    = method = accepts no arguments
      Marks detected end of press sequence and invekes user provided .f 
      callback function in a setTimeout() with .C.D delay.
    */
    SWBtn.prototype.e = function() {
      this.t = null;
      var _k = this.k;
      if (_k.length > 0) {
        this.k = "";
        var _this = this;
        setTimeout(function(){ _this.f(_k); },this.C.D);
      }
     };
    
  • Testing robustness of SWBtn's .disable() method

    Copy SWBtn and test code at the bottom of this post into edit pane and send it to Espruino.

    Basically, there will be intervals with enabled and disabled SWBtn. During and straddling those intervals you will perform press sequences.

    Be prepared to timely perform press sequences as instructed below while test code shows passed seconds in command window.

    The test code will do this:

    1. Starts mySWBtn in enabled state and shows passed seconds in command pane
    2. Disables mySWBtn after 7 seconds (7 seconds after start)
    3. Re-enables mySWBtn after 7 more seconds (14 seconds after start)

    You perform these timely sensitive press sequences:

    1. About 1 to 2 second after test code started, begin and complete a press sequence with a few short presses.
      2 . About 4 to 5 seconds after test code started, begin a press sequence with at least four (5) long presses that lasts beyond second 7 or 8 after start. Try to make at least three (3) or more long presses by second 7.
    2. Before 14 seconds after test code started, perform a complete press sequence with a few short presses. Note that press sequence has to be completed before second 10 after start.
    3. After 14 seconds the test code started, perform a press sequence with just one (1) or two (2) long presses.

    The output you will see in the command pane should look like this (on [1v67] - at this time the most recent version):
    ...
    Second: 1
    Second: 2
    BTN1 detected SSS
    Second: 3
    Second: 4
    Second: 5
    Second: 6
    Second: 7
    mySWBtn disabled - Partial press sequence: .k={LLL}
    Second: 8
    Second: 9
    Second: 10
    Second: 11
    Second: 12
    Second: 13
    Second: 14
    mySWBtn re-enabled
    Second: 15
    Second: 16
    BTN1 detected LL
    Second: 17
    Second: 18
    ...

    ...which means, that:

    1. Partial long presses sequence is noticed on disablement
    2. Short presses sequence between second 7 and 14 is not noticed
    3. Press sequences after re-enablement are not affected by partial sequences

    Note1: press the reset button to stop the test code's seconds to keep going on. Disconnect is not enough (After reconnect, passed seconds output shows at once). Of course, powering off does stop it too,

    Note2: At one time I got Uncaught Error: Unknown Timeout in the command pane (plus details about where in the code it happened) - this happened before I got all my robustness ducks in a row. But it raised the question:

    Should clearTimeout(withInvalidTimeoutId) or clearTimeout(withTimedOutTimeoutId) even 'throw' this or any error? - afaik, JavaScript in browsers do not 'throw' any error under these circumstances... may be those implementations arn't up to spec (yet)... and what about me... - :( oooops! - ?

    For fun, extend (and post your) test that begins with a disabled button and shows that it is disabled and starts observing press sequences when enabled.

    Notice that when a press sequence is going on and the button is (re-)enabled, only the part that leaps into the (re-)enabled period is picked up... and if the (re-)enablement happens during a long press, this long press may be detected as a short press.

    So far, only software and a (external) button operator are used for the testing. Adding some hardware - such as a wire that connects an output (configured) pin with and input (configured) pin or even using another Espruino board - and creating a SWBtn on the output pin controlled input pin allows to write complete software driven testing of the SWBtn. For sure something worth to be tackled at a later point in time.

    var mySWBtn = new SWBtn(function(k){ // create mySWBtn (on BTN1 and enabled)
        console.log("BTN1 detected " + k);
      });
    var secs = 0, t1 = 7000, t2 = 7000; // define seconds and t1/t2 inteval timings
    setInterval(function() { // get the seconds going 1,2,3...
      secs = secs + 1;
      console.log("Second: " + secs);
     },1000);
    setTimeout(function(){ // get the intervals t1 and t2 going with disable and re-enable
      mySWBtn.disable(1);
      console.log(["mySWBtn disabled - Partial sequence: {",mySWBtn.k,"}"].join(""));
      setTimeout(function(){
        mySWBtn.disable();
        console.log("mySWBtn enabled");
      },t1); // 7 [s]
     },t2); // 7 [s]
    
  • how to use this in some simple example with LED function?
    i mean let say when 1 short press (SS) is detected then turn on LED1, when LL turn off etc.?

  • Example below assumes default button BTN1.

    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k);
        if/*sel*/ (k === "S") { LED1.set(); // one short press on
        } else if (k === "L") { LED1.reset(); // one long press off
        }
      });
    

    If you have a button on a pin - for example, A8 - use this:

    var mySWBtn = new SWBtn(function(k){
        console.log("Button on A8 detected " + k);
        if/*sel*/ (k === "S") { LED1.set(); // one short press on
        } else if (k === "L") { LED1.reset(); // one long press off
        }
      },A8);
    

    More sophisticated, you can go with this:

    var fs = // functions 
    { "S": function() { LED1.set(); }
    , "L": function() { LED1.reset(); }
    };
    var mySWBtn = new SWBtn(function(k){ if (fs[k]) fs[k](); }, A8);
    

    Note: Code only partially tested. ;-)

  • thank you, I'll test it

  • @allObjects did you ever submit this as a module?

  • Yes... but I posted on there twice asking if the webpage that goes with it could be fixed - no reply yet :)

    @allObjects would you mind fiddling with the documentation file?

    For now, it can be used with:

    var SWBtn = require("https://raw.githubusercontent.c­om/muet/EspruinoDocs/master/modules/SWBu­tton.js");
    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k); // log key press pattern
        if        (k === "L"  ) { LED1.set();
        } else if (k === "LS" ) { LED1.reset();
        } else if (k === "LL" ) { LED2.set();
        } else if (k === "LLS") { LED2.reset();
        }
      });
    
  • @Gordon, yes. My working w/ git needs a bit shoe-horning. - Thanks for the great example.

  • @allObjects - wow - this is very helpful and can be used as button extension for Puck.js

    I use it like this

    /* 
    S :  single short touch to switch on 
    SS : double short touch to switch off
    */  
    var SWBtn =require("https://raw.githubusercontent.­com/muet/EspruinoDocs/master/modules/SWB­utton.js");
    
    var mySWBtn = new SWBtn(function(k){
          console.log("BTN1 detected " + k); // log key press pattern
          if (k === "S"  )
            LED3.set();
          else if (k === "SS" ) 
            LED3.reset();
    });
    

    Thanks for sharing !

  • I was wondering how to code given behaviour:
    increment counter when button is pressed
    keep incrementing when button is held down

    such a behaviour is often implemented for setting hours etc.

    I've tried this:

    var presses=0;
    //presses count
    pinMode(A7, "input_pulldown");
    // software pulldown
    
    setWatch(function(){
      clearInterval();
      presses++;
      setInterval(function(){
        if(digitalRead(A7)) {
          presses++;
          console.log(presses);
        } else {clearInterval();}
      }, 500);
      console.log(presses);
    },A7,{ repeat : true, debounce : 30, edge : "rising" }  );
    

    It works... but it's clearing other intervals.
    Is there a cleaner/better way to code this?
    If you know how to code it better, or have your own solution please share

    Thanks

  • You can identify an interval by naming it
    and use the name to clear only that interval

    var id = setInterval(function () { print('foo'); }, 1000);
    
    clearInterval(id)
    

    https://www.espruino.com/Reference#l__gl­obal_setInterval

    https://www.espruino.com/Reference#l__gl­obal_clearInterval

  • @ancienthero, the idea of holding and measuring how long a button is held pressed conflicts with the idea of detecting series off short and long presses. To cover holding and holding how long beyond a short or long press has to be managed by some other module.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Software Buttons - Many buttons from just one hardware button

Posted by Avatar for allObjects @allObjects

Actions