• Mystery solved... there is an issue with the setWatch() function: reference documentation of setWatch() has obviously changed. Originally, both pins AND built-in buttons had for edge option the same default: "both"... but at some time since this was built - almost 7 years ago Thu, Jul 24, 2014 1:25 AM • #1 - 'it' was decided to change that... and handle built-in buttons differently from pins... otherwise option object in Line 25 would include ..., edge:"both",...... last but not least, this code was developed on an original board using BTN1 / pin B12 as default and reference documentation valid at that time... haha... ---

    How to fix (quickly)? Clone the module to local and add the edge:"both" option...

    TL;DR:

    With the test rig below I just tested the original code - no line 144 (no edge option specified) - as inline module and got this output, which indicated that the button BTN1 works just as expected, but SWBtn just does not get events in button release... Then read the doc... n-times... until reading finally the fine print. This is the test output that shows that SWBtn never fires a .c(change state) method on button release:

    `____                 _
    |  __|___ ___ ___ _ _|_|___ ___
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v08 (c) 2019 G.Williams
    >
    >t1()
    =undefined
    >t2()
    =undefined
    >t3()
    SWBtn constructor: function(): 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();
        }
    } , button/pin: undefined , disabled: undefined
    SWBtn disable(): disable: undefined
    =undefined
    >mySWBtn
    =SWBtn: {
      f: function (k) { ... },
      b: B12, t: null,
      k: "",
      w: 2, d: false }
    BTN1:  1
    SWBtn c(changeState)(): event: { "state": true, "lastTime": undefined, "time": 1621850579.22702693939, "pin": B12 }
    BTN1:  0
    BTN1:  1
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621850579.64894866943, "time": 1621850585.34660243988, "pin": B12 }
    BTN1:  0
    BTN1:  1
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621850585.73445510864, "time": 1621850588.07980346679, "pin": B12 }
    BTN1:  0
    BTN1:  1
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621850589.15429878234, "time": 1621850591.14645862579, "pin": B12 }
    BTN1:  0
    >mySWBtn
    =SWBtn: {
      f: function (k) { ... },
      b: B12, t: null,
      k: "",
      w: 2, d: false }
    >
    `
    

    After addin line 144 - incl. option ...,edge:"both",..., this is the output:

    `
    >t3()
    SWBtn constructor: function(): 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();
        }
    } , button/pin: undefined , disabled: undefined
    SWBtn disable(): disable: undefined
    =undefined
    SWBtn c(changeState)(): event: { "state": true, "lastTime": undefined, "time": 1621851160.59780693054, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851160.59780693054, "time": 1621851161.43814468383, "pin": B12 }
    BTN1 detected L
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851161.43814468383, "time": 1621851163.25969314575, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851163.25969314575, "time": 1621851164.56733226776, "pin": B12 }
    BTN1 detected L
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851164.56733226776, "time": 1621851165.82536792755, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851165.82536792755, "time": 1621851165.95684528350, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851165.95684528350, "time": 1621851166.10351085662, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851166.10351085662, "time": 1621851166.22873687744, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851166.22873687744, "time": 1621851166.36725425720, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851166.36725425720, "time": 1621851166.48242568969, "pin": B12 }
    BTN1 detected SSS
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851166.48242568969, "time": 1621851168.26646137237, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851168.26646137237, "time": 1621851168.62865352630, "pin": B12 }
    BTN1 detected L
    SWBtn c(changeState)(): event: { "state": true, "lastTime": 1621851168.62865352630, "time": 1621851169.11860752105, "pin": B12 }
    SWBtn c(changeState)(): event: { "state": false, "lastTime": 1621851169.11860752105, "time": 1621851169.33710670471, "pin": B12 }
    BTN1 detected S
    `
    

    Test rig and adjusted setWatch() in code - now also fixed on github:

    // SWButtonTest.js 
    
    var log = function() { if (lon) loc.log.apply(loc,arguments); } // log wrapper
      , loc = console  // logging device that understands all what Console does
      , lon = true     // log or not to log...
      ;
    
    var  SWBtn = // require("SWButton");
    
    // inline module emulation
    (function(){
    var exports = {};
    // ----- module begin
    /* Copyright (c) 2014 allObject. MIT LICENSE. */
    /*
    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 of Espruino onboard BTN1 turns LED1 
    on, and one long and one short turn it off. Two long presses and 
    two long presses followed 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.
    NOTE: Works on 1v72+ due to export and usage pattern
    Usage examples:
    // 1st example just logs the key press pattern
    var SWBtn = require("SWButton");
    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k); // log key press pattern
      });
    // 2nd example tests key pattern and executes accordingly
    var SWBtn = require("SWButton");
    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();
        }
      });
    // 3rd example avoids chained ifs by:
    var functions = // function names match key press pattern
    { L:   function(){ LED1.set();   }
    , LS:  function(){ LED1.reset(); }
    , LL:  function(){ LED2.set();   }
    , LLS: function(){ LED2.reset(); }
    };
    var SWBtn = require("SWButton");
    var mySWBtn = new SWBtn(function(k){
        console.log("BTN1 detected " + k); // log detected key pattern and...
        if (functions[k]) { functions[k](); }; // ...dispatch if defined
      },BTN1,false);
    // 4th example assumes a push button on pin B2 connecting to +3.3
    pinMode(B2,"input_pulldown");
    var SWBtn = require("SWButton");
    var mySWBtn = new SWBtn(function(k){
        console.log("Btn on B2 detected " + k); // log key press pattern
      },B2);
    */
    /**
    SWBtn - SoftWare Butten 'class'
    - constructor - accepts these arguments:
      - f - (optional) anonymous, one argument accepting dispatching callback function
      - b - (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 - stores passed or set dispatching callback function
      - b - stores button / pin id
      - d - stores disabled (status) of button
      - t - stores 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()
    */
    var SWBtn = function(f,b,d) {
      if (lon) log("SWBtn constructor: function():",f,", button/pin:",b,", disabled:",d);
      this.f = (f) ? f : function(){};
      this.b = (b) ? b : 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). 
      NOTE2: The .k key property 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 (lon) log("SWBtn disable(): disable:",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
                           , edge:"both" // <----- required for built-in buttons!
                           , 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 (lon) log("SWBtn c(changeState)(): event:",e);
      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 timeout set in .c(e)
    - method - accepts no arguments
      Marks detected end of press sequence and invokes 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);
      }
     };
    exports = SWBtn;
    // ----- module end
    return exports;
    })();
    
    var iId, wId, mySWBtn;
    
    function t1(runOrStopBoolen) {
      if (runOrStopBoolen || typeof runOrStopBoolen == "undefined") {
        if (iId) t1(false);
        iId = setInterval(function(){
          digitalWrite(LED1,digitalRead(BTN1)); }, 100);
      } else {
          if (iId) iId = clearInterval(iId);
      }
    }
    
    function t2(runOrStopBoolen) {
      if (runOrStopBoolen || typeof runOrStopBoolen == "undefined") {
        if (wId) t2(false);
        wId = setWatch(function(){
            loc.log("BTN1: ",digitalRead(BTN1)); }
          , BTN1,{repeat:true, edge:"both", debounce:10});
      } else {
          if (wId) wId = clearWatch(wId);
      }
    }
    
    function t3() {
      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();
        }
      });
    }
    
    function onInit() {
      t1(); // turns red LED1 on when BTN1/pin is pressed/high
      t2(); // logs event state on press and on release
      t3(); // starts SWButton on BTN1/pin B12 (B12 on original board is BTN1)
            // ...and SWButton code includes some logging....
    }
    
    setTimeout(onInit,999);
    

    After upload, playing with button BTN1, tests t1() and t2() work just fine... t2() had initially NO edge:"both" option and it failed, so I added it, and added it then also to the SWButton code... see line 144 and all is as expected...

    Reference documentation added some more fine print since inception of setWatch() 7+ years ago...

About

Avatar for bigplik @bigplik started