-
• #2
Are you trying this all in the console?
this
is resolved at runtime and means the context.exports
is known from outside of the module when it is uploaded into theModules
cache, because there it is executed... (again, think of 'there is no static code' after Espruino has swallowed the source code...).I'm not sure about the context and intentions... but these conversations may shed some light on what's about with modules in addition to: espruino.com/Writing+Modules:
-
• #3
Sat 2018.09.08
re: 'I'm not sure about the context and intentions...'
Thanks for the quick response @allObjects and I'm reviewing the additional links now.
Just trying to test a simple module with cut-n-paste into the editor window right-pane. Following the instructions at Writing+Modules, wrapping the code in
var exports={};
statements. Basically a getter accessor method to attach to, such asled.testUR();
I stripped out all non-essential lines and have it down to the fifteen lines above. Just performing a send creates the error. Simple concepts taken from and modeled after:
http://www.espruino.com/modules/GroveRelay.js
http://www.espruino.com/modules/MySensors.js
http://www.espruino.com/modules/Ping.jsAt this point, I'm just trying to get something, . . . anything to work.
re: 'this is resolved at runtime and means the context'
For clarification for me, technically, as the code has just been sent to the device, isn't it actually running, and therefore we see what I presented above, typing 'this' into the left-hand pane console? My point is, the IDE is aware of the function, otherwise it couldn't possibly know about it otherwise in order to produce the error.
-
• #4
...the IDE is aware of the function
yes, but more things happen and the context is different then when
this
shows up on Espruino when coming through console... The this through the console has the object / context of the 'console listener' - JS interpreter - on Espruino.Also, when a module is uploaded, the variable
exports
pointing to an empty object{}
is already in the context when Espruino executes the arriving module code. In other words usingexports
and populating it with properties is the usual - a-la node.js - use in a module 'definition'. Assigning a new object, such as a 'class' - in object-oriented terms, is making the variable to point to a different object while loosing a previously added property... and this IS the cause (among others) that things did or do not work for you.I noticed
Just performing a send creates the error.
I assume you are not trying to send the module, do you?
You send only your application code - that uses your modules - to Espruino board in the IDE. The module you store in the modules folder of your local sandbox. The use of the module in your application code is detected by the upload function of the IDE on the presence of
require("...")
. On encountering it, the upload function of the IDE goes for the module hunt. It tries to find it in your local / sandbox module folder or on the web - espruino.com/modules/... - when it is a plain file name - and at the url - when the module name is a complete url - and grabs the source code - and uploads it as source code to Espruino... and stores it in the Modules cache... During the process it may also minify it if needed and told so to save space (leave more to variable space, since minifying makes things shorter, and because Espruino runs off of the source code as is - for the most part - in its RAM, more RAM is left for variables...)Your application code using - for example, the GrovRelay - looks something like this:
var GrovRelay = require("GroveRelay"); var r = new GroveRelay([B3]); ...
After upload, Espruino has a (global) variable
r
(orglobal.r
in the global scope of its execution context that points to an instance of theGroveRelay
. Since thru console global things in the Espruino execution context - like all other JavaScript functions and key words, because it is an interpreter - are accessible by just mentioning the variable name, you can enter in the consoler.on();
and your relay turns on. ;)Now you go back to your modules, and they will work...
Stick this code into your modules folder:
function TestLED(pin) { this.pin = pin; pinMode(this.pin,"output"); this.off(); }; TestLED.prototype.on = function() { this.pin.set(); }; TestLED.prototype.off = function() { this.pin.reset() }; exports = TestLED; };
Then upload the following application code:
var TestLED = require("TestLED"); var testLED1pin = B3; var testLED1 = new TestLED(testLED1pin); setInterval(function(){ testLed1.on(); setTimeout(function(){ testLED1.off(); },500) },1000);
...and you have your elaborate ;-) blinking example... blink frequency: 1Hz, duty cycle: 50%
(should it not work... then there may be a typo... code proof read but not executed on Espruino yet...)
-
• #5
Sat 2018.09.08
Thank you for the detailed explanation. That content confirms what I thought I understood.
re: 'I assume you are not trying to send the module'
I'm following this pp at:
"To test out your module, we'd suggest that you copy it verbatim into the right-hand side of the Web IDE, with the line var exports={}; at the top, and at the bottom, where you use the module, write exports.myfunction()
instead of require('MOD123').myfunction()."If I understand the above correctly, I placed those fifteen lines of code between, and was able to create one instance. It was when I attempted to create the accessor, the error occurred.
Don't have much time this evening, but I'll try your code snippets as you suggest and, as the above pp suggests.
-
• #6
...Ic what you mean you follow...
As you have seen the example of module and the example of use in my previous post, I do now what the instructions for test say. In a nutshell, it is emulating the way the module gets accessed. All code, application AND module are in the same code that is uploaded. Here it goes.
// to emulate module access for local, inline testing var exports = {}; // begin module code function TestLED(pin) { this.pin = pin; pinMode(this.pin,"output"); this.off(); }; TestLED.prototype.on = function() { this.pin.set(); }; TestLED.prototype.off = function() { this.pin.reset() }; exports = TestLED; }; // end module code // begin application code // --- var TestLED = require("TestLED"); // require(... replaced w/ line below for module inline testing var TestLED = exports; // instead of require, directly access what the module definition exports var testLED1pin = B3; var testLED1 = new TestLED(testLED1pin); setInterval(function(){ testLed1.on(); setTimeout(function(){ testLED1.off(); },500) },1000);
My style is always getting the module into a variable, and then using it from there, since it is a class or some object... more the object-oriented (oo) way. Most example do the other way - for various but not needed reasons: They do the require(... and cascade the function or method all on it right away... more... more the function - and JS basic - way. Separating helps with testing... less code is different from final way.
Your first example is kind of a mix of both and messes with your exports anyway. So decide for one. The functional approach is like the example describes an your second approach in your first example, where you expose a plain function that calls the new on the class method and return an object. Most modules work this way because there is a connect(... required that creates and returns the connection object through which you access the periphery / sensor / display.
Initially, assigning a constructor as class was not working... and since even my first Espruino tries were using a class (constructor funtion) rather than a plain function that hides oo. The thing returned from requre(... did not understand new (Espruino intrepreter's new could not apply new on an object returned from require(...).
I asked then @Gordon to remove the restriction/enable exports to be a class (constructor function) and he changed Espruino right away. Now more and more modules show as classes. It is kind of more natural, because you do not want to think that you are talking to a connection object, which is a technical thing that hides the real object. You want to talk to the sensor object or what directly... so the class that is built models the sensor... You can read up in these conversations. That all started 4+ years ago:
- Software Buttons - Many buttons from just one hardware button
- How to create a module that is a 'Class' - aka a function usable with new - or: How to make require() return a function and not an object
Until the oo/class-way was available, I had to build the module a bit funny: esxport a function that returned me the class / constructor function... and it worked but required extra code, like I demonstrate here - module code and application code (as final):
Module code:
function TestLED(pin) { this.pin = pin; pinMode(this.pin,"output"); this.off(); }; TestLED.prototype.on = function() { this.pin.set(); }; TestLED.prototype.off = function() { this.pin.reset() }; exports.getClass = function(){ return TestLED; // returning the constructor 'functio' };
Application code:
var TestLED = require("TestLED").getClass(); // to get the constructor 'function' var testLED1pin = B3; var testLED1 = new TestLED(testLED1pin); setInterval(function(){ testLed1.on(); setTimeout(function(){ testLED1.off(); },500) },1000);
Functionally, there is no real difference... but the oo way looks more straight forward - at least to me, especially when you can have multiple instances of something like a sensor, testLED, etc. (Some modules are built the way that multiple instances cannot exist, because not all instance variables are in the object, but rather just in the module on level . They survive garbage collect only because of being referenced directly or indirectly by exposed variables (functions / data objects), which is by the way how to create private state and behavior in JavaScrpt. So watch out for that... it is the same thing with hidden initializations, which should not happen on load of the module but on construction of the instance object.
Last paragraphs deal with things not really of your concern at hand and I hope it did not confuse more - or only temporarily a bit - but in the end are useful to know and apply to end up with better systems.
- Software Buttons - Many buttons from just one hardware button
-
• #7
I'm on holiday this week so only quickly glanced at this, but the error shows the line
exports = testUR;
- which isn't actually in the code you pasted up. The initial code you actually pasted up looks fine.In this case
testUR
doesn't exist in the scope you're referencing, because you defined it intestLED
's prototype.exports = testLED
is absolutely fine, and that's what's done in the code you showed.So... I'm not sure what's going on here - in this case (and in previous posts) you've shown code that's been running on your device that you don't think you've uploaded.
I guess it's possible you've been flipping between upload styles (save on send, or normal) and have two copies of your code in there? Can you try running
reset(true)
on the left-hand side of the IDE, just to clean out any other code? And can you scroll right down in the editor on the right and check that you don't have any code that's just offscreen that is causing the error? -
• #8
Sun 2018.09.09
re:'you've shown code that's been running on your device that you don't think you've uploaded.'
Not sure what you mean here: 'don't think you've uploaded' after send, I check the left-hand console for ack. Sometimes the errors are there, then coding progress ceases. If I can get a successful send, I keep going.
or, did you mean the reference to when the green progress bar never completes and the IDE freezes, forcing a close re-launch? In that case I know it hasn't uploaded and I'm forced to power down the Pico as well, memory clears, so that Windows can remove and recognize the Com Port on IDE launch.
re: 'which isn't actually in the code you pasted up'
That is the error I see with the code I posted. Per suggestion, I started with a larger file and kept paring until the least possible amount of code that still caused the error remained.
re: 'you've been flipping between upload styles'
re: 'Can you try running reset(true) on the left-hand side'No, I'm not and haven't made any changes to settings. It has the defaults from the install.
I'm making every attempt not to introduce any other activity that could get in the way.
EVERY time I, reset(1), load file if needed, block copy code into the right-hand side editor, edit until syntax checker agrees, then send. Look for Ack.
I'm making sure of that process in an attempt to locate an offending line(s)I'll try
reset(true)
againstreset(1)
Could the Ctrl+C Ctrl+V process be introducing garbage chars? In addition, I'm now attempting using notepad.exe and notepad++.exe to see if the editors use a different mechanism. When I block highlight Ctrl+C code in the right-hand editor, could garbage chars be copied to the clipboard? Could the clipboard retain garbage chars?
-
• #9
For me, things do still not add up...
...and made me conclude that this is the one reason - co-concluding w/ @Gordon:
re: 'which isn't actually in the code you pasted up'
indeed... :[ - because if the IDE settings are installation default with sandbox set, then the following application code uploads just fine - because there is no issue in the module - and it runs as expected (after issuing
run();
in the console):Module code (as in initially posted by @Robin in post #1 w/ semicolon (;) removed from
3rd
line, since Espruinio IDE editor complains... :))// testLED.js - module function testLED(pin) { this.p = pin[0]; } exports = testLED; testLED.prototype.testUR = function() { var nInt = 42; console.log( "inside testUR() " + nInt ); }; exports.create = function() { return new testLED([A5]); };
Application code:
// testLEDApp.js - uses module testLED.js in 'method mode' var testLED = require("testLED").create(); var iId = null; function onInit() { iId = setInterval(function(){ testLED.testUR(); },1000); } function run() { onInit(); } function stop() { if (iId) iId = clearInterval(iId); }
Console output as expected:
1v99 (c) 2018 G.Williams > =undefined >run() =undefined inside testUR() 42 inside testUR() 42 inside testUR() 42 >stop() =undefined >
Above application code uses the method mode (I call it this way). It works because you added
.create()
plain function as property to the exports object which is the carrier to expose your exported.create()
function. Since the exports is at the same time the constructor function (class) , I'm curious if it can be used in class mode as well:// testLEDApp2.js - uses module testLED.js in 'class mode' var testLED = new (require("testLED"))([B3]); var iId = null; function onInit() { console.log("testLEDApp2 (testLED module in class mode)"); iId = setInterval(function(){ testLED.testUR(); },1000); } function run() { onInit(); } function stop() { if (iId) iId = clearInterval(iId); }
And guess what, it woks, as console output shows:
1v99 (c) 2018 G.Williams > =undefined >run() testLEDApp2 (testLED module in class mode) =undefined inside testUR() 42 inside testUR() 42 inside testUR() 42 >stop() =undefined >
***Just make sure that precedence to the Espruino interpreter with parentheses to get the module first and then apply 'new' to it - object return by it . ***
Also be aware that now an array with a pin (as first element) has to be passed in the constructor function with new to allow the constructor to work (and the upload not to fail, because - as said again - construction is executed on upload, since code is in level 0... and [sic: now rambling] I'm saying here again as said in many other places, that executing application code in Level 0 is 'bad' / calls for troubles, because it is executed and creates all kinds of havoc, timing, things not initialized when running disconnected, etc... therefore, the application code again in a more safe way, with execution of application code in onInit(){...} or triggered in onInit()) :
// testLEDApp3.js - uses module testLED.js in 'class mode' var TestLED = require("testLED"); // get class (1st Upper) var testLED; // instance of TestLED (1st lowercase) var iId = null; function onInit() { console.log("testLEDApp2 (testLED module in class mode)"); testLED = new TestLED([B3]); iId = setInterval(function(){ testLED.testUR(); },1000); } function run() { onInit(); } function stop() { if (iId) iId = clearInterval(iId); }
And example of how to test the module inline as instructed by espruino.com/Writing+Modules:
// testLEDInlineTest.js // to emulate module access for local, inline testing var exports = {}; // --- begin module code // testLED.js - module function testLED(pin) { this.p = pin[0]; } exports = testLED; testLED.prototype.testUR = function() { var nInt = 42; console.log( "inside testUR() " + nInt ); }; exports.create = function() { return new testLED([A5]); }; // --- end module code // --- begin application code // var TestLED = require("testLED"); // get class (1st Upper) // commented for inline testing var TestLED = exports; // instead of require, directly access what the module definition exports var testLED; // instance of TestLED (1st lowercase) var iId = null; function onInit() { console.log("testLEDInlineTest (testLED inline test)"); testLED = new TestLED([B3]); iId = setInterval(function(){ testLED.testUR(); },1000); } function run() { onInit(); } function stop() { if (iId) iId = clearInterval(iId); } // --- end application code // eoc --- end of code
NB: I'm PICO, and I approve this post! ...eh: code! ;-)
-
• #10
Sun 2018.09.09
Thank you @allObjects for making an extra ordinary effort in completing this and welcome the world of Pico. I used your snippets verbatim,
here's the output, after I discovered there are two 'modules' folders beneath the installation _sbx folder
C:\Users\Robin\Documents\2017\Espruino_sbx\projects\modules
C:\Users\Robin\Documents\2017\Espruino_sbx\modules>run() =undefined Uncaught Error: Cannot read property 'testUR' of undefined at line 7 col 8 testLED.testUR(); ^ in function called from system Uncaught Error: Cannot read property 'testUR' of undefined at line 7 col 8 testLED.testUR(); ^
There is definitely something different between this IDE on this Windows platform than that which you guys have. Would love to know what is going on. But we are getting closer . . .
Do I need to change the Settings >> Project folder to point to the 'modules' folder?
~\Documents\2017\Espruino_sbx\projects
EDIT
The IDE won't allow me to place the insertion caret cursor in the URL field of Settings >> Project
So, I made copies and placed in the \projects\modules the \modules and the \projects folders, but get the same results. I restated the IDE and now get a Module testLED not found error.
Under Settings >> Communications"Where to search online for modules when
require()
is used"
http://www.espruino.com/modulesas the module isn't at the remote URL while we are online, does this need to be changed for the local drive?
-
• #11
@Robin, that second folder could come from once using the project folder in the already existing sandbox folder again as sandbox folder in Espruino IDE settings... or when saving your code, application or module,... (this comes from some convenience that folders get created when not present... without telling the user... but thats a different ball game). I doubt that there is a difference in the IDE in that respect.
PS: I notice you using _sbx as sandbox folder... did I publish or suggest somewhere that I do so? If you found two overlapping hierarchies there, I should go and fix that to not mislead...
The sandbox folder structure (on my system, crated a while ago, 2 years?) is as follows:
- .../_sbx - sandbox root folder
- .../_sbx/binary
- .../_sbx/firmware
- .../_sbx/modules
- .../_sbx/projects
- .../_sbx/snippets
- .../_sbx/testing
- .../_sbx/testinglog
To make .../_sbx folder the Espruino IDE sandbox, just enter .../_sbx in the SETTINGS - PROJECT.
No change of SETTINGS - COMMUNICATION. The modules are found in precedence as described in espruino.com/Writing+Modules.
IDE knows into which folder it saved the last time... I do not know about the reinstall/update, what this does... I would not expect that it takes into account where and what it saved the last time and use it for sandbox default... which would create that mess....
- .../_sbx - sandbox root folder
-
• #12
That's the same as mine with the addition of a duplicate set under _sbx\projects that must have been added when I did the last update/install roughly six months ago. Haven't figured out how to map to the modules folder.
re: 'I notice you using _sbx as sandbox folder'
Just did the installations (then) using the defaults.
Do you happen to know how the IDE finds the module by name? Is it the same as the name part, and case, of the filename?From #9 above - there isn't any wrapper code that defines that snippet as a module.
ref: // testLED.js - module
Thanks for responding while jointly online, that has helped. Only have fifteen minutes before I've got to go. -
• #13
You do not map to the modules folder... if you you save code in the IDE, you have to pick the folder... and therefore, when you when you save modules, you have to pick another folder on save pop-op/dialog. But just watch out: IDE remembers the folder, and your next save goes into the same folder if you do not change / explicitly select it.
-
• #14
Sun 2018.09.09
later same day . . .
Back at it, several times, but with no luck. Console reveals that it doesn't appear to even be looking for the local folder.
allObjects see new post:
Placing module code in local modules folder doesn't appear to be located by IDE
http://forum.espruino.com/conversations/325378/
I remembered, but was unable to locate another individual post (around three-four months ago) that was attempting to use the projects folder, but it was suggested to place module code between
var exports={};
and test that way.While searching for that post, I did uncover this, where it seems an attempt was made to resolve this, but never completed. Maybe we are at that point again to revisit again?
http://forum.espruino.com/comments/14110348/
WinXP: how can I require() some locally held modules?
https://github.com/espruino/EspruinoWebIDE/issues/186
I'm at ver 0.68.6 from install ~six months ago. What version of native IDE are you using, and on a Windows PC allObjects?
EDIT
By using instructions at https://www.espruino.com/Moduleswas able to create repository link to test
require("https://github.com/sleuthware/EspruinoDocs/blob/master/modules/testLED.js").create(); -
• #15
Looks like the other post you created on the IDE/modules issue is kind-of sorted now.
In http://forum.espruino.com/conversations/279130/#14110348 you posted there's a link to an issue I filed, which is resolved: https://github.com/espruino/EspruinoWebIDE/issues/186
I raised the timeout from 50ms to 250ms - but it seems that people just keep finding ever-slower computers to run the IDE on :)
-
• #16
but it seems that people just keep finding ever-slower computers to run the IDE on :)
Back to the future, but future is not even close to today.
Dealing with timeouts / polling in absence of events, it is difficult to be speedy and efficient and still reliable... (yes, there be an timeout event - for example, http timeout/404 - but the timeout is so far out in the boonies, that one tries to take chances... and become unreliable). It obviously is what it is. These timeouts may one day become part of the settings so the unearthend, flintstones owned computers still can be used for IDE... ;-)
Sat 2018.09.08
I am attempting to test a module function between
var exports={};
statements.The IDE shows correct syntax check in right-hand editor pane.
On immediate send to device, Uncaught ReferenceError: "testUR" is not defined
despite the fact I just created the function definition. Observing the 'this' contents confirms this
Ideas anyone?