-
@JackHoward - and @Gordon - grouping is accomplishable and was done in the past - with lots of work, for example by creating a page for each of the groups... More recently, tagging and presenting tag clouds have replaced it, because they support also favorably searching, which is dynamic, ad-hoc, on demand grouping. Just think about the very spread hashtagging - #tagvalue
@Gordon, does the environment you use for the examples, (blog) conversations, etc. support tagging options? I know that most (blog) conversation environments have the option of blocking or selective enabling for posts and and with that also the tagging. Moving the examples in a tagging enabled context with (moderator or author) controlled posting and tagging would be a good solution, no matter what needs will come up. Is there some documentation about the environment you are using for the Espruino Web site?
Edit: I had great laugh reading my post after posting: #tagvalue got highlighted and presented as a link. Clicling it threw the very same post right back to me... So no (real) work needed for posts... may be just a simple nudging of the poster by adding a field for tags in the post form, or at least a solicitation with the explanation that hash (#) prefixed terms are detected as hashtags. Too bad - more: really bad, (?) Formatting help does not even mention the tagging option {{{:((((>>>. After a while, the nudging for tagging can be removed, because readers will notice the tagging options by the posts with tags they read.
-
first of all, I'd like to commend you - as simple as that. You stepped into this endeavor with the right motives, and you show an incredible spirit, creativity, dedication, perseverance - and last but not least - awesome productivity.
With most products the question is how to balance engineering and marketing. Both cost, latter opens the wallet, and - or but - first one keeps it open (after discovering that it was not just a marketing bluff and a good sales pitch). Too much engineering (time) upfront makes the money (ROI) come (back) in too late... or too little or never at all, because by the time the product hits the market, the requirements/demands have already changed (...that's why 'agile methodology' came to be to help with this dilemma - especially in the domain of software development - it helps to be shippable at any time, ...on and with every increment in business value.)
You may not have promoted Espruino with the term 'agile product development', but you live it - include it in your new home page ;-)... All of us Espruino users experience agile through Espruino's evolution of firmware and (Web) development environment. JavaScript is inherently and thus exceptionally well suited for the agile approach. The built-in, straight forward and simple extensibility is architected into its implementation of its object-orientation and runtime engine (VM). I could imagine that those aspects were crucial reasons enticing you to call Espruino into live.
The next paragraphs belong more into the topic 'About JavaScript'. I like to pick them up in this context, because for me they are part of the overall, high-level architecture of the Espruino product as a whole - and its mission to make it easily accessible to a broad community. Part of this 'easily' is guaranteed with feature rich Espruino board working righ out-of-box: plug in and use - with no hick-up(s). The (Web) development environment shows even a ready to example in the editor. Therefore, I'd better had said: just plug and play! - See yourself what got me hooked and started at http://forum.espruino.com/conversations/1781/: Running LED lights - with the three (3) on-board LEDs - controlled by three (3) buttons... oops: only one user button on the board though... ;-) Therefore: buy the original Espruino board! None of the few saved $, £, ¥, or what ever currency you prefer - may be esteemed and stable CHFs - bring you back the time you lost tinkering with this 'other' board and most likely made you gave up on it, or even worse, giving up on the technology all together,... and missing out for the rest of your life... (...what a big mouth full). Generally, I'm a 'cheap' guy - or more nicely: I like frugality - overcome with creativity - hence the software buttons ;-) - but I have no regrets what so ever having bought into the very solid engineered Espruino board. - This much for: 'Why to buy the genuine Espruino board'.
//////////////////////
As a language and implementation, JavaScript follows more Smalltalk's than Java's model: In JavaScript - as well as in Smalltalk - the prototypes or classes - respectively - can be (incrementally) extended at practically no cost, where as with Java either a subclass or a modified/extended clone has to created. JavaScript goes even further than Smalltalk: in JavaScript one can extend just one or selected particular instance(s) versus all. Agreed, with Java requiring subclasses for extensions, future coexistence/integration is much less a source for (name) conflicts. The limits of extending classes (by adding methods to the existing class) and subclassing has anyway to be overcome with composition... and even there, JavaScript has its advantages. Btw, for how to apply the class hierarchy pattern of class-based object orientation to prototype/object-based JavaScript, please take a look at http://dojotoolkit.org - in particular at http://dojotoolkit.org/documentation/tutorials/1.7/declare/. The tutorial provides a very nice, concise explanation of how prototypical/object-based object-orientation's binding happens. It helps to grasp the very dynamic oo nature of JavaScript:
It is important to have a clear understanding of prototypical inheritance. When a property is read from an object instance, the instance itself is first inspected to see if the property is defined on it. If not, the prototype chain is traversed and the value from the first object in the chain that has the property defined is returned. When a value is assigned to a property it is always on the object instance, never the prototype. The result of this is that all objects that share a common prototype will return the same value for a property defined on the prototype, unless the value has been set on the instance. This makes it easy to define default values for primitive data types (number, string, boolean) in your class declaration and update them on instance objects as needed. However, if you assign object values (Object, Array) to a property on the prototype, every instance will manipulate the same shared value. (from: Classy dojo, dojotoolkit.org)
I'd like to see something like declare natively implemented in Espruino's runtime. It hides the somewhat cumbersome, JavaScript native Xyz.prototype.abc = ... syntax and much more, such as invoking the super classe's method (which means even more reuse, more extension with less code writing - a nice feature in a memory constrained setup). The availability and use of 'declare' fosters the thinking in classes rather than a collection of functions pinned to a (pseudo) object, as seen a lot in Java with static methods of a Java class - or simply - a function library, where the subject - in this case the this-object, has to be passed as well in order to work (oo). On the other hand, it cannot be that bad. Java 8 finally learns what JavaScript was already born with: Lambda expression - functions / methods as parameters. Never too late to learn something new and become smarter... that's though true for JavaScript as well... ;) ...will see what ECMA is coming up with in the times to come.
Have a great Espruino time! - until next time - allObjects
-
Nice modern, bright design. And what I like the most: it is responsive - in terms of responsive web design. For the (language) reference page I have some suggestions: clicking on a heading could go back into the (sub) index. This back/cross referencing would help a great deal when using the page as cheat sheet. ;-)
-
Was working on getting a video going... don't have the hang on focus yet... ;) ...will get there. Movie is now added.
Regarding the speed: operation is event/timer driven. Operation is basically a continuous repetition of 4 phases:
- advance half a cell, draw two lines with small angle
- 'undraw' w/ background color what was drawn in previous phase
- advance half a cell, draw two lines with wide angle
- 'undraw' w/ background color what was drawn in previous phase
The phases are optimized for the seamless, timer driven repetition in pairs of two with no logic in them what so over... all logic happens by 'direct' and indirect addressing... (invocation of functions - 1 per phase - stored in an array and called by a cycling index and index for next phase set in the function).
Because of the optimization, a start and stop cycle exist that compensate where needed (for now only the stop):
- Start cycle - is a phase 1 (or 3) starting from a x/y position compensated by the 'advance half a cell'
- Enter repetition mode using a timeout which then executes either phase 2. and 3. or 4. and 1. and calls itself in the next timeout.
- Stop cycle - is the phase to remove the last drawing by the undraw, which is either a phase 2. or 4.
Each phase sets the next phase index so that on timeout the next function in the cycle is called (indexes in this lists off by 1+):
- phase 1 sets index to 2.
- phase 2 sets index to 3.
- phase 3 sets index to 4.
- phase 4 sets index back to 1
Since each direction - E, S, W, N - have all their own implementations, they have all their own cycles - but all phases are stored in the same array:
- E. - 1, 2, 3, 4
- S. - 5, 6, 7, 8
- W. - 9, 10, 11, 12
- N. - 13, 14, 15, 16
In other words, every cycle's last phase sets the index for the next phase to the first phase of the cycle.
The discussion 'where to draw the line' between high-level (language and device(s) agnostic) and low-level (language and device(s) dependent) application and driver implementation in the hybrid 'over all implementation' may take place in another post...
- advance half a cell, draw two lines with small angle
-
[2of2] continued Exploring 2.8" Color TFT Touch LCD by Pacman Game attempt
Add this code to the initial one for having pacman continually walking around ins a square.
// pacman walking around in a square (until w is set to false in cmd pane): var d, w; function r() { G.c(); G.sClr(2); G.g.drawRect( x0 - (Math.round(P.w/2) + P.b) , y0 - (Math.round(P.w / 2) + P.b) , x0 + ((nmx + 1) * P.w + 1 ) , y0 + ((nmx + 1) * P.w + 1 ) ); G.g.drawRect( x0 + (Math.round(P.w/2) + P.b) , y0 + (Math.round(P.w / 2) + P.b) , x0 + ((nmx - 1) * P.w - 1 ) , y0 + ((nmx - 1) * P.w - 1 ) ); d = 0; // start direction (E) and ... P.s0(d,x0,y0); // start position n = 0; w = true; rx(); } function rx() { if (n < nmx) { n++; P.n(); setTimeout("rx();",tt); } else { P.f[P.s](); if (w) { d = (d < 12) ? d + 4 : 0; P.s0(d,P.x,P.y); n = 0; setTimeout("rx();",1); } } }
In IDE's command / console window enter:
- r(); // starts the walking in the NW corner
- w = false; // stops the walking at the next corner
PS: I used the 2.8" TFT LCD with Touchscreen Breakout Board w/MicroSD Socket from http://www.adafruit.com/products/1770 and wired according to http://www.espruino.com/ILI9341 Note that B2 goes to Lite on board (LED on module). See picture and watch (short, low-res pacmanvideo.mp4) movie...
- r(); // starts the walking in the NW corner
-
ADDED 2021/10/04: ...great help for coding the underlaying ILI9341 display controller
The moment I did some basic setup, set some pixel and drew some lines, I knew that performance is a challenge... especially when driving the display over SPI... Therefore, graphics take a bit a back seat vs. simplification. ;-)
Here some basic code:
// Exploring 2.8" Color TFT Touch LCD by Pacman Game attempt // [0r0] - [1v67] release / version info // basic setup: light on, SPI, graphics context B2.set(); SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000}); var g = require("ILI9341").connect(SPI1, B6, B8, B7, function() {}); var G = // Game { g: null // Grapics , c: function() { this.g.clear(); } // clear , clrs: // colors (6 bit color depth, 262K colors) [ 0 // black , 63 * (64*64) + 63 * (64) + 0 // yellow , 0 * (64*64) + 0 * (64) + 63 // blue , 63 * (64*64) + 63 * (64) + 63 // white ] , clr: null // color index , sClr: function(cdx) { if (this.clr != this.clrs[cdx]) { this.clr = this.clrs[cdx]; this.g.setColor(this.clr); } } , ini: function(g) { this.g = g; this.c(); } }; var P = // Pacman { g: null // Graphics , x: 0 // pos x , y: 0 // pos y , d: null // direction E, S, W, N phases start = 0, 4, 8, and 12) , s: 0 // next state 0..15, show and hide states for mouth closed and open // advancing halft a cell with before showing // (0,4,8,12 = on cell center, mouth closed, facing E,S,W,N) , f: null // function for each state (0..15) , w: 10 // with , b: 6 // base , o: 8 // open , c: 3 // closed , sp: function() {} , se: function(i,m) { G.sClr(i); g.drawLine(this.x - this.b, this.y, this.x + this.b, this.y - m); g.drawLine(this.x - this.b, this.y, this.x + this.b, this.y + m); } , ss: function(i,m) { G.sClr(i); g.drawLine(this.x, this.y - this.b, this.x - m, this.y + this.b); g.drawLine(this.x, this.y - this.b, this.x + m, this.y + this.b); } , sw: function(i,m) { G.sClr(i); g.drawLine(this.x + this.b, this.y, this.x - this.b, this.y - m); g.drawLine(this.x + this.b, this.y, this.x - this.b, this.y + m); } , sn: function(i,m) { G.sClr(i); g.drawLine(this.x, this.y + this.b, this.x + m, this.y - this.b); g.drawLine(this.x, this.y + this.b, this.x - m, this.y - this.b); } , s0: function(s,x,y) { this.s = s; this.x = (s == 0) ? x - this.w : (s == 8) ? x + this.w : x; this.y = (s == 4) ? y - this.w : (s == 12) ? y + this.w : y; this.f[this.s](); } , n: function() { this.f[this.s](); this.f[this.s](); } , ini: function(g) { this.g = g; var _this = this; this.f = [ function() { _this.x = _this.x + _this.w; _this.se(1,_this.c); _this.s = 1; } , function() { _this.se(0,_this.c); _this.s = 2; } // 1/2 , function() { _this.x = _this.x + _this.w; _this.se(1,_this.o); _this.s = 3; } , function() { _this.se(0,_this.o); _this.s = 0; } // 1/1 E , function() { _this.y = _this.y + _this.w; _this.ss(1,_this.c); _this.s = 5; } , function() { _this.ss(0,_this.c); _this.s = 6; } // 1/2 , function() { _this.y = _this.y + _this.w; _this.ss(1,_this.o); _this.s = 7; } , function() { _this.ss(0,_this.o); _this.s = 4; } // 1/1 S , function() { _this.x = _this.x - _this.w; _this.sw(1,_this.c); _this.s = 9; } , function() { _this.sw(0,_this.c); _this.s = 10; } // 1/2 , function() { _this.x = _this.x - _this.w; _this.sw(1,_this.o); _this.s = 11; } , function() { _this.sw(0,_this.o); _this.s = 8; } // 1/1 W , function() { _this.y = _this.y - _this.w; _this.sn(1,_this.c); _this.s = 13; } , function() { _this.sn(0,_this.c); _this.s = 14; } // 1/2 , function() { _this.y = _this.y - _this.w; _this.sn(1,_this.o); _this.s = 15; } , function() { _this.sn(0,_this.o); _this.s = 12; } // 1/1 N ]; } }; setTimeout(function(){ G.ini(g); P.ini(g); },1500); var tt = 100; // [ms] timout time for next phase, 4 phases per cell) var x0 = 20, y0 = 70; // left, top (left top corner of square w/ nmx cells) var n, nmx = 10; // n = cell, nmx = max cells (in x and y direction) // pacman walking E, S, W, N: function ne() { P.s0( 0,x0 ,y0 ); n0(); } function ns() { P.s0( 4,x0+nmx*P.w,y0 ); n0(); } function nw() { P.s0( 8,x0+nmx*P.w,y0+nmx*P.w); n0(); } function nn() { P.s0(12,x0 ,y0+nmx*P.w); n0(); } function n0() { n = 0; setTimeout("nx();",tt); } function nx() { if (n < nmx) { n++; P.n(); setTimeout("nx();",tt); } else { P.f[P.s](); } }
To let Pacman walk E (x-axis), S (y-axis), W, or N, enter in IDE's command window (one at a time):
- ne();
- ns();
- nw();
- nn();
[1of2] to be continued... Exploring 2.8" Color TFT Touch LCD by Pacman Game attempt
- ne();
-
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:
- Starts mySWBtn in enabled state and shows passed seconds in command pane
- Disables mySWBtn after 7 seconds (7 seconds after start)
- Re-enables mySWBtn after 7 more seconds (14 seconds after start)
You perform these timely sensitive press sequences:
- 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. - 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.
- 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:
- Partial long presses sequence is noticed on disablement
- Short presses sequence between second 7 and 14 is not noticed
- 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]
- Starts mySWBtn in enabled state and shows passed seconds in command pane
-
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, edge:"both", 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); } };
-
-
(2 of 2) continued. Software Buttons - Many buttons from just one hardware button
Constructor and methods:
SWBtn(fnc,btn,d) is the software button class or constructor method. It accepts three (3) parameters:
- fnc one parameter function to be invoked on press sequence detection.
- btn symbol identifying the button or input pin; default: BTN1 / B12.
- d boolean disabled; default: false; allows to create button in disabled state.
- fnc one parameter function to be invoked on press sequence detection.
.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 examples:
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 >echo(0); =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 What is LED1/A13? (addressing pins via variables),
replace the Btn1 code with the SWBtn code, and replace the last line withvar 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
-
Update 2022.0410: see post #29
Update 2021.0524: optionedge:"both"
addedInspired 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:
- Start the light show.
- Stop the light show.
- Make the light show run faster.
- 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:
- .r(boolean) for run(true/false) aka start and stop: RLL.r(true); RLL.r(false);
- .t() for toggling start and stop the light show: RLL.t();
- .f() for making the light show run faster: RLL.f();
- .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, edge:"both", 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 - Start the light show.
-
-
The setWatch is absolutely clear... at least to me... that's why I said I'm a bit embarrassed not to look through all the provided functions before venturing into my own implementation.
Regarding 'Basically': this became clear to me when some release notes mentioned, that the event buffer size was increased from 64 to 128. Before my work life, I was more a hardware mind - tinkering with germanium transistor OC72... (a black painted glass housing filled with transparent heat conducting paste) and I still love hardware, because you can touch it and you actually can 'see' when something goes wrong (...the heat conducting past developed bubbles... another kind of interrupt). Back to the interrupts we are talking about here: When starting to work with micro processors - Zilog Z8 - to build an in-cable protocol converter - interrupt serving time mattered for timing - and still does, just in a different way. Explaining the setWatch to myself would read like this:
Set watch registers a JS function with the particular pin. Depending on the raise or falling or both, the core - or basic 'operating system' puts the JS function into a first-in-first-out (FIFO) buffer, from which the JS interpreter later pulls it and invokes it.
Putting the registered function into a FIFO is - time wise - predictable and defines the rate of how many interrupts can be noticed until getting lost, and the FIFO idea assumes that over time all interrupts can be served, because interrupts come not continuously and therefore the serving can be queued. This may though have ramifications for the interrupt service routine (function) implementation with time dependencies, because with the deferred execution 'things' may already have changed again and have to be newly interrogated to make the proper decision. Catching key strokes have a different dependencies, and in order to handle time sensitive sequences, the time of the even is recorded in the FOFO as well.
Above 'definition' raises several questions: can a pin have multiple watches set? For the sake of simplicity and basic usage I assume not... that's why I use the pub(lish)/sub(scribe) mechanism which allows me to register with one single setWatch the pub, which in its turn reaches any number of subscribers. Another question is: How can I check 'how full' the FIFO is in order to, for example, tell the user 'to slow down' his activities?
.itr() does about the same as .forEach() within a few 'nuances':
- is not object-oriented - to take a 'bad' part head on
- performance is of course way inferior to .forEach() - an other drawback
- accepts a start index - helpful for an array that has some meta data about itself in the first (few) element(s), because length is not enough, and to make a new subclass is an overkill.
- the index of the element processed is passed to the callback function,... because it may be of interest - same as array may be passed for both, .itr() and .forEach(). With .forEach() getting to the index of the element in the callback function amounts to effort of the order of O2. If accomplishable 'natively'/in the interpreter. It would actually be interesting to look a comparison. On the other hand, I'm sure you - Gordon - have a good guts feeling top of your head, which one would win.
Regarding, for example,
"SLS"
for S(hort)L(ong)S(hort) versus".-."
, was intentional: SLS can be use to directly address a method/function within an object, and furthermore, allows the dot (.) notation for topic paths in pub/sub. Btw, there is already code on the web that uses the".-."
notation for morse-texting:
https://github.com/espruino/EspruinoDocs/blob/master/tutorials/Morse Code Texting.md, and another one that describes a lock coded using more codes: http://www.espruino.com/Single Button Combination Lock. - is not object-oriented - to take a 'bad' part head on
-
-
[1v64] - Hi,
thanks for the friendly - (somewhat) embarrassing ;-) - 'hint' to look for setWatch(). I must have read '...that Espruino does not support hardware interrupts...' in a very early/old (or not very informed/educated) publication. That's why I came up with a software solution - shared below with - for now - only a few comments. I'm a bit embarrassed because my style of approaching a new language (extension) or library is to study the whole API/specs first before venturing into doing by mapping my programming patterns into particular target designs/implementations...
But I just got the board from Adafruit and had to get my hands dirty to quench my curiosity. The setWatch() will not only simplify the code dramatically, but free up lots of cycles for other things than just watching and interpreting 'bug leg moves' and setting up and handling of timeouts. Interesting is the similarity of some of the parameters - last but not least the debouncer. The debouncer's implementation had to wait until my day job concluded. But with setWatch(), it may not even come to actual life. (So far, the hardware button is not worn out yet and therefore the software buttons works pretty predictable. More details following the code).
Beside the button handling, the code has some interesting features. Implemented in a so called 'JS survival/micro framework' w/ single global muej are:
- Simple, variable controlled LED on/off switching led.(id,booleEvaluating) method.
- Software .tme w/out external components to get milliseconds since start-up.
- A .pub()(lish) / .sub()(scribe) mechanism for tight integration but loose coupling.
- An .itr(array,function) iterator method w/ function(element,index,array){...}
- Multiple software buttons using the only one onboard hardware BNT1 button.
- Running led light started/stopped and made faster/slower with one (1) single hardware but three (3) software buttons.
And all this in almost pure, very easy to use, object-oriented fashion.
var muej = // js survival framework - (f)markus@muet.com = FREE w/ reference { lds: [LED1,LED2,LED3] , led: function(i,v) { this.lds[i].write((v)?1:0); } , tme: 0 , tms: function(y,m,d,H,M,S) { this.tme = new Date(y,m,d,H,M,S).getTime(); } // (?) , tmr: function(p) { this.tme = this.tme + p; setTimeout("muej.tmr(10)",10); } , sbs: {} , sub: function(t,f) { var ss = this.sbs[t]; ss = (ss) ? ss : (this.sbs[t] = []); ss.push(f); } , pub: function(t,ps) { this.itr(this.sbs[t],function(s){ s.apply(null,ps); }); } , itr: function(a,f,s) { if (a) { var i = (s) ? s : 0; var m = a.length; while (i < m) {f(a[i],i,a); i++; } } } , log: function(vs) { console.log(vs.join(" ")); } }; var RLL = // running led lights { P: 160 // on Period [ms] , i: 2 // current LED index , o: null // on timeout , a: false // running light state (active) , t: function() { this.r(this.a = !this.a); this.l(["i",(this.a) ? "on" : "off"]); } // toggle start/stop , f: function() { var p = this.P / 2; this.P = (p < 10) ? 10 : p; this.l(["i","faster",this.P]); } // faster , s: function() { this.P = this.P * 2; this.l(["i","slower",this.P]); } // slower , r: function(s) { // run(1) = start / run(0) = stop muej.led(this.i); if (s) { this.i = (this.i < 2) ? this.i + 1 : 0; muej.led(this.i,1); this.o = setTimeout("RLL.r(1)",this.P); } else { clearTimeout(this.o); } } ,l: function(vs) { muej.log(["RLL: "].concat(vs)); } }; var Btn1 = // SW buttons aka A. Morse by muet { B: 100 // min Short press / debounce [ms] - not used yet , S: 150 // max Short press [ms] , L: 250 // min Long press [ms] , P: 220 // min pause [ms] , b: 0 // last button bounce state ts [ms] , t: 0 // last butten state change ts [ms] , p: false // last button state (pressed) , k: "" // key , c: function() { // check BTN1 (detect 'up' and toggle start/stop var n = muej.tme; // now // muej.led(0,1); muej.led(0); // dbg 1 var p = digitalRead(BTN1) == 1; // muej.led(1,p); muej.led(1); // dbg 1 , 2 if (p) { // pressed if (!this.p) { // was !pressed // muej.led(2,1); muej.led(2); // dbg 1, 2 this.t = n; this.p = true; } } else { // not pressed if (this.p) { // was pressed // muej.led(2,1); muej.led(2); // dbg 1 this.k = this.k + ((n - this.t < this.L) ? "S" :"L"); this.t = n; this.p = false; } else { // was !pressed if (n - this.t > this.P && this.k.length > 0) { var _k = this.k; var _n = n; this.k = ""; this.t = n; // muej.led(0,1); muej.led(0); // dbg 2 // console.log(_k); // dbg 3 setTimeout(function(){ muej.pub(_k,[_n]); },1); } } } setTimeout("Btn1.c()",100); } }; muej.tmr(0); muej.sub("S",function(){ RLL.t(); }); muej.sub("SS",function(){ RLL.f(); }); muej.sub("LL",function(){ RLL.s(); }); Btn1.c();
Originally, I had planned to make the code the subject of a multi part post/series with tutorial like comments... A perfect, fun tutorial that requires just the board and still have a UI and UI interaction. Now it is out there as a whole... and already outdated... :(]
Time permitting, the series as tutorial may still happen... in a refactored form using setWatch(). For now, just some information about the implementation of the (quasi unlimited number of) software buttons:Most interesting is the button BTN1 handling and making it software-wise multiple buttons by loaning the ideas from http://en.wikipedia.org/wiki/Morse_code, developed 1836 by Samuel B. F. Morse... some 'things and thoughts' just never go out of style! - I was trained on it in the army in the signal corps.
The Bnt1 object detects sequences of short and long presses. It decodes them into strings of "S"s and "L"s; for example, two short presses are decoded into "SS", and a short, long, and short press into "SLS". Once detected, the string is used as the topic to be published with some other parameters, such as the time. Any subscriber will be notified aka the subscribed function is invoked.
In the running light RLL example, one short press ("S" topic) toggles the lights on and off. Two short presses ("SS" topic) make it run faster, and two long presses ("LL" topic) make it run slower. Try it yourself...
PS: Gordon, did you notice the IDE's syntax coloring issue in line 8?
- Simple, variable controlled LED on/off switching led.(id,booleEvaluating) method.
-
[1v68] - Hi,
very cool! ...weeding out - or at least mitigating - the differences.
Do I get this right:
var f = process[memory];
returns the function object (as would
var f = process.memory;
as well), and a following
f();
calls the process.memory function - invokes memory method on process object?
Btw, using Espruino is a very capturing journey! Working on a (Espruino) JS survival micro framework and software buttons (BTN1). Latter would really benefit from being able to tap into/connect to hardware interrupts.
-
[1v64] - Hi,
from a programming point of view I feel like Richard, and from a resourceful one I understand very well Gordon's implementation. Since obviously RAM is the constraining factor, I might cave in on some of my programming paradigms and expectations. I do not (yet) know engine implementation details, but could imagine a solution with a federated symbol table - (current) RAM symbol table and a ROM symbol table. Lookup - and many other things - will of course have to be adjusted. The 'many other things' is I guess what Gordon refers to by 'reasonably big change'.
For myself - in the spirit of my user name - I solved the variable access challenge as follows:
LED is a singleton or class object knows all about the LEDs and has a s(et) method accepting the led i(d) and a on/off (true/false) evaluating v(alue) as parameters:
var LED = // leds 'class'/singleton object { ls: [LED1,LED2,LED3] // leds , s: function(i,v) { // 'value tolerant' set method this.leds[i].write( (v) ? 1 : 0); } };
Switching LED2 on and LED3 off is achieved by the following statements:
LED.s(1,1); LED.s(2);
The LED1..LED3 are mapped to the *i*ds 0..2. If you like to preserve the index of the led, modify the set method to look like this:
this.leds[i - 1].write( (v) ? 1 : 0 );
The 'program' for a running light Red(LED1),Green(LED2),Blue(LED3) is implemented as an singleton object as well:
var RLL = // running led lights 'class'/singleton object { i: 2 , t: null , r: function(s) { // run(1) = start / run(0) = stop LED.s(this.i,0); if (s) { this.i = (this.i < 2) ? this.i + 1 : 0; LED.s(this.i,1); this.t = setTimeout("RLL.r(1)",166); } else { clearTimeout(this.t); } } };
The commands RLL.r(1); and RLL.r(0); start and stop the running lights. To start and stop the running light with the espruino onboard BTN1 button, add a button up .c() check method that toggles .run() with start/stop. Note the start of the repetitive button checking by its immediate invocation after definition: RLL.c();. The immediate invocation of the repetitive button check can be left out, but has then to be issued as a command.
Paste the complete code below into the edit area of the IDE, send it to Espruino, and push BTN1 button.
var LED = // leds 'class'/singleton object { ls: [LED1,LED2,LED3] // leds , s: function(i,v) { // 'value tolerant' set this.ls[i].write((v)?1:0); } }; var RLL = // running led lights { i: 2 // current LED index , t: null // timeout , a: false // running light state (active) , b: false // last button state (pressed) , r: function(s) { // run(1) = start / run(0) = stop LED.s(this.i); if (s) { this.i = (this.i < 2) ? this.i + 1 : 0; LED.s(this.i,1); this.t = setTimeout("RLL.r(1)",166); } else { clearTimeout(this.t); } } , c: function() { // check BTN1 (detect 'up' and toggle start/stop var b = digitalRead(BTN1) == 1; if (this.b && !b) this.r(this.a = !this.a); this.b = b; setTimeout("RLL.c()",100); } }; RLL.c();
-
@user6350, working on getting Espruino to understand my 'old' marine #GPS, I read through your code in post http://forum.espruino.com/conversations/1578/#comment26928. I tried to understand getUTMLatitudeZoneLetter(latitude) {... #latitudezone function. Analyzing the implied algorithm and underlaying data values and structure, a How about:... popped up in my head:
And this would be the code:
The two-termed logical AND expressions, for example,
((84 >= latitude) && (latitude >= 72))
threw me off. Two-termed logical expressions are usually only used for dealing with non-contiguous ranges.
To verify the How about, I first morphed the given, quite redundant code into a more concise form - of which one you find below.
After that it became clear how the arithmetic version would look like for the computational approach. Separating the defined range from the undefined ones is though still best done with the comparison approach. The values in the defined range are converted by a formula into range of 0..18, just parallel matching the "CDE...X" string. Note that letters I and O are 'skipped'.
Luckily, the irregular interval [72..84] N still works for the divide by 8, because the top value is less than 2 * 8 and collapse with Math.floor() to the the range of [72..80).
I'd like to point out a nice, genuine JavaScript language behaviors: #operatoroverloading, and #typeconversion. You may wonder about the multiplication by 1 in the correction term (latitude * 1 + 80) to base the defined range to begin with 0. JavaScript has the nice feature - Java-ists call it flaw - of going further with auto type conversion than many other languages - especially (strongly, static) typed ones. When the function argument latitude is as a string - which it most likely is when reading plainly from the wire) - then types matter expressions operator overloading.
Therefore, the value of (latitude + 80) with latitude having the string value "-80", the expression ("-80" + 80) evaluates to "-8080" string, because the "-80" is a string type and defines the type precedence of the operator overloading: + operator becomes string concatenation and hence the result is a string too. JavaScript will convert the number 80 into a string to accommodate the concatenation. Reordering and taking the number 80 first - (80 + latitude) - does not help either. The number 80 is operator overloading happens again to accommodate the "-80" string. Multiplying the latitude by 1 - (latitude * 1 + 80) - solves the issue with no (real) harm or value effect, even when latitude is (already) a number. Multiplication has no operator overloading and numeric is the only option. The "-80" string is converted to the number -80 to accommodate it and the expression evaluates to 0 - just as expected ;-).
Furthermore, notice the opening parenthesis on the same line of the return statement. This is because of another JavaScript genuine 'feature', or #returnpitfall, #jspitfall: If a line feed follows a return, the return is considered as complete and the function returns #undefined, no matter what keeps going on in the next line(s). I could have of course put something relevant after the return keyword to avoid it, for example, the logical expression for separating the valid from the invalid ranges:
Try it yourself with the uploaded .html file... ;-).
For some (random and visualizing) unit testing I like to have a visual UI environment that I can drive with HTML5/JavaScript. Therefore I created a simple file-protocol served .html file. See uploaded screenshot and .html file. You can download the .html file, look at the various implementations, open it in a browser (with no harm) and play with it.