• 2. For the second step, we want to use the code like a module.

    *** EDIT No 'hands-on' for this 2nd step as outlined in posts #3 thru #7. Following it in thoughts and understanding the concepts is sufficient. Hands-on is again asked for post #8 and beyond. ***

    To use the code like a module, in other words, use the require(moduleName), we have to shove the code as module into the Modules cache: see [espruino.com/Reference#Modules]http://www.espruino.com/Reference#Module­s). The Modules is a global JavaScript object that holds on to the uploaded modules. The modules are uploaded, stored and retrieved by the 'moduleName' (also called 'moduleId'), which is at the same time the file name for local and official modules, and the url for a module from anywhere of the web.

    Usually, the upload retrieves the module from either the local sandbox, the official espruino.com/modules library of the Web, or any other place of the Web by supplying the full url as 'moduleName'. How code and modules are loaded is touched on in the conversation about simple explanation how to save code that espruino run on start?.

    The same means that upload uses to upload modules to the board are available to us in either the console or the already uploaded to the board. The Modules as global JavaScript object has behavior - methods / functions - which we can use to store a module. The method looks like this in the complete JavaScript expression: Modules..addCached(id, sourcecode), where id is the moduleName as string and sourcecode is the complete source code as a single string.

    To make the code work like a module (on retrieval or add and retrieval), we must add exports = objectToBeExported (or ...toBeExposed) at the end of the the code. The objectToBeExported is for most modules a simple object with one method / function connect, which uses module code to connect to and sometimes initialize the device the module is all about. The nice thing about this export / expose approach is to intentionally make to the application available only what is needed and hide and protect other things. Hiding thins also avoids naming conflicts between application and modules and among modules that would make use and reuse of modules a very difficult not to say impossible task.

    Looking at some existing modules, you find that export does this - for example, for the DS18B20 Temperature Sensor:

    ...
    exports.connect = function(oneWire, device) {
        return new DS18B20(oneWire, device);
    

    The exports object is already there and sometimes the module adds to the object the properties / methods / functions it wants to export/expose or it set the exports object all together to point to a new object. In 'DS18B20' case, the .connect(...) method is added which uses the particular module code for implementation. In our 'class' / prototype case, we make the 'exports' point to the 'class' / prototype function, which is the constructor function that we will use in the application with new:

    ...
    exports = Temperature;
    

    With the module code now complete, we have to feed it into the Modules cache as a single string. There are multiple ways to do that as described in conversation about Module creation at runtime:

    • most obvious is to make each line of the source a string and concatenate them all...
    • use ES6 multi-line strings that are supported by Espruino.

    Making the source code lines all string and concatenate them is though quite some work and will need more work when further down the development process the will be stored as separate module file. Therefore we will use the ES 6 multi-line string to achieve the change very quickly:

    • before the first line of module code we add Modules.addCached("Temperature",backTick­
    • after the last line we add backTick);

    ...where 'backTick' is the backwards-looking single quote (`), also called (in French) 'accent-grave'.

    Putting backTicks around multiple lines makes JavaScript to look at it these a single string ignoring the line ends (CR / CRLF / LF). Using this string in the Modules.addCached(...) methods as parameter, Espruino does not anymore look at it as code that is to be processed (compiled, interpreted, and executed). Latter is a major shift in regard how and when module code is looked at as code. So far, it was looked at as code right on arrival at Espruino on upload. backTicked, it is looked at as plain string on arrival on the board and is looked at as code and processed as such (compiled) when stored in Modules cache.

    Note: When the module code is very large and so the source as string will be too, you may ran out of memory... But to that, there is a solution as shown in the next post.

    Last change is in the application part where we now have to retrieve the the Temperature 'class'/prototype definition as module instead of direct reference the already available constructor function. Since - for what ever reason ... = new require("Temperature")(...) construct is not working in Espruino, we add an extra line just before the line with ...new...': var Temperature = require("Temperature");.

    Since Temperature is a 'class'/prototype, it is appropriate to name/spell it so. If it would be another module, for exmple the one for the 'DS18B20' temperature sensor, we would name it lower case beginning like var ds18b20Mod = require("DS18B20"); and later use var tempSensor = ds18b20Mod.connect(...); to ('create' a sensor and) connect to it.

    Even though we are done with 'modularizing', it is not good enough: Since we do not go through the usual upload process that parses any required module for nested 'requireds', our module will fail on usage. Why? Answer: From where should Espruino know that it should have retrieved the module(s) referenced in the module as well?

    Solution to the missing nested module 'DS18B20' is to upload the referenced module(s) 'upfront' - if known at that time - or retrieved and added to the cache 'now' dynamically as well, either from included code or code read from 'somewhere', such as SD Card... or the Web.

    For our simple example - and because we know what will be used, we add this require("DS18B20");" to the code... almost as a dummy,... but because it is in the active, uploaded code (and not in a string), it is detected at the beginning of the upload and retrieved and uploaded by the uploader (...may be @Gordon may change this and let pass strings in the code as well through the recursive module detector and uploader. Until then, we just do it in our application code... and it will not hurt even when the function becomes available). It does not really matter where we place thisrequire("DS18B20");` - before of after the dynamically loading of the module or somewhere else. Useful is to put it close, and I decided it to put it just before. I will have to remove it when the module will be completed and statically available

    The final code looks like below... where the Temperature 'class'/prototype definition is handled and used as a module:

    // TemperatureDevTest2.js
    
    require("DS18B20"); // used by the dynamically loaded module
    Modules.addCached("Temperature",`
    // Temperature.js
    // 'class' (Prototype definition)
    var Temperature = function // module (class), for multiple instances
      ( name      // name hinting room / location of sensor\
      , oneWire   // one-wire; for example: new OneWire(B8);
      , addr      // address on the one-wire (required for multiples)
      , interval  // interval in milliseconds of measurements
      , preferred // optional, unit - "C" or "F" (default and not F is C)
      ) {
      // set givens
      this.name       = name;
      this.oneWire    = oneWire;
      this.addr       = addr;
      this.interval   = interval;
      this.preferred  = (preferred==="F") ? "F" : "C";
      this.enabled    = (typeof enabled === "undefined") || enabled;
      this.t = (this.preferred==="C") ? 0 : 32; // set current to 'frozen'
      this.intervalId = null; // later also used as indicator for enabled
      this.sensor     = null; // laster also used as indicatore for connected
      // get going
      this.sensor = require("DS18B20").connect(this.oneWire)­;
      this.intervalId = setInterval(function(_this) {
          _this.sensor.getTemp(function(t) {
          _this.t = t;
        });
      }, this.interval, this);
    };
    Temperature.prototype.getTemp = function
    ( unit // optional, unit "F" or "C", default "C"
    ) {
      var u = ((typeof unit !== "undefined") && (unit === "F")) ? "F" : "C";
      var d = (u === "F")
        ? (this.t * 1.8) + " Fahrenheit"
        : this.t + " Celsius";
      return d;
    };
    
    exports = Temperature;
    `);
    
    
    // usage
    
    // setup oneWire
    var oneWire = new OneWire(B8);
    
    // setup 1st temperature sensor ts1
    var Temperature = require("Temperature");
    var ts1 = new Temperature
      ( "office" // name / location of temperatre sensor 1
      , oneWire
      , null     // addr on one-wire currently not implemented / used
      , 5000     // every 5 secs make a read (to keep it not booring)
      , "F"      // preferred unit is Fahrenheit
      );
    
    
    // for sample's sake, do everhthing deferred for more 
    // than 5 seconds in order to have value(s) to display
    setTimeout(function(){
    
    // get temp in preferred unit 
    console.log(ts1.getTemp());
    
    },6000);
    

    It works as expected and shows in the console:

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v94 Copyright 2016 G.Williams
    >
    =undefined
    31.375 Celsius
    > 
    

    Pretty hot...is it! - I mean also literally...

About

Avatar for allObjects @allObjects started