-
-
@DrAzzy, you are absolutely correct... the strings are way more substantial than algorithmic derivable values. The actual strings are shown in the code snippet below, which also includes the join-split and store operations in the ini() method. The 'KeyNMO'-thing was just a placeholder to imply that there are many of those - relatively short - strings. I did run out of memory when the strings in 2nd and 3rd block - which correlate - were less then half in count. The characters in the string are encoded values. The encoding is preliminary - but human readable and quick to interpret - and the encoded (unsigned) integer values are in ranges from 0 to 16, 32, and 96. I'm thinking of running them through a generator to compact them even more, and then use them as typed arrays. It would be interesting to elaborate on various implementations and the overall impact - not just in regard of the 'data and its structure', but also the dependent algorithms and their performance. For runtime - after initialization - the values are neededas individual addressable items. At one time I thought I could leave them as complete strings (joined) and access with .substr()... May be it will become possible after compacting them and then 'pay a bit more for it' while repeatedly extracting values. At a later time in the project I will be a more verbose about the strings' purposes.
;-)
var o = { o: "some singleton object" , bs: // borders "XY[Steps Right|steps left]...,..." filled polygons ["AACBC FADBD LaADA OADBD UACBC AECAC FEACcACcA IEGACcACc REAGACcAC UECAC" ,">HFDACe >LEadBa UHFDACe ULEadBa IKBAAcA NKBDACa JNEAE >NFDACe >REadBa" ,"FNADA RNADA UNFDACe UREadBa IQGACcACc ATCDACb FTDAD OTDAD UTCABcA ?WBAB" ,"FWACcAIAE IWGACcACc RWACeAIAC XWBAB >LAmMan LaNMano >RAKmAN L]NkALO" ] , ns: // nodes to segs - [<..K..[..k..}] - "xyG(oodie(s))h...(oriz)v...(ertic)" ["@@.(U E@.)V K@.W N@.*X T@.+Y Y@.Z @D.,[ ED.-\\ HD..] KD./ ND.0 QD.1^ TD.2_ YD.`" ,"@G.3 EG.a HG.4 KG.b NG.5c QG. TG.6d YG. HJ.7e KJ.8 NJ.9 QJ.f @M-: EM.;g HM.h" ,"QM.<i TM.=j YM- HP.>k QP.l @S.?m ES.@n HS.A KS.o NS.Bp QS.C TS.Dq YS.r @V.E" ,"BV.s EV.Ft HV.Gu KV.H NV.I QV.Jv TV.w WV.Kx YV. @Y.Ly BY.M EY. HY.N KY.z NY.O{" ,"QY. TY.P WY.Q YY.| @\\.R K\\.S N\\.T Y\\." ] , ss: // segs - h[(..;..N..T] and v[V..a..s..|] - s(tart node)e(nd node)s(paces) ["<=D =>E ?@E @AD BCD CDB DEB EFB FGB GHB HID JKD LMB NOB PQD RSB STB TUB VWD" ,"WXB YZB Z[D \\]H ^_D _`B `aB bcB cdB deD fgA hiB ijB jkB klB lmB noB pqB qrB" ,"stB uvB wxB xyB z{J {|B |}J <BC =CC >EC ?FC @HC AIC BJB CKB DLB GOB HPB IQB" ,"KWE MSB NTB PzE RxB UyB W_E X\\B Y]B ZdE \\`B ]cB ^fB _hB ajB bkB dmB eoB" ,"gqB hrB isB lvB mwB nxB pzB t{B u|B y}B" ] , ini: function() { (this.bs = this.bs.join(" ").split(" ")).forEach(function(b){ // do some stuff with the 'encoded' b-order information... }); (this.ns = this.ns.join(" ").split(" ")).forEach(function(n){ // do stuff with the 'encoded' n-ode information... }); (this.ss = this.ss.join(" ").split(" ")).forEach(function(s){ // do stuff with the 'encoded' s-egment information... }); }; o.ini();
-
part 5 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
PS: If you want to see the raw data in the console all the times, add a line handler and callback tuple with an alway false returning line handler as last to the handlers. This will get you all lines send by the GPS receiver printed in the console.
var handlers3 = [ [ null // re-using GPS module's ggaHandler... , callback2 // ...w/ callback2 ] , [ function(line,callback) { // rmcHandler var tag = line.substr(3,3); if (tag=="RMC") { var d = line.split(","); callback( { timc: d[1].substr(0,6) , latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1) , lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1) , velc: parseFloat(d[7]) , angl: d[8] , datc: d[9] }); return true; } else { return false; } } , callback ] , [ function(line,callback) { callback(line); return false; } , function(data) { console.log("raw > ",data); } ] ];
Output in the console:
cbk2 > {```
"lat": 41.2242, "lon": -102.87471116666, "fix": 2, "satellites": 9, "altitude": 92.4 }```
raw > $GPGSV,3,1,10,02,31,052,15,05,53,089,37,10,15,042,21,12,42,164,33*79```
raw > $GPGSV,3,3,10,46,38,145,,51,44,156,38*7C```
raw > $GPRMC,131934.00,A,4113.45229,N,10252.48332,W,1.188,204.02,300914,,,D*7A```
"timc": "131934",```
"angl": "204.02",```
}
raw > $GPVTG,204.02,T,,M,1.188,N,2.199,K,D*3F
End of 5 parts series: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
-
Some pointers about the implementation of JS in Espruino is very welcome here. I provide an example from code I'm working on (Part of this conversation - including some helpful responses - began in http://forum.espruino.com/conversations/255757/ about GPS module and Extensions there of. It is now its own conversation for separation of concerns.)
I needed an array of strings - keys - for processing. So I created an array of strings in the source code and it got me quickly out of memory ( #outofmemory ) into the 'out of memory jail':
var keys = [ "key000" , "key001" , "key002" , "key003" // ...and many more... , "keyNM9" ]; keys.forEach(function(k) { console.log(k); }
In a desperate move - with no real rational but just some guessing - I chose to group the keys w/ space as separator in fewer strings and apply some joining and splitting before processing to keep the same algorithm (because of performance) - and 'miraculously - it got me out of 'out of memory jail' (The joining-splitting with/by space is possible because the keys cannot include spaces):
var keys = [ "key000 key001 key002 key003 key004 key005 key006 key007 key008 key009" , "key010 key011 key012 key013 key014 key015 key016 key017 key018 key019" , "key020 key021 key022 key023 key024 key025 key026 key027 key028 key029" // ...and many more... , "keyNM0 keyNM1 keyNM2, keyNM3, keyNM4, keyNM5, keyNM6, keyNM7, keyNM8, keyNM9" ]; keys = keys.join(" ").split(" "); keys.forEach(function(k) { console.log(k); }
There are several conversations out there about memory and out of memory and I reference them in this list:
- http://forum.espruino.com/conversations/1724/ clearTimeout and WARNING: Out of memory while appending to array
- http://forum.espruino.com/conversations/1578/
GPS code/module/library leaking memory before 'satellite lock' occurs - http://forum.espruino.com/conversations/1192
Out of memory? - [http://forum.espruino.com/conversations/1032](http://forum.espruino.com/conversations/1032/ Memory usage
But none of them can explain the following:
You may have notice in the above code the line
keys = keys.join(" ").split(" ");
Even though I then kept/stored the joined-splitted-processed array, I was kept out of 'out of memory jail'. It must have to do something with how source code of arrays of string literals is pulled in and hold onto.
What is the 'somewhat puzzling' key-note to take from this experience?
PS: I had temporarily similar experience as mentioned in 2nd reference above, even though I used the fixed version of the .indexOf(...) - [1v70]. The issue showed in the GPS modules handleGPSLine when going for the decimal points with indexOf('.') in the lines received from the GPS receiver. Could it be that the .indexOf() String method is still a bit 'weak on its legs'. Having read about it I felt it had something to do by releasing the search argument string... but I may be totally wrong about ti. As said: it was temporary. Nevertheless, observing it cannot hurt.
- http://forum.espruino.com/conversations/1724/ clearTimeout and WARNING: Out of memory while appending to array
-
...may be I tapped into memory beyond the safety margins, which is a sure call for disaster... I'll start a new conversation - http://forum.espruino.com/conversations/255954 - for that memory stuff and cross reference to keep this one focused on the GPS with the tint of Extensibility and what is right for Espruino.
-
@DrAzzy, this explains why I ran out of memory... but how do you explain that after adding even more logical strings (keys) in less physical strings, but then storing them back with the old format - 1 key per element - out of memory condition was avoided? The explanation would be: A string takes 1 jsvar per 12 characters in the source code. Does that sound right?
-
part 4 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
@Gordon, had some time to think... and code... under the influence of your input.
The code below is a first attempt.
// GPS2.js // ---- beg (modified) GPS module code function ggaHandler(line, callback) { var tag = line.substr(3,3); if (tag=="GGA") { var d = line.split(","); var dlat = d[2].indexOf("."); var dlon = d[4].indexOf("."); callback({ tag: tag, time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2), lat : (parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1), lon : (parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1), fix : parseInt(d[6],10), satellites : parseInt(d[7],10), altitude : parseFloat(d[9]) }); return true; } else { return false; } } function handle(line,handlers) { var idx = handlers.length; while(idx > 0) { idx--; try { if (handlers[idx][0](line,handlers[idx][1])) return; } catch(x) { if (console) console.log("GPS:",x); } } } var connect = function(serial, handling) { // replace with next line for module // exports.connect = function(serial, handling) { var handlers = (typeof handling == "function") ? [[null,handling]] : handling; if (!handlers[0][0]) handlers[0][0] = ggaHandler; var gps = { line:"", handle:handle, handlers: handlers, enabled: true }; serial.on('data', function(data) { gps.line += data; var idx = gps.line.indexOf("\n"); while (idx>=0) { var line = gps.line.substr(0, idx); gps.line = gps.line.substr(idx+1); if (gps.enabled) gps.handle(line, gps.handlers); idx = gps.line.indexOf("\n"); } if (gps.line.length > 80) gps.line = gps.line.substr(-80); }); return gps; } ; var gps = {connect: connect}; // drop line for module // ---- end (modified) GPS module code // common setup console.log("Serial4 > ",Serial4); console.log("Serial4.setup() > ", Serial4.setup(9600,{tx:C10,rx:C11})); // var gps = require("GPS"); // uncomment when using GPS code as module console.log("gps = req(GPS) >",gps); // (default) callback for gps var callback = function(data) { console.log("data > ",data); }; function go() { // connects the gps and gets it handling the lines console.log("gps.connect() >", gps = gps.connect(Serial4,callback)); } function pause() { // pauses the handling of lines gps.enabled = false; } function resume() { // resumes the handling of lines gps.enabled = true; }
Output in console:
"time": "09:22:57",```
data > {```
"lat": 41.22417449999, "lon": -102.87471283333, "fix": 2, "satellites": 8, "altitude": 74.8 }```
Basically - for the usage of the module - either:
- a callback function accepting a the data object of the module's line handler is passed
or:
- an array of tuples of lineHandlers and callbacks are passed.
Basically - for the implementation -
- Paired / tuples - or chain - of line handlers and callbacks stored as handlers are used (implemented as array of 2-elements-arrays, with lineHandler as first and callback as second element of the 2-elements-arrays). A lineHandler looks at the line and calls the paired up callback with the handled line data. ggaHandler() - current module's handleGPSLine - is an example of a lineHandler. callback is an example of a callback and does the 'stuff' the GPS module user wants to have done for the line by the (paired up) lineHandler.
- For each line from the GPS receiver, the module iterates over this array of handlers in handle() function and stop looping when handler did 'hit'. The handlers, such as ggaHandler() handling GGA tagged lines, return true when handling the line, otherwise return false (line 38).
- The same data structure and algorithms are used for both 100% compatible basic and extended mode: if first tuple's lineHandler is null (evaluates to false), the modules basic ggaHandler() is reused for it (line 40).
- Like line, handle() and handlers[] - and an extra enabled flag - are kept in the object returned by the connect() function
Btw, I would consider above approach a Lambda or functional approach, where after module load 'no' application nor object oriented object is (yet) available, but merely a bootstrap function, which is not even equally to a 'new' or a constructor. This 'into an object wrapped' function has first to be called in order to get a (useful) object (with state, and now also with function) back. - Btw, what is/was the rational of returning the {line:""} object on .connect()? - The module documentation http://www.espruino.com/Writing+Modules follows more the object-oriented approach, even though - due to the nature of JS - the 'class definition' is a function. For the GPS module I would like to have a similar approach chosen, even if the GPS stays a Singleton / would not have a constructor, so that already with require("GPS") an object is available for custom alteration. I will look into this option in a next phase while - of course - keeping 100% compatibility with current 'usage documentation'.
I still try to find what's right for Espruino. Having now lived many years - and still live - in a world of memory abundance, doing the right thing for Espruino is a welcome challenge. Not that I waste memory or cycles - I actually have the opposite habit - because I started out with having just 16KB (Kilo bytes) for application AND data AND ANY utility code - such as ISAM-create/read/update/delete, input routines, etc., and some of my Espruino-like hardware real world projects had just 256 bytes and 1KB EPROM and 4MHz 8-bit processing capabilities. Since then there is always the frugal guy in the back of my head watching over my spending of both memory and cycles, because even in today's memory and cycle paradise resources are limited... if not technically, then more so economically (currently building software for 15K+ concurrent transactional users - where user AND operator has to be kept happy: first one w/ snappy response times and latter one w/ spending 'no fancy' $s).
@Gordon, some pointers about the implementation of js in Espruino is very welcome here. I provide you an example in more detail at (see http://forum.espruino.com/conversations/255954) from code I'm working on: An array with a lot of small string elements in source code uses obviously more memory than one with few but long strings that later are chopped up into the small ones and stored again in the very same array. The short string are of the form of "keyNMO".
Back to the GPS discussion at hand:
Add the following code to the above one, send code to board, and use command go2() in the command pane to get the gps connected and handling lines (just) with the custom RMC line handler instead of the module's own ggaHandler. For simplicity, same already known callback is used.
var handlers = [ [ function(line,callback) { // rmcHandler var tag = line.substr(3,3); if (tag=="RMC") { var d = line.split(","); callback( { timc: d[1].substr(0,6) , latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1) , lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1) , velc: parseFloat(d[7]) , angl: d[8] , datc: d[9] }); return true; } else { return false; } } , callback ] ]; function go2() { // connects the gps and gets it handling the lines console.log("gps.connect() >", gps = gps.connect(Serial4,handlers)); }
Output in console:
"timc": "103314",```
"angl": "",```
}```
"timc": "103315",```
"angl": "",```
}```
You may notice that the ggaHandler 'is lost' for good... (became inaccessible for good).
The module's valuable ggaHandler can though easily be reused/included in the custom handling. Just provide the first tuple of 'handler duos' with null (no) line handler. On connect, the module's ggaHandler will be pulled and paired up with the callback provided in the first handler duo. To make that obvious / visible, a different calback2 call back is provided.
Add the following code to the above one, send code to board, and use command go2() in the command pane to get the gps connected and handling now two lines, the GGA tagged line with the module's own line handler and callback2(), and the RMC tagged line with the custom line handler and initially established callback().
// special callback for in reuse of GPS's module own ggaHandler var callback2 = function(data) { console.log("cbk2 > ",data); }; var handlers2 = [ [ null // re-using GPS module's ggaHandler... , callback2 // ...w/ callback2 ] , [ function(line,callback) { // rmcHandler var tag = line.substr(3,3); if (tag=="RMC") { var d = line.split(","); callback( { timc: d[1].substr(0,6) , latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1) , lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1) , velc: parseFloat(d[7]) , angl: d[8] , datc: d[9] }); return true; } else { return false; } } , callback ] ]; function gox() { // connects the gps and gets it handling the lines console.log("gps.connect() >", gps = gps.connect(Serial4,handlers2)); }
Output in console:
"timc": "124700",```
"angl": "",```
}```
"time": "12:47:01",
I'm not really sure about the overall value of enabled - but I liked the option to to shut up the console... - last but not least for copying console output to this post without having a nervously scrolling away text - or, in the target application - to quiet down the display... ;)
What should be discussed is the value of the try-catch-block. In different context I had issues with the process stopping after some time - less than one hour - and errors showing in the console. Especially with line handlers that do not take into account when the GPS is not yet providing complete data, for example, while still trying to get enough satellites for decently reliable position determination.
What I'm though confident about is:
- 100% compatibility
- Easy, simple, but quite powerful and flexible extensibility
- Handling can be modified even after being established
- Extensibility of the extension showed in the example, for example, adding the a 3rd array element to name the tuples for addressable change or removal - preferably when disabled
- ...and many things more.
part 4 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
- a callback function accepting a the data object of the module's line handler is passed
-
I'm in very 'violent' agreement with the 100% backward compatibility - nothing is as annoying as trying something out of the box and it does not work... and stops working after a while.
Other comments:
- Had something like this with third argument in my first - not posted - elaboration.
- Do not like to have to provide 2nd argument when using extended mode.
- Do not like the comparison inside the loop that is done over and over again.
- Do not like the constraint that handlerFn's return value has to evaluate to true.
- Module provides still no means to start/stop/enable/disable line handling.
- I like very much the leanness of the basic GPS module.
- Would like to foster more object oriented approach in building and using modules and components - already done in many places where an object is provided as a parameter and not just a function-object (even though, JS allows to stick 'things' - value and function properties - it, but that is dangerous...).
Therefore I chose to use different type(s) for 2nd argument. Type is handled only once at connect execution time. The handler object's constructions allows to reuse the existing line handling.
@Gordon, you did not mention the addition of the tag in the existing handleGPSLine() function. Would you consider the code with the addition still compatible?
Furthermore, you did not mention the renaming of the internals. Would that keep the compatibility for the people?
May be a general discussion about initial setup of modules whit very limited, exemplary functionality is worth a discussion. The questions to answer would be:
- What is the purpose of a module?
- Who is the audience for a module?
- How is extensibility rated?
- How about the module's general 'structure': Lambda or Object oriented - or both?
- How could extensibility - particularly for GPS module - be implemented - override - addition - registration?
- Had something like this with third argument in my first - not posted - elaboration.
-
@DrAzzy - oooops... how do I look... - pretty st..id. But thanks!
-
@Gordon, sorry to bug you again... ;-) - I noticed that the Web IDE has a selection issue in the command pane. I'm not totally sure if it is 'right from the beginning' (after a system restart, chrome restart, Web IDE restart) or some time into the usage. I noticed it when I wanted to copy part of the console output and imbed it into a forum post. The work-around is to move the pointer out of the pane and then release the mouse button. If released within, the selection goes away. The disadvantage is that either every thing to the begin or end is then included in the copy for the paste. Pasting into another editor and then pull only the part of interest kept me going. While selecting the background darkness changes. The selection blue-ish marking still works while selecting.
-
-
@JumJum, there are multiple TFT LCD displays of different size - 2.2", 2.8", 3.2",... even up to 5.7", all controllable by the ILI9341 controller chip, and therefore have all the same resolution and color depth: 320x240px 262K colors. For helpful information on Espruino site see http://www.espruino.com/ILI9341 - I know that the Buying on eBay link on this site goes for 2.2". Just search directly in eBay with above given sizes, for example: 2.8" TFT LCD. Resolution(s), Color depth(s) and interface(s) are defined by the controller. Some controllers have it all configurable. Some controllers can be cascaded to achieve higher resolutions, for example, https://www.displaytech-us.com/sites/default/files/driver-ic-data-sheet/HX8264-D02_DS_v01.pdf
For more details about the ILI9341 controller see
http://www.newhavendisplay.com/app_notes/ILI9341.pdf #ILI9341 #ili9341datasheet - -
The end goal is to have a GPS that is also connected to other inputs - such as Wind direction, Wind speed, heeling, etc. Of course: there is plenty of electronics to buy (and rely on), but it is a fun and entertaining training project to get all these things working together.
Basis for the basic GPS code (module) is the extended code as posted with title GPS Module, Extensions for handling other sentences, and u-blox NEO-6M GPS receiver on http://forum.espruino.com/conversations/255757/. Get the GPS code from there.
The gpsHandlerObject is picking up and transforming the gps receiver sentences and sending it to the navi object which displays the information on the LCD display (code below).
In this stage, the whole Hardware and Software system works as GPS / satellite driven clock... with time zone adjustment ;). Provisions for handling changes due to jitters and displaying of other essential data are in infancy to absence.
The satelliteClock.mp4 clip - link below the code - shows a send-to-board / restart phase.
// ===== LCD (ILI9341) common setup; B2.set(); SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000}); var lcd = require("ILI9341"); // ----- navi to display (changed) data on (ILI9341 controlled) LDC as 'sent' by GPS var navi = { lcd: null , gpsHandler: null , data: { timAdj: -7 , timc: "" } , timc: function(v,p) { var v0 = this.data.timc; this.data.timc = v; var j = 0, i = 5; while (i > 0) { i--; if (v.charAt(i) == v0.charAt(i)) { j = i + 1; i = -1; } } v0 = v.split(""); if (j < 2) { j = 0; i = parseInt(v[0],10) * 10 + parseInt(v[1],10) + this.data.timAdj; i = (i < 0) ? i + 24 : (i > 24) ? i - 24 : i; v0[1] = i % 10; v0[0] = (i - v0[1]) / 10; } j = (j >= 4 ) ? j + 2 : (j >= 2) ? j + 1 : j; v0.splice(4,0,":"); v0.splice(2,0,":"); // console.log(".......".substr(0,j) + v0.join("").substr(j)); var s = this.fonts[2], x = 240 - 8 * s * 0.8, y = 5; this.setClr(0); this.lcd.fillRect((i=x+j*s*0.8),y,i+(8-j)*s*0.8,y+s*1.45); while (j < 8) { this.drawString(2,1,v0[j],x + j * s * 0.8,y); j++; } } , initDsp: function() { this.lcd.clear(); } , clrs: [ [ 0.0, 0.0, 0.0] // 0 = black , [ 1.0, 1.0, 1.0] // 1 = white , [ 0.8, 1.0, 0.8] // 2 = ... ] , cid: -1 , setClr: function(cid) { if ((cid >= 0) && (this.cid != cid)) { this.cid = cid; var rgb = this.clrs[cid]; lcd.setColor(rgb[0], rgb[1], rgb[2]); } } , fonts: [ 12 // 0 = 'small' , 20 // 1 = 'medium' , 32 // 2 = 'large' ] , fid: -1 , setFont: function(fid) { if ((fid >= 0) && (this.fid != fid)) { this.fid = fid; lcd.setFontVector(this.fonts[fid]); } } , drawString: function(f,c,s,x,y) { this.setFont(f); this.setClr(c); this.lcd.drawString(s,x,y); } , init: function(lcd,gpsHandler) { this.lcd = lcd; this.gpsHandler = gpsHandler; gpsHandler.navi = this; this.initDsp(); } }; // ===== GPS common setup Serial4.setup(9600,{tx:C10,rx:C11}); // var gps = require("GPS"); // uncomment when using GPS code as module // ----- gpsHandler(Object) calls navi to display (changed) data on ILI9341 controlled LCD var gpsHandler = { // GGA (default): tag, time, lat[deg], lon[deg], fix, satellites, altitude[m] // GSA: // GSV (multiples): // GLL // RMC // NMEA's pvs - position, velocity, and speed (plus other things: true angle, date and time, mag var) includesDefaultLineHandler: false, enabled: false, enable: function(ena) { if (ena) { if (!this.enabled) { this.data.reset(); this.enabled = true; } } else { this.disable(); } }, disable: function() { this.enabled = false; }, lineHandler: function(line, handler) { if (handler.enabled) { try { var tag = line.substr(3,3); if/*sel*/ (tag=="GGA") { // console.log(line); handler.handleGGA(line,handler.data.handleChange,handler); // console.log(line); } else if (tag=="RMC") { handler.handleRMC(line,handler.data.handleChange,handler); // } else { // gpsHandlerFunction({"raw":line}); } } catch (x) { if (console && console.log) { console.log(x); } } } }, handleGGA: function(line,callback,handler) { var d = line.split(","); callback({ tag:"GGA", // fix : parseInt(d[6],10), sata : parseInt(d[7],10), alta : parseFloat(d[9]) },handler); }, handleRMC: function(line,callback,handler) { var d = line.split(","); callback({ tag:"RMC", timc: d[1].substr(0,6), latc: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1), lonc: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[4]=="W"?-1:1), velc: parseFloat(d[7]), // angl: d[8], datc: d[9] },handler); }, navi: null, data: { handleChange: function(d,handler) { // console.log(d); for (var p in d) handler.data.changed[p](d[p],p,handler); }, reset: function() { this.latc = -999.9; this.lonc = -999.9; this.velc = -999.9; this.alta = -999.9; this.sata = 0; this.datc = ""; this.timc = ""; }, tag: "-", latc: -999.9, lonc: -999.9, velc: -999.9, alta: -999.9, sata: 0, datc: "", timc: "", latcd: 0.0, loncd: 0.0, velcd: 0.0, altad: 0.0, satad: 0.0, latct: 0.0, lonct: 0.0, velct: 0.0, altat: 0.5, satat: 0.1, changed: { adj: -7, // -7 = UTC-PSD, -8 = PST-UTC tag: function(v,p,h) { h.data[p] = v; }, latc: function(v,p,h) { var v0 = h.data[p]; var d = v0 - v; h.data[p] = v; }, lonc: function(v,p,h) { var v0 = h.data[p]; var d = v0 - v; h.data[p] = v; }, velc: function(v,p,h) { var v0 = h.data[p]; var d = v0 - v; h.data[p] = v; }, alta: function(v,p,h) { var v0 = h.data[p]; h.data[p] = v; }, sata: function(v,p,h) { var v0 = h.data[p]; h.data[p] = v; }, datc: function(v,p,h) { var v0 = h.data[p]; h.data[p] = v; }, timc: function(v,p,h) { h.data[p] = v; h.navi[p](v,p); }, } } }; // function naviInit() { lcd = lcd.connect(SPI1, B6, B8, B7, function(){ setTimeout(function(){ gps.connect(Serial4, gpsHandler); navi.init(lcd,gpsHandler); gpsHandler.enable(true); },100); }); // }
The satelliteClock.mp4 clip (link below) shows a send-to-board / restart phase. Notice the nice skipping of displaying some time, and also that only the part of the string that changes is redrawn. Uncommenting line 28 will show the not redrawn part as dots followed by the part to redraw on the LCD in the console pane (see below). Most of the code of .timc() method is dedicated to that part and is combined with the time zone adjustment for display. In other words, internally, UTC is used, and converted only to local time for display.
>echo(0); =undefined 12:50:59 ....1:00 .......3 .......4 .......5 .......6 .......7 .......8 .......9 ......10 .......1 >gpsHandler.disable() =undefined >gpsHandler.enable(true) =undefined ......48 .......9 ......50 .......1 .......2 .......3 .......4 .......5 .......6 .......7 .......8 .......9 ....2:00 =undefined >gpsHandler.enable(true) =undefined ......49 ......50 .......1 .......2 .......3 .......4 .......5 .......6 .......7 .......8 .......9 ....3:00 .......1 .......2
The difference detection algorithm has though still a flaw! What is the flaw? When will it show? And how could it be fixed with practically the same code lines?
-
part 3 of 5 of: GPS Module, Extensions, and u-blox NEO-6M
Example C_ shows - finally - extensions with two line handlers in addition the to reused defaultLineHandler() of the GPS module:
- transforming line handler implementation for the NMEA $GPRMC sentence
raw line handler - handling all sentences not handled by transforming line handlers
// C) for enhanced use: gpsHandlerObjectDefaultAndOther reusing basic function as in A and other extensions var gpsHandlerObjectDefaultAndOther = { // GGA (default): tag, time, lat[deg], lon[deg], fix, satellites, altitude[m] // GSA: // GSV (multiples): // GLL // RMC // NMEA's pvs - position, velocity, and speed (plus other things: true angle, date and time, mag var) includesDefaultLineHandler: true, handle: true, lineHandler: function(line, handler) { if (handler.handle) { var tag = line.substr(3,3); if/*sel*/ (tag=="GGA") { handler.defaultLineHandler(line,changeHandler); } else if (tag=="RMC") { handler.handleRMC(line,gpsHandlerFunction); } else { gpsHandlerFunction({"raw":line}); } } }, handleRMC: function(line,callback) { var d = line.split(","); callback({ tag:"RMC", timec: d[1].substr(0,6), latc: d[3].substr(0,2) + "_" + d[3].substr(2) + "_" + d[4], lonc: d[5].substr(0,3) + "_" + d[5].substr(3) + "_" + d[6], velo: d[7], angl: d[8], date: d[9] }); } };
Use the related goEnhancedWithDefaultAndOther() function to run the example.
function goEnhancedWithDefaultAndOther() { gps.connect(Serial4, gpsHandlerObjectDefaultAndOther); }
The raw handler shows the line 'as is' in the console for the purpose of exploring all the sentences the GPS receiver sends on serial data event, creating the following out put:
>echo(0); =undefined >goEnhancedWithDefaultAndOther() =undefined { "raw": "1\r" } { "tag": "GGA", "time": "09:11:24", "lat": 41.22417566666, "lon": -102.87468133333, "fix": 2, "satellites": 11, "altitude": 70.3 } { "raw": "$GPGSA,A,3,13,51,07,20,46,16,08,10,09,27,23,,2.73,1.11,2.49*08\r" } { "raw": "$GPGSV,5,1,17,02,02,309,,03,22,042,,06,09,273,,07,45,265,27*7F\r" } { "raw": "$GPGSV,5,2,17,08,65,279,41,09,62,328,22,10,30,309,28,13,57,326,26*7E\r" } { "raw": "$GPGSV,5,3,17,16,46,059,31,19,02,135,,20,41,182,34,23,71,061,45*74\r" } { "raw": "$GPGSV,5,4,17,27,12,106,18,30,16,250,,32,06,155,,46,42,142,34*77\r" } { "raw": "$GPGSV,5,5,17,51,44,156,32*48\r" } { "raw": "$GPGLL,4113.45054,N,10252.48088,W,091124.00,A,D*7E\r" } { "tag": "RMC", "timec": "091120", "latc": "41_13.45054_N", "lonc": "102_52.48076_W", "velo": "0.035", "angl": "", "date": "270914" } { "raw": "$GPVTG,,T,,M,0.035,N,0.066,K,D*20\r" } { "tag": "GGA", ..... ... .
part 3 of 5 of: GPS Module, Extensions, and u-blox NEO-6M
- transforming line handler implementation for the NMEA $GPRMC sentence
-
part 2 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
All code fragments of previous post and this one can coexist and can therefore be copied and pasted into the Web IDE edit pane, sent at the same time to the board, and individually invoked from the console pane or custom made functions.
The following setup is common to both - basic and enhanced - modes. Again, notice comments for the inline vs. module way of using the code.
// common setup Serial4.setup(9600,{tx:C10,rx:C11}); // var gps = require("GPS"); // uncomment when using GPS code as module
The code below - example A) - shows the basic mode usage and validates the backward compatibility.
// A) for basic use: gpsHandlerFunction as callback as per ESPRUINO doc var gpsHandlerFunction = function(data) { console.log(data); };
For convenience - and coexistence in the Web IDE and sending to board - I added a goXyz...() function to be invoked in the command pane to run the vaious modes A) through C).
The goBasic() function connects the gps and passes the callback handler function .gpsHandlerFunction() as specified in A) above. This function is the same as used in Espruino's module documentation - http://www.espruino.com/GPS. Instead of defining it inline while passing it, it is defined as a variable and used in all scenarios to write to the console the data objects which is created by the various line handlers.
function goBasic() { gps.connect(Serial4, gpsHandlerFunction); }
After sending the code to the board, enter goBasic() into the command pane to use the GPS module in basic mode.
Example B) is kind of a twitter: uses an wrapping line handler to wrap the defaultLineHandler() of the GPS module and is thus reusing it. The .includeDefaultLineHandler property with value true, makes the enhance GPS module to add the defaultLineHandler() function as same named method to the passed handler object.
// B) for enhanced use: gpsHandlerObjectDefaultOnly reusing just same basic function as in A) var gpsHandlerObjectDefaultOnly = { includesDefaultLineHandler: true, lineHandler: function(line, handler) { handler.defaultLineHandler(line,gpsHandlerFunction); } };
Invoke the function below in the command pane to run example B).
function goEnhancedWithDefaultOnly() { gps.connect(Serial4, gpsHandlerObjectDefaultOnly); }
part 2 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
-
A five part series
In preparation for a navigation and tracking device with GPS and Display I looked into using the GPS module. The current module is very frugal. It supports just the NMEA $PGGA - essential Fix 3D position - sentence - basically: time, longitude, latitude, and altitude.
Initially I wanted to use my 20+ years old Magellan marine GPS with RS232 serial connectivity, but I dropped it in favor of a more recent and readily available ublox NEO-6M module, the same as used in Espruinos GPS moduel doc - http://www.espruino.com/GPS.
Hardware and software connectivity worked right away out of box. It took some time for the GPS module to pickup the satellites, but everything came together nicely.
From the past I knew that NMEA has also sentences that deliver, bearing, speed, etc... Speed over ground and bearing are part of a the NMEA $PGRMC - essential pvt (position/velocity/time) - sentence.
Looking into the implementation of the GPS module to extend it with a handler for other sentences - handleGPSLine()-methods - was a bit sobering... to say the least. My basic expectation was to find an externally extensible sentence filter with hooks and registration for other line handlers, or at least a way to override the already implemented, default GPS line handler. Unfortunately, the module is built in a way that ,gives no access what so ever for modifications or extensions. ;(
Sure, it is not much code there, but what I liked and noticed by rolling up the history of the modules especially in the forum, that quite some enhancements and validation went into the on.data method to extract and process available - (CR) LF delimited = lines.
Below is some code that is backward compatible and allows even the reuse of the existing default lineHandler for the use in extended mode. (The code is temporarily adjusted to run embedded while elaborating on the extensibility - see respective comments.)
// ---- beg (modified) GPS module code function defaultLineHandler(line, callback) { var tag = line.substr(3,3); if (tag=="GGA") { var d = line.split(","); var dlat = d[2].indexOf("."); var dlon = d[4].indexOf("."); callback({ tag: tag, time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2), lat : (parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1), lon : (parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1), fix : parseInt(d[6],10), satellites : parseInt(d[7],10), altitude : parseFloat(d[9]) }); } } var connect = function(serial, handler) { // replace with next line for module // exports.connect = function(serial, callback) { var gps = {line:""}; var lineHandler; if (typeof handler == "function") { lineHandler = defaultLineHandler; } else { lineHandler = handler.lineHandler; if (handler.includesDefaultLineHandler) { handler.defaultLineHandler = defaultLineHandler; } } serial.on('data', function(data) { gps.line += data; var idx = gps.line.indexOf("\n"); while (idx>=0) { var line = gps.line.substr(0, idx); gps.line = gps.line.substr(idx+1); lineHandler(line, handler); idx = gps.line.indexOf("\n"); } if (gps.line.length > 80) gps.line = gps.line.substr(-80); }); return gps; } ; var gps = {connect: connect}; // drop line for module // ---- end (modified) GPS module code
Btw, I added the tag into the line handler's return object. It is useful when having only one callback for multiple sentences.
The basic idea of detecting and operating in the extended mode vs. the basic mode is by passing a handler object vs a handler callback function. Line 23 implements the detection. With this approach backward compatibility is given, and extensibility is opened up.
I adjusted some names to make the code extensions and the multi purpose use of the existing implementation pattern obvious.
The existing internal - not exposed - .handleGPSLine() function is renamed to defaultLineHandler, and is assigned to an internal variable named lineHandler in basic mode - when a in the .connect() a callback function is passed as handler (line 24). This variable is then used in the serial's on-data callback for each detected line (line 37). The function expects - as in the original code - a line and the external, custom callback for the line handler for invocation with the data object as built from the line.
The above setup to retain backward compatibility create constraints/requirements for the handler object in the enhanced mode: the handler object has to have a .lineHandler() method that accepts the same line and callback function arguments as the (existing) .handleGPSLine() - respectively renamed - .defaultLineHandler() function. This handler object's .lineHandler() method is then also assigned to the same internal variable named lineHandler, and corollary, is then also invoked by the serial's on-data callback for each detected line (line 37).
When the handler object includes a true evaluating .includeDefaultLineHandler property, it is complemented with the .defaultLineHandler() function as same-named method and therefore accessible in the handler object's lineHandler method - as the follow up post will show.
part 1 of 5 of: GPS Module, Extensions, and u-blox NEO-6M GPS receiver
-
-
-
@Gordon, thanks for the quick response.
I'm glad and relieved that the mixed space/tab indenting behavior is a regression... so nobody is above Murphy's law, not even Google (with their 37.... Chrome release). Yes, this editor- created mixed/tab-indentation with automatically following warning - and me straightening out every time it happened - was the straw that broke the camel's back (...do I have a weak back?).
Regarding the save: there is currently a combined save/save as in place (only one way to initiate a save:" hit the floppy icon). I'm fine with that, even with the all the time notice to overwrite or not. I just checked / verified in 0.47.1 - again: Loaded file f1.js, hit save and gave name f2.js. Hit save again, and it (still) suggests: Save as f1.js. I would expect f2.js.... may be it is just me (or the the editors I used in the past that did train me this way... 'humans are animals of customs/habits')
Remembering the last used name in save I would see as a good thing, as well as displaying that name in the IDE - top (title or blue bar right next to the connect icon) or bottom bar (bottom right). - Btw, I noticed that more recent OS X included Utility SW goes similar ways of not changing to the newly given name... even to the point, where closing makes an automatic, silent save... UX basics: thinking and silently executing for the user is good, too much (assuming what the user wants) is worse then doing nothing at all.
-
@Gordon, not really complaining just expressing a bit my frustration with the Web IDE source editor: the frustration grows by the hours. There comes a point where a plain - JS agnostic - editor is preferred over a stubborn and single minded editor. I see the 'well intentions' to support writing syntactically formatted, good looking code... but many roads lead to Rome, and the road(s) are picked based on the needs.
Most bothering is when the editor creates automatically things that it then points out as warnings. As mentioned before, when typing a colon or a curly bracket 'down the line' in a line, the editor thinks I need to adjust the formatting / indentation and it creates a space-tab-mixed indentation - looks like derived from the previous line, event though the current line has already a clean, non-mixed indentation, and so has the previous line. It is anyway not clear what the indentation policy is the editor follows.
It is not good enough to derive formatting from the syntax alone, which - for most JS supporting editors - is still script and function oriented vs. object oriented. May be knowing about the provenience of the underlaying editor could be helpful to adjust and work with and around the uniquenesses / caveats.
On another note, I see the well intentions to make coders aware of things such as == and === and mistaken = (assignments) in logical expressions for ifs and consort - and indeed - the latter gave me grief in the past. But the warnings 'get at me' - and even get the editor's code validator: the validator gives up the really helpful error detection and marking just because there were warnings. I'm now moving code around so the sections I'm working on are in the beginning where 'my warning allotment' is not yet used up and the very helpful syntax error detection support still works.
I'd like to change some of the nasty JS things as well... but differences to the defacto JS standard bother me more. I hope over time things move more as they already do, and as they did for example for HTML: You may recall the approach of XHTML 1.0 (HTML 4.1 / 4.01), which failed miserably after years to get XHTML 2.0 spec out the door. It did not fail because of missing well intentions, but because history can just not be ignored: existing code base and (human) resources . The solution was to abandon the idea of radical XHTML and have a smoother migration path... and HTML5 came into being and is picking up - even with the few missing backward compatibilities. The browsers still know to handle past - not so clean - pages (may be that's why the browsers are now all that bulky / big foots / memory foot print... and I'm aware of Espruino's resources are just too limited to work under all circumstances, so a line has to be drawn. The best line is the line with the least differences that benefit maximum simplification for Espruino - most bang for the buck - $ - £ - CHF -... ;) ).
Therefore my question: what would be the easiest way to make the editor just talk (real (Espruino) JS) errors and nothing else? ...or make the editor file change time stamp sensitive, so another editor - for example, Eclipse - can be used and WEB IDE checks and informs time stamp changes with optional refresh-from-file-storage before sending to the board?.. I know that I loose much of the coding support - especially the cool board custom specific support - that just as been added. After using that great support for getting the connectivity modules for the hardware and base software modules done though, I spend most of the time in algorithms processing the data, which has nothing to do anymore with PINs, LEDs, or board specific JS, etc. and that support is just idling... and - too bad - not missed when working in another editor.
Another weird thing is that when saving a file with a different name than it was originally loaded with - like a save as... - the next save's default name will still hold on to the previous file name - and got me overwriting things - not really a surprise, because every overriding save asks (annoyingly every time) ... override or not? - even when saving with the same name. There are obviously different philosophies out there about saving with a new name and saving with the same name. A mitigation with minimal effort would be to show the file's name in-edit - which is known as the file's default safe name - somewhere in the UI, for example in the top blue bar, which now looks pretty inviting (...or empty).
-
@DrAzzy, looks like downgrading can be skipped. Soon 38 will show up as stable: http://forum.espruino.com/conversations/254907/#comment11843402
-
-
@Gordon, in regard to JS's CR LF behavior: I'm aware of some, of which the worst is a line ending with a blank return statement and - for what ever reasons - the value to return on the following line. Doing JS since shortly after its inception, I learned to live quite well with that and - for my own sanity - I go even further also for other things that the strict syntax is not asking for. For example, in (multi termed) logical expressions, returning of expressions, etc., I use extra parentheses with never ever having had regrets abot. If the runtime has issues with that, let's fix it there. (The last sentence may though lead to a regret(s), because I do not know what I'm talking about in respect of Espruino, even though I built multiple VMs and JIT (execution) code generators for custom / tech / business domain languages incl. garbage collection in all kinds of environments, from main frame 'down' to single chip computers - some of them now running 30+ years and still going - deep buried in all kinds of apps.)
To me, it does not matter if the compiler can 'read' it or not - of course I do, because if the compiler cannot, there is a syntax error and never useful execution. To me it matters that I can read it, and that I quickly understand what's (obviously) going on, even when just glancing over it. It matters to me that the style of writing conveys as much of the underlaying semantics as possible so that a human can read it like a good novel. Coding is an art - and hard work: only the fifth to tenth (refactored) version is worth 'hanging on walls' (even though it is still true that 'good looking' things are not necessarily good, but good things do look good... because the inner beauty shines through).
A major part of good coding is choosing the right names for the 'things' - even with space constraints and thus shortened names. I'm aware of minification which can take care of drawback of long names and lots of comments... but how many pieces of code do really go all the way through that final step? If is not done in a JIT - just in time - way, then then JS's delivered promise of quickest round trip gets 'lost in translation'.
-
@Gordon, in regard to .fillPoly(): I have no problem what so ever with it for my PacMan game board drawing, because most of the polygons do just fine - actually very good: speed is nice, so I think about to use it even for other things, because sprites or bit maps may take more space and time and be slower. Of course, having a general purpose, more powerful .fillPoly() may be desirable, but it should be a separate: .fillIrreg(). It would be bad to punish implementations of moderate requirements with overly elaborate, but slow, implementations driven by over the top requirements. I'm sure .drawString() has about the same complexity as .fillIrreg() would have. Therefore, I'm not sure about the worth of handling decently regular polygons the same way as irregular polygons... The-'Where do you draw the line?'-question will never go away. Also, there is nothing wrong with a 20 year 'old' - standard scan line - algorithm, that is proven to be fast and covers 80% (or more) of the cases. Think of them the same way as Evergreens in the music business... ;-) - #polygon #draw #fillpoly
In this update, the project uses the GPS module variation with the array of tuples of lineHandlers and callbacks - as discussed in http://forum.espruino.com/conversations/255757 - GPS Module, Extensions, and u-blox NEO-6M GPS receiver.
This is the running code - with a first cut of the display implementation. Since the display poses significant performance challenges, expect updates, and furthermore, expect updates too for the GPS module for handling multiple GPS sentences, for which the implementation has not settled yet. As usual I (will) add shot and clip to see what's going on.
Don't try to copy from below code section into Web IDE: it will corrupt the code. Download the code from uploaded navi5p.js file at the very bottom of this post.
The first shot was taken at the moment when the last digit of the time in top row was re-drawn - digit 8. The clip shows the sequence after 'warm'-reboot (send code to board, GPS already locked in). You will notice the partial drawing... because of the display's performance drawing strings... You can see code of the sort of this.cnt = ... this.cnt + ... and this.cnt2 = ...this.cnt2 + .... These are counters from 0..9 and 0..59 - updated every second - that control partial and staggered full re-draw of the data in addition to the code that triggers partial redraw only on data changes. The redrawing code is still in its infancy and needs to be refactored for less redundancy (copy-paste-modify is good only for figuring something out). The second shot was taken after returning from work with taking the GPS for a spin for the commute.
The display shows - from top to bottom:
Note: the degree symbol - character - is not part of LCD controller's font - assuming the LCD controller takes the character and draws it... (@Gordon, is that assumption correct?)
TBC - to be completed w/ some code walk through in a bit. For now, enjoy the short clips (links below):