simple explanation how to save code that espruino run on start?

Posted on
Page
of 2
/ 2
Next
  • hi, I play now a couple hours with save and onInit function and can't run my program when my pico is starting after reset (unplug from usb), is there any simple explanation?
    here is the code

    
    
    function oled(){
     
             g.clear();
             g.setFontVector(28);
             g.drawString("FIRST",0,12);  
             g.flip();
      
      setTimeout(function(){
        nextOne();
        setTimeout(function(){clear();},3000);
        setTimeout(function(){setInterval(data,1­000);},3200);
      },2000); 
    }
    
    function nextOne(){
        g.clear();
        g.drawString("NEXT",0,12);
        g.flip();
    }
    
    function clear(){ g.clear();g.flip(); }
    
    function data() {
      var bat = analogRead(A5);
      g.clear();
      g.drawString(bat.toFixed(2) + "v",0,0);
      g.flip();
    }
    
    function onInit(){
    setTimeout(function(){
      oled();
      },2000); 
     // onInit();
    }
    
    onInit();
    
    I2C2.setup({scl:B10,sda:B3});
    var g = require("SSD1306").connect(I2C2);
      
    E.on('init', function() { onInit();  });
    
  • I do it like this:

    function onInit() {
     // Call to an function that kicks off my code
    }  
    save();
    

    My undertstanding is that onInit() fires automatically on startup/reset. I make save() the last line of code. I can't advise if this is the best way, but it does seem to work.

  • I do it like this:

    function start(){
      // place your code here 
    }
    E.on("init",start);
    save();
    
  • onInit will get called automatically, so no need to add E.on('init', function() { onInit(); });

    Your main problem though is you're not initialising the oled display in onInit.

    Try:

    var g;
    
    function onInit() {   
      I2C2.setup({scl:B10,sda:B3});
      g = require("SSD1306").connect(I2C2, function() {
       oled();
      });
    }
    
  • Oli and Gordon, the way your wrote isn't working, OLED doesn't start after reset,
    but MaBe advice looks like good one, OLED and all code is starting after reset, but EspruinoIDE return some error in console

    >echo(0);
    Erasing Flash.....
    Writing.........
    Compressed 81600 bytes to 6195
    Checking...
    Done!
    Uncaught ReferenceError: "o" is not defined
     at line 1 col 1
    o(1);
    ^
    

    btw. is this save operation store some data on the MCU every save I make? I mean should I flash espruino to save space on the mcu or during save oparation it will erase all old data and replace them with new one?

  • you can list your saved code by using dump();

    >dump();
    

    and than check for the error.

  • btw. is this save operation store some data on the MCU every save I make? I mean should I flash espruino to save space on the mcu or during save oparation it will erase all old data and replace them with new one?

    save() writes the current contents of all JSVars to the flash, replacing whatever was previously there. Then it resets the Espruino and runs onInit().

    You can also, instead of saving, send the code and just manually kick off onInit() or the function you'd reference from E.on("init",...).

  • A few things... like 2 cents :)

    Something most not-understood about Javascript Espruino is the fact that - with default settings - source code from within the IDE code editor is already processed - executed - by Espruino interpreter on the board on upload and *stored in the (volatile) RAM only. An upload independent save() command in the console is required to save the cod in non-volatile Flash EEPROM to be available again after a power cycle. With most other environments (and languages), the source code is 'completely' transformed - compiled - into executable machine code before upload, and uploading is a plain copying of that code into the FLASH (EEPROM) (of the chip). Default settings has 'save on upload un-checked'.

    Upload on Espruino uses the REPL nature and state of Espruino in connected mode and the interpretative nature of JavaScript with the small modification: no-print: echo is while uploading. About REEPL, see for example Wiki Read-eval-print_loop.

    Let's play out the story on 'the subject' while moving on in this post. For that, we start with a clean slate: connect to the board with the Web IDE and enter reset() in the console (left pane).

    That any code is processed by Espruino on upload becomes most noticeable when sending following code source string digitalWrite(LED1,1); to the board. Send the code by either typing it in the console and pressing Enter or by typing it in the editor - right pane - and clicking the upload 'button'.

    At the very moment the code arrives - streams in - to Espruino board, Espruino's Javascript interpreter interprets it, and therefore turns LED1 on.

    Let's look at some more things we upload - one after the other:

    The code source string var v = 5; tells the interpreter to establish a variable by the name of v in a piece of RAM and set it to the value of 5. Note: If you use the editor pane to upload, add this code to the code that turns on LED1.

    The code source string function turnOnLED1() { digitalWrite (LED1,1); } tells the interpreter to establish a function by the name of turnOnLED1 in a piece of RAM and set it to the function body's source code, the code between the curly braces, and - as expected - nothing else happens.Note: If you use the editor pane to upload, add this code to the code already in the editor pane.

    For the sake of understanding what save() really does, execute it now by entering it in the console.

    Espruino treats save() just the same way as it did previous digitalWrite(..., var... and function(...: Espruino interprets save() and while doing justice to the 'command' it saves - or - simply copies the RAM content to the FLASH, which means that now the value 5 as variable named v and the source of the function body as function named turnOnLED1 are stored in Espruino's FLASH... firmly and safe from power loss... and for 're'-use... later on.

    Disconnect Espruino from Web IDE and de-power/unplug it, then plug it back in and re-connect.

    The LED1 is not/does not come on, and nothing else really visible happens... but Espruino copies the FLASH content back to RAM - restores the Espruino machine state, such as the RAM (and some other things) so they becomes accessible / usable again:

    Just type v into the console and press Enter and you get a (high) 5 back. Entering turnOnLED1() will invoke the (re)stored function code and latter turns LED1 on. Espruino interprets v, finds it as variable with value 5 and prints the value in the console. Espruino interprets turnOnLed1, finds it as a function and the following open-close parentheses () makes Espruino to execute it.

    Looking at any of your code you upload, you will see just 'code pieces' that have either immediate effect or are 'stored' under a name for 'later' use.

    For example, if you want to turn on LED1 on power on, add (upload), for example, function onInit(){ turnOnLED1 (); } *before you save*. This will bottom line - as you just learned by experience - store the invocation of turnOnLED1 function as function with the name onInit in the RAM, and on save() in the FLASH.

    Note: If you use the Web IDE editor for entering the code, the final code you upload and save looks like the code below. The save() is NOT part of the uploaded code. You enter save() command after uploading the 'final cod version'; save() is only used when the code ready to be run in disconnected, re-powered state. To save intermediate states, use the save code to disk and load code from disk functions (buttons located just above upload to board button).

    digitalWrite(LED1, 1);
    var v = 5;
    function turnOnLED1() { digitalWrite (LED1, 1); }
    onInit() { turnOnLED1(); }
    

    On power on, Espruino looks for onInit() function in RAM and executes it as first thing, after it has restored RAM content from FLASH.

    In (most) other environments, on power on, (application) code execution starts at a particular, predefined location in the FLASH.

    There is more to upload and save, such as uploading referenced modules and minification of uploaded code - latter to use less memory and execute/interpret faster... - ...'saved' for another post.

  • @allObjects great explanation, eagerly awaiting your next post about modules and hardware initialisation.

  • I'm also struggling with the save()/restore() stuff. I would like the following set-up:

    • I have some "environment variables", such as wifi password, save()'ed in a couple fo global variables.
    • I have a program in the IDE that I'd like to upload and run (using the 1-click button) and make use of these variables
    • I need the IDE to issue a reset() because otherwise there are timers and other crap left from the previous iteration

    I can't figure out how to make this work. When the IDE does a reset() the saved variables are not loaded. The environment is literally empty. I tried to use load() at the start of my file, but it causes a reset also, and seems to have a pile of side-effects on the parsing of the remainder of the file. Any ideas?

  • @tve sounds like for you, the best solution would be if the Web IDE issues a load() call rather than a reset() one...

    A simple hack is just to do reset=load;, and then next time the IDE tries to reset it'll actually end up loading your saved code.

    There was a post a while back about adding something like E.setBootUpCode that'd save code that would always be executed - even on reset. That could be an option - but on the whole the idea of reset is to reset the board to a totally fresh state so you always know where you start from.

  • @tve, how about putting your default stuff in a module (in project folder modules) from where you can grab it? For example:

    // env_tve_xyz.js - module
    exports.nw = "myWifiNetworkName"; // wifi network / access point id / ssid
    exports.pw = " myWifiNetworkPassword"; // wifi network access / password
    
    // application code
    var env = require("env_tve_xyz");
    ...
    ...env.nw... // using wifi network / access point id / ssid
    ...env.pw... // using wifi network access / password
    ...
    
  • good idea!

  • What is the best way to save() code that has required modules (whether local or from the espruino modules directory), so to be able to run standalone?

  • I use the IDE as chrome APP and set up a project,
    this allows me to add local modules in the project folder modules.
    Send to Espruino will include this modules than save() and check with dump()

  • Yep - save() is a bit like hibernating a PC - it saves the whole contents of RAM to flash - that includes any modules that were loaded in as well.

    The only thing you have to watch out for is if you've got any external components that need configuring after power-on (like a display). In that case you need to put the initialisation code in onInit or on('init', ...), but if you required a module anywhere in your code, it will have been loaded in by the Web IDE and saved with save().

  • @MaBe @Gordon thank you both. This is what I thought should be happening and what I've been doing. Thanks for confirming.

  • Two more cents... about save() and power-on reset / power cycle... actually only 1 cent for now, since this post covers only delays in detail. Callbacks are covered in a next post.

    @Spocki and @MarcoM - and @Ollie, here you go:

    Think about your electronics / hardware: some things you time - delay - with an RC-circuit, and some you setup for trigger - callback - on a rising or falling edge. That sums it up quite simply... and may be a bit too simply, but it is a good starting point.

    Complexity arises then when you want to bring a lot of these into sequence and and more so when it is a mix and match. Not much different is it with software. The means are a bit different, but you still need to do the proper setup and do the enabling for the right time.

    As you know from electronics, some ICs have power-on / reset sequencing /setup / stabilization dependencies, and you have to wait before you can 'use' the IC. Even Espruino with its STM MC and the elaborate Espruino firmware have a power on reset sequence. The sequence is a combination of various hardware and (low level / internal) software tasks, after which - at last - Espruino firmware invokes onInit() function. Therefore, you put the start of your code into this onInit() function (or in function(s) invoked within onInit(). onInit() was there from the beginning, but in recent releases E.on("init",aFunction) is available as alternative. All functions you want to have invoked on power-up / reset you define and register with, for example, something like this:

    var globalVarSetInFuncA = 0;
    var globalVarSetInFuncB = 0;
    function yourStartFuncA() { globalVarSetInFuncA = 10; }
    function yourStartFuncB() { globalVarSetInFuncB = globalVarSetInFuncA + 5; } 
    function yourStartFuncC() {  
      console.log(["A: ",globalVarSetInFuncA," , B:",globalVarSetInFuncA].join(""));
    } 
    E.on("init",yourStartFuncA);
    E.on("init",yourStartFuncB);
    E.on("init",yourStartFuncC);
    

    All function registered with E.on("init",...) are also invoked after Espruino is ready for you in the same sequence as you registered them. The output in the console is:

    A: 10, B: 15

    Doing the same with onInit() instead of E.on("init",...) looks like this:

    var globalVarSetInFuncA = 0;
    var globalVarSetInFuncB = 0;
    function yourStartFuncA() { globalVarSetInFuncA = 10; }
    function yourStartFuncB() { globalVarSetInFuncB = globalVarSetInFuncA + 5; } 
    function yourStartFuncC() {  
      console.log(["A: ",globalVarSetInFuncA," , B:",globalVarSetInFuncA].join(""));
    } 
    function onInit() {
        yourStartFuncA();
        yourStartFuncB();
        yourStartFuncC();
    }
    

    You can run the examples above one by one by pasting them into the editor pane of the Espruino Web IDE and then upload them to the board.

    All the above works nicely - in intended sequence - when these functions are simple, blocking, synchronous function. Blocking means:

    • While the function is running, nothing else of your code is running.
    • The function completes all its related work.
    • When function returns control, all results are available.

    Unfortunately - and actually fortunately - the world is not that simple: there are functions that need some time to complete and they are implemented the way that they do not block the processor so that the processor can execute other things 'at the same time'... for the common benefit.

    Some function may take specified time to execute, and some use some unspecified time - unspecified in the sense that they depend on something else to happen before they can complete their work. Such functions behave asynchronously, non-blocking, which means:

    • While the 'function related work is done', other parts of your code can run.
    • The function 'moves on' before all its related work is completed.
    • When function returns control, not all expected results are available.

    That puts the simple - linear, sequently - invocation into trouble!

    Assume that the work of yourStartFuncA() in above example code takes a second to complete. For example, a sensor needs to 'warm up' before it can perform the (first) measurement and store its value in globalVarSetInFuncA. therefore, you need to give it time to do so. We emulate this using

    function yourStartFuncA() { setTimeout("globalVarSetInFuncA = 10;", 1000); }
    

    This adds some quirk to our simple initialization work, namely:

    setTimeout("source code", timeoutTimeInMilliseconds):

    • Tells Espruino to 'delays' the execution of globalVarSetInFuncA = 10; by 1000 milliseconds.
    • Lets the function 'move on' before the execution of the assignment.
    • Returns control before the result is available in globalVarSetInFuncA.

    Which means - with given initialization code using E.on("init",...) or onInit() { ... } - that our console output will show:

    A: 0, B: 5

    Not exactly what we expect...

    See yourself by using onInit() example with above setTimeout modification in yourStartFuncA().

    Indeed you will see the unexpected result... But:

    We can easily fix that, because we know what really happens. We fix it by delaying 'everything' (in the initialization) after invocation of yourStartFuncA() for as much time it takes to complete the work of yourStartFuncA(). To be safe, we chose 1100 ms. For example (using onInit()):

    function onInit() {
        yourStartFuncA();
        setTimeout("yourStartFuncB(); yourStartFuncC();", 1100);
    }
    

    Run the the code with above modification in onInit() and you will get the expected console output.

    A: 10, B: 15

    Having more than just one 'delaying' function in the initialization sequence will challenge the statement because we know what's going on therefore we can easily take care of dalays.... That's when other options have to come in, and that is a sequencer that allows timing...

    For additional details see posts in conversations:

    To make a timed initializer - using gCtl controller object - in link above: just put the cb() callback invocation into the (or a) setTimeout() with the delay as needed, and add the initializer to / register the initializer with gCtl, just like you do for the other functions:

    // g(lobal)Log(ging) function:
    var gLog = function(v) { 
      console.log(v); // NOTE: COMMENT THIS LINE FOR DISCONNECTED RUN
    };
    // g(lobal) C(on)t(ro)l(ler) that handles:
    // - sequencing of initializers
    // - run workers after save() and power-cycle / power-on reset
    var gCtl = 
    { is:[], i:-1 // (registered) i(initializer)s, index for iterating initializers 
    , adi: function(i) { this.is.push(i); } // add/register initializer
    , ini: function(ok,str) { if (this.i >= 0) { // invoke registered initializers
        gLog("i[" + this.i + "]" + ((str)?str:"") + ": " + ((ok)?"ok":"failed")); } 
        if (ok) { if (++this.i < this.is.length) { var _t = this;
          setTimeout(function(){_t.is[_t.i](functi­on(ok,txt){_t.ini(ok,txt);});},1);}  
        } else { /* initializer is[i]() failed */
          /* code to handle failures... may be retries/start over... */
      } }
    , run: function() { this.i = -1; this.ini(true); } // run() invoked in onInit()
    };
    
    var globalVarSetInFuncA = 0;
    var globalVarSetInFuncB = 0;
    
    function yourStartFuncA(cb) { 
      globalVarSetInFuncA = 10;
      cb(true,"yourStartFuncA");
    }
    function yourStartFuncB(cb) { 
      setTimeout(function(){ 
          globalVarSetInFuncB = globalVarSetInFuncA + 5;
          cb(true,"yourStartFuncB");
        }, 1000); 
    }
    
    function yourStartFuncC(cb) {  
      gLog(["A: ",globalVarSetInFuncA," , B:",globalVarSetInFuncA].join(""));
      cb(true,"yourStartFuncC");
    } 
    
    function yourContinuousOperationFunc(cb) {
      gLog("Now entering my continuous operation function");
      //...
    }
    
    gCtl.adi(yourStartFuncA);
    gCtl.adi(yourStartFuncB);
    gCtl.adi(yourStartFuncC);
    gCtl.adi(yourContinuousOperationFunc);
    
    function onInit(){ gCtl.run(); }
    

    NOTE 1: IN NO PLACE YOU SEE save() AS PART OF THE CODE. It is though entered and executed in the console AFTER UPLOADING THE CODE.

    Note 2:

    Holding on to the module with an extra variable as in the code in the post where the gCtl controller object was introduced, may inadvertently - depending on how the module is implemented - hold on to some extra memory. Instead of holding on to the module with an extra variable and later referencing to the mdoule using the variable in the typical .connect(), again a require() could be executed, which - afaie(expect) - would go after the module cache and pull it from there. @Gordon may shed some light on possible memory issues. (In the client-side / browser world where for this approach requirejs.org's require() is used, a once (over the network) required module is cached (in a module cache) and can be referenced quickly again:

    require(
       ["modules/path/name1","modules/path/name­2"]
      ,function(module1,module2){
      var connection1 = module1.connect(...);
      module2.connect(connection1); // assume to be a statefull singleton
      ...
    });
    

    In some other place, later on, module2 would deliver the very same module - but now connected - as above:

    require(
       ["modules/path/name2"]
      ,function(module2){
      module2.doXyz(...);
      ...
    });
    

    node.js (nodejs.org)'s require() philosophy - what Espruino leans on - is similar, because it was born server-side and therefore most importantly synchronously vs as require() in the browser, where everything is best pulled asynchronously in order to optimally use the network connections. The browserify technique is now available to use node.js oriented code also in the browser, in order to use the very same source code both client AND server side. Therefore - in order to share the same Javascript code on Espruino and on your servers, use node.js for your server side implementations rather than any .cgi or Java or PHP or Python or ... You can use Python if you have MicroPython instead of Espruino... MicroPython was born - mind, distance and time wise - not far away from Espruino ;-)

  • Very clear and detailed. Thanks. One small question. For the Arduino(Uno) they are distinguishing between Flash memory and EEPROM. Are they the same for the Espruino ?

  • The Arduino boards have EEPROM because it's built into AVR chips they use.

    The Espruino boards don't have EEPROM built in - STM32 chips don't include any EEPROM.

    There's a "fake EEPROM" module that uses the flash (flash that's not being used to store code, ofc) to give functionality that looks a lot like EEPROM:
    http://forum.espruino.com/conversations/­277552

    Or you can connect an external EEPROM:
    http://www.espruino.com/AT24

  • EEPROM is more general term for Electrically Erasable Programmable Read Only Memory, an evolution of the EPROM (which had to be reased w/ UV light), an (functional) evolution from PROM - one time programmable only, an (functional) evolution from ROM - content 'hardwired'/set in stone on 'build time'.

    There are various types of memory cells, wirings, and control logic in use in EEPROMs which make them either a byte-wise erasable / programmable (writeable) memory, versus a Flash one which can only block-wise erasing and (mostly) byte-wise programming.

    Erasing means to set all bits to 1, and programming means make some 0. Depending on cell type it can be 0 and 1 vs. 1 and 0. Depending on the 'luxuriousness' of the driver available to the application code to drive the EEPROM, the erasing and programming and byte- and block-wise operation is transparent: it is just simple read and (re)write for the application. Elaborate drivers also take care of the balanced erasing (and programming), because the prevalent EEPROMS have an 10'000...100'000 erase/write cycle limit. Technologies though get better and better and some have practically unlimited cycles and can be used like RAM (MRAM, and to a lesser extent, FRAM). Today's SSD - Solid State ('Hard') Drives fair almost unlimited.

    Flash vs. 'plain' EEPROM in a nutshell: Flash is a particular EEPROM (more details).

  • The following code seems valid but the led is not blinking. The console output is correct. It is as if digitalWrite is not working.
    When I run the code without save() and onInit() functions, inside simple setInterval the led is blinking. Any iade?

    var toggle=1;
    
    
    function start(){
      
    setInterval(function() {
      toggle=!toggle;
      digitalWrite(D5, toggle);
      
      console.log(toggle);
    }, 1000);
    
    }
    
    function onInit() {
      start();
    }
    
  • Your code is absolutely 'fine'. To verify, I used onboard red LED1 instead of D5 (false for initialization of toggle... more on that later).

    var toggle = false;
    function start() {
      setInterval(function() {
        toggle = !toggle;
        digitalWrite(LED1, toggle);
        console.log(toggle);
      }, 1000);
    }
    function onInit() {
      start();
    }
    

    You have nothing 'active / immediate' in your code that you upload. The code just defines several functions but nothing invokes any of them.

    onInit() ins only invoked when powering on (or pressing reset on Original board) - or saved.

    There is an option in the settings that saves the code automatically after upload (to cater to Arduino souls...).

    I assume, you entered the code in the right pane of the Web IDE - the editor - and uploaded it.

    After doing above, you enter in the left pane - the console - onInit() it works just fine what you have.

    My only recommended change in your code is to initialize toggle with true or false... which is perfectly understood as value by digitalWrite(). If you want to stay numeric - 0 / 1 - for you toggle, change line 4 to toggle = (toggle) ? 0 : 1;.

  • hi @allObjects , thanks for your quick response and recommendations. I just want to clarify two things.

    1. The code below runs perfectly, but it does not run in the form I described above.

      var toggle=1;
      setInterval(function() {
      toggle=!toggle;
      digitalWrite(D5, toggle);
        
      console.log(toggle);
      }, 1000);
      
    2. As you assumed, I write my code in the right pane and upload it. Then I say save() in the left pane. I can see onInit() gets called and the console logs true, false.... I mean everything works as expected except digitalWrite()

  • Does putting a pinMode(D5,"output"); into onInit() fix it?

    (if it does, it's still a bug, but at least we know which one it is).

    What board are you using? What version of Espruino?

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

simple explanation how to save code that espruino run on start?

Posted by Avatar for bigplik @bigplik

Actions