-
-
In Windows, when I attach files, I first put them in one directory and then select multiple files at once then send them. Trying to do one at a time requires editing the submitted post.
Both of the files Wilberforce seem to be the minimized version.
How does one go about minimizing a module?
Magic PID tuners exist in the world, there is some variance in PID implementations so one has to use an auto-tune with the matching PID implementation.
There also so rules of thumb methods that cut down on the iterations of a slow process that are required to achieve acceptable results. Google "PID tuning rules of thumb".
The most common heater control is the On/Off method. Some diffusion furnaces I worked with in the past have had SCR based control that varies the current to the heater.tve thanks for the link. The method I used is based on differentiation the basic equation PID so
it calculates the change in output. -
Continued:
Add a method to do one iteration of PID controlPIDobj.prototype.update=function(){ this.N++; var A=this.PID; var inp=this.Input(); this.error=this.Setpoint-inp; this.integral+=this.error*this.Interval/1000; if(this.integral<0)this.integral=0; if(this.integral>1)this.integral=1; this.derivative=(this.error-this.previous_error)*1000/this.Interval; var b=this.out+A.Dir*(A.P*this.integral+A.I*this.error+A.D*this.derivative); if(b<0)b=0;if(b>1)b=1; this.Output(b); this.out=b; console.log(this.N,',',this.error.toFixed(4),',',inp.toFixed(4),",",b.toFixed(4),",",this.Setpoint.toFixed(4)); var Q = E.openFile(this.PID.Fname, "a"); Q.write(this.N,this.error.toFixed(4),inp.toFixed(4),b.toFixed(4),this.Setpoint.toFixed(4)+"\r"); Q.close(); this.previous_error=this.error; };//end update
And finally some code to test it all.
function input1(){ // console.log("in ",analogRead("A1")); return analogRead("A1"); }// end input; function output1(a){ // console.log("Out ",a); analogWrite('A4',a); }//end output function Testit(){ //A1.mode('analog'); //A4.mode('analog'); output1(0.5); //1.0 var PID1=new PIDobj(PID,0.5,input1,output1,1000); input1(); PID1.start(); }//end Testit Testit();
For a simple test on an Espruino board I connected pin A4 to Pin A1.
Some console output:>echo(0); =undefined 1 , -0.0005 , 0.5005 , 0.0000 , 0.5000 2 , 0.4802 , 0.0198 , 0.1441 , 0.5000 3 , 0.3562 , 0.1438 , 0.2509 , 0.5000 4 , 0.2493 , 0.2507 , 0.3652 , 0.5000 5 , 0.1348 , 0.3652 , 0.4672 , 0.5000 6 , 0.0322 , 0.4678 , 0.5602 , 0.5000 7 , -0.0618 , 0.5618 , 0.6384 , 0.5000 8 , -0.1399 , 0.6399 , 0.6965 , 0.5000 9 , -0.1978 , 0.6978 , 0.7309 , 0.5000 10 , -0.2329 , 0.7329 , 0.7409 , 0.5000 11 , -0.2427 , 0.7427 , 0.7282 , 0.5000 12 , -0.2297 , 0.7297 , 0.7065 , 0.5000 13 , -0.2080 , 0.7080 , 0.6879 , 0.5000 14 , -0.1899 , 0.6899 , 0.6707 , 0.5000 15 , -0.1719 , 0.6719 , 0.6553 , 0.5000 16 , -0.1563 , 0.6563 , 0.6412 , 0.5000 17 , -0.1423 , 0.6423 , 0.6284 , 0.5000 18 , -0.1291 , 0.6291 , 0.6168 , 0.5000 19 , -0.1182 , 0.6182 , 0.6061 , 0.5000 20 , -0.1067 , 0.6067 , 0.5965 , 0.5000 21 , -0.0986 , 0.5986 , 0.5875 , 0.5000 22 , -0.0884 , 0.5884 , 0.5797 , 0.5000 23 , -0.0811 , 0.5811 , 0.5723 , 0.5000 24 , -0.0739 , 0.5739 , 0.5656 , 0.5000 25 , -0.0669 , 0.5669 , 0.5596 , 0.5000 26 , -0.0605 , 0.5605 , 0.5542 , 0.5000 27 , -0.0552 , 0.5552 , 0.5492 , 0.5000 28 , -0.0498 , 0.5498 , 0.5448 , 0.5000 29 , -0.0459 , 0.5459 , 0.5406 , 0.5000 30 , -0.0415 , 0.5415 , 0.5369 , 0.5000
Tuning the P, I, and D values is an art. Please consult Google.
Things to try:- Use an RC low-pass circuit between the output pin and the input pin. One or many stages of RC filter.
- Try setting the analogWrite() to a 50 Hz frequency and the pulse width of 1ms for a zero value and 2ms for a 1 value. Connect this output to a servo and use an accelerometer as the input. Mount the accelerometer on the servo arm and have some fun tilting it back and forth. (my parts are on order).
- Setup a temperature sensor and a solid state switch supplying current to a heating element to control an oven temperature.
How to make input, output and setpoint appear to the user in Engineering units?
- Use an RC low-pass circuit between the output pin and the input pin. One or many stages of RC filter.
-
A standard way to implement a feedback control system is the Proportional, Integral, Derivative (PID) control algorithm.
Think of things to control and how different the control action can be.
Driving a car. Use a compass module as the input and a motor to steer the wheels. As a driver you turn and hold the steering wheel until the new heading is achieved. Does a boat work the same way?
When piloting an airplane you turn the yoke to tilt the plane over about 15 degrees and then neutralize the yoke. The plane will circle until you turn the yoke to level the plane. (I’m ignoring rudder action)
When riding a bicycle or motorcycle, once in motion, the controls act opposite to that of a car. Turn right and the bike tilts to the left and you the bike turns left.
So how to implement PID control?
We need:- An input sensor such as a compass module, a thermistor, a thermocouple, an accelerometer, a piezo-gyro etc.
- An output device that can be controlled using either a 0 to 3 Volt Voltage or a pulse width (servo motor).
- The PID algorithm implemented in Espruino.
Using Google we find:
https://en.wikipedia.org/wiki/PID_controller
The pseudocode from the link:
previous_error = 0
integral = 0
start:
error = setpoint - measured_value
integral = integral + error*dt
derivative = (error - previous_error)/dt
output = Kp*error + Ki*integral + Kd*derivative
previous_error = error
wait(dt)
goto startSuppose the setpoint is 0.5 and the measured value is 1.0. Then errpr= 0.5 – 1.0 = -0.5. Let dt = 1 second, then integral = 0.0 – 0.5 * 1 = -0.5 and derivative= (-0.5 -0.0 )/1 = -0.5.
I think we have a problem since in an Espruino world analog inputs and outputs work between 0.0 and 1.0. I tried coding this and ran into this problem.
Another link from a University course just seems to confuse the issue.
http://www.cds.caltech.edu/~murray/courses/cds101/fa02/caltech/astrom-ch6.pdf
I recall from a course taken many years ago that the PID algorithm can be expressed in a differential form so that the change in output is expressed as a function of the PID terms.
doutput = P*sumof(error) + I *error + D*derivative,
Where:
P=Kp= the proportional gain,
I=Ki= the integral gain, and
D=Kd= the derivative gain.
Maybe a math GURU can properly insert the dt values into the equation.First let’s setup a PID object:
//The PID is an object that could be supplied as a stringified file or client //request using http. For development it is coded here. var PID={ Dir:1,//direction +1 or -1 P:0.1,//proportional gain I:0.1,//integral gain D:0.1,//derivative gain Fname:"PID.CSV" //a file to record results in CSV format on the SD card };
The Dir field is used to reverse the control action.
The Fname field is used to record a CSV file.
Now implement an object to use the PIS information and extend it.//The constructor for PIDobj function PIDobj(pid,setpoint,input,output,interval){ this.Interval=interval; //How often to apply algorithm this.Input=input; // a function that returns the process value this.Output=output; //a function that applies the output this.PID=pid; // a pointer to the PID object this.error=0; this.Setpoint=setpoint; this.integral=0; this.derivative=0; this.previous_error=0; this.id=0; this.out=0; this.N=0; // a counter for the CSV file }//end TimeObj //exports =PIDobj;
Adding a method to setup and start the timer
PIDobj.prototype.start = function() { this.N=0; var Q = E.openFile(this.PID.Fname, "w"); Q.write("N,Error,Input,Output,Setpoint\r"); Q.close(); if (this.id) return; this.id = setInterval( this.update.bind(this), this.Interval ); };//end start
Add a method to stop the timer
PIDobj.prototype.stop=function(){ if (this.id) { clearInterval(this.id); this.id = null; console.log(this.PID.Fname, "Finished"); console.log(process.memory().usage); }//endif this.id };//end stop
- An input sensor such as a compass module, a thermistor, a thermocouple, an accelerometer, a piezo-gyro etc.
-
Trying it with process.memory().usage inserted.
function ReadObjectFile(fname){ return JSON.parse(require("fs").readFileSync(fname)); }//end ReadObjectFile function TestIt(){ var j=0; console.log(j," ",process.memory().usage);j++; var T= new (require("TimeObj"))(); console.log(j," ",process.memory().usage);j++; T.updateTime(); //start the stopwatch T.SWstart(); // output day of the week console.log(T.getDay()); Cal=new (require("CalObj"))(ReadObjectFile("Mycalibrations.cal")); console.log(j," ",process.memory().usage);j++; Log=new (require("LogObj"))(ReadObjectFile("Mylog1.cfg"),Cal,T); console.log(j," ",process.memory().usage);j++; Log1=new (require("LogObj"))(ReadObjectFile("Mylog2.cfg"),Cal,T); console.log(j," ",process.memory().usage);j++; Log2=new (require("LogObj"))(ReadObjectFile("Mylog3.cfg"),Cal,T); console.log(j," ",process.memory().usage);j++; //Changes to the onjects can be masde // Log1.A.Interval=3000; // Log1.A.Fname="xx1.csv"; //start the logging Log.doHeaders(); Log.start(); console.log(j," ",process.memory().usage);j++; Log1.doHeaders(); Log1.start(); console.log(j," ",process.memory().usage);j++; Log2.doHeaders(); Log2.start(); // output the elapsed time console.log(T.SWstop().toFixed(3)," ms"); console.log(j," ",process.memory().usage);j++; }//end TestIt setBusyIndicator(LED1); TestIt();
The output:
>echo(0); 0 445 1 453 Sat 2 571 3 717 4 827 5 937 6 958 7 975 418.646 ms 8 997 =undefined xx1.csv Finished 10472.984 ms xx2.csv Finished 20561.429 ms xx3.csv Finished 30603.546 ms >console.log(process.memory().usage); 957 =undefined >
-
Pins used are for an Espruino board.
//Using a rotation matrix to generate sin and cos values //output them on Pin A4 and input them on Pin A1 as analog values. //Pin A4 is wired to Pin A1 for this test A1.mode('analog'); A4.mode('analog'); //console.log(A1.getMode()); //console.log(A4.getMode()); //for angles <5 degrees sin(a)=a, where a is in radians var s=6.28/360; //cos^2 = 1-sin^2 var b=1.0-s*s; // the cos var c=Math.sqrt(b); // a unit vector point along positive x axis var ss=0.0; var cc=1.0; var sss=ss; var ccc=cc; for(i=0;i<361;i+=1){ //sin values analogWrite(A4,0.5+ss/2.0); console.log(i,' ',0.5+ss/2.0,' ',analogRead(A1)); //cos values // analogWrite(A4,0.5+cc/2.0); // console.log(i,' ',0.5+cc/2.0,' ',analogRead(A1)); //Matrix multiply the unit vector times the rotation matrix // [c,-s] // [ccc,sss] = [cc,ss] * [s, c] // ccc=c*cc-s*ss; sss=s*cc+c*ss; ss=sss; cc=ccc; }//next i
And some of the output:
>echo(0); 0 0.5 0.50098420691 1 0.50872222222 0.50928511482 2 0.51744178999 0.51783016708 3 0.52615604967 0.52661936369 4 0.53486234924 0.53565270466 5 0.54355803908 0.54468604562 6 0.55224047282 0.55298695353 7 0.56090700813 0.56226443884 8 0.56955500750 0.57032120241 9 0.57818183907 0.57935454337 10 0.58678487742 0.58814373998 11 0.59536150437 0.59693293659 12 0.60390910979 0.60547798886 13 0.61242509236 0.61329060807 14 0.62090686040 0.62232394903 15 0.62935183263 0.63062485694 16 0.63775743899 0.63868162050 17 0.64612112138 0.64747081712 18 0.65444033447 0.65577172503 19 0.66271254646 0.66407263294 20 0.67093523984 0.67237354085 21 0.67910591219 0.68018616006 22 0.68722207692 0.68848706797 23 0.69528126402 0.69703212024 24 0.70328102082 0.70460059510 25 0.71121891274 0.71314564736 26 0.71909252403 0.72193484397 27 0.72689945850 0.72828259708 28 0.73463734025 0.73658350499 29 0.74230381440 0.74366369115 30 0.74989654779 0.75123216601 31 0.75741322972 0.75904478522 32 0.76485157262 0.76661326009
-
Equipment: Espruino board, USB cable, WebIDE and SD card.
This program makes use of three objects saved in local modules in a WebIDE project.
The first object “CalObj” makes use of a file “Mycalibrations.cal” located on the SD card in the Espruino board.{"calibrations":[ {"name":"Inlet","units":"psig","pin":"A0","slope":100.13897386464,"Intercept":-0.03419722673}, {"name":"Outlet","units":"psig","pin":"A1","slope":50,"Intercept":-25}, {"name":"Motor","units":"Amps","pin":"A2","slope":1,"Intercept":0}, {"name":"Supply","units":"Volts","pin":"A3","slope":1,"Intercept":0}, {"name":"Flow","units":"Liters/minute","pin":"A4","slope":1,"Intercept":0} ]}
The following link lays out the object used to calibrate the analog inputs into engineering units. It was used to create the file “Mycalibrations.cal” on the SD card.
http://forum.espruino.com/conversations/¬286215/The second object “TimeObj.js” is used for its stopwatch function and ability to format date and time from a pattern stored in an array. See the following link.
http://forum.espruino.com/conversations/286424/The third object “LogObj.js” uses the previous two object modules and a “.cfg” file stored on the SD card. This program uses three different “.cfg” files an produces three different “.csv” files. The program used to create the “.cfg” files is “saveLogCfgXX1.js”
One of the “.cfg” files looks like this:{"Fname":"xx1.csv", "Interval":1000, "items":[ {"header":"N","cmd":"count"}, {"header":"Date","cmd":"DATE","Args":["M","-","D","-","Y"]}, {"header":"Time","cmd":"TIME","Args":["H",":","M",":","S"]}, {"header":"A0","cmd":"SAR","Args":["A0",2]}, {"header":"A1","cmd":"SAR","Args":["A1",2]}, {"header":"A2","cmd":"AR","Args":["A2",2]}, {"header":"Board Temp C","cmd":"BTC","Args":[2]}, {"header":"Board Temp F","cmd":"BTF","Args":[2]} ], "count":0, "N":10 }
The “Fname” is the name of the csv file to create.
The “Interval” is the milliseconds between each data sample.
The “Items” array contains records that set up the data to log to the csv file.
The “Header” is the text at the top of a column in the csv file.
The “cmd” can be “count”,“DATE”,“TIME”, “SAR”,”AR”,”BTC” or “BTF”
Date and time commands use the information in the “Args” to format the data.
AR is analog read without calibrations applied.
SAR is a calibrated analog reading. For SAR the header information comes from the calibration file.
For AR and SAR Args[0] is the pin name, and Args[1] is used in a toFixed(Args[1]) to format the data.
The BTC and BTF are the board temperature in F or C units.
Count is used to count and N is the number of samples to takePrograms to load into Project:
LogCSV6a.js, saveLogCfgXX1.jsModules to load into Modules:
TimeObj.js, LogObj.js and CalObj.js
Files on SD card in the Espruino:
Mycalibrations.cal, Mylog1.cfg, Mylog2.dfg, and Mylog3.cfg
Files created on the SD card by this program:
Xx1.csv, xx2.csv, and xx3.csv.LogCSV6a.js listing:
function ReadObjectFile(fname){ return JSON.parse(require("fs").readFileSync(fname)); }//end ReadObjectFile function TestIt(){ var T= new (require("TimeObj"))(); T.updateTime(); //start the stopwatch T.SWstart(); // output day of the week console.log(T.getDay()); Cal=new (require("CalObj"))(ReadObjectFile("Mycalibrations.cal")); Log=new (require("LogObj"))(ReadObjectFile("Mylog1.cfg"),Cal,T); Log1=new (require("LogObj"))(ReadObjectFile("Mylog2.cfg"),Cal,T); Log2=new (require("LogObj"))(ReadObjectFile("Mylog3.cfg"),Cal,T); //Changes to the onjects can be masde // Log1.A.Interval=3000; // Log1.A.Fname="xx1.csv"; //start the logging Log.doHeaders(); Log.start(); Log1.doHeaders(); Log1.start(); Log2.doHeaders(); Log2.start(); // output the elapsed time console.log(T.SWstop().toFixed(3)," ms"); }//end TestIt setBusyIndicator(LED1); TestIt();
and the console output:
>echo(0); Fri 1680.916 ms =undefined xx1.csv Finished 11005.221 ms xx2.csv Finished 21572.389 ms xx3.csv Finished 3202
-
http://forum.espruino.com/conversations/286424/#comment12967777
Attached is an upgraded module timeobj.js and a project file testTimeObj.js.You're correct that new.Date is an object, so the duplications in the object has been removed.
I found one bug in my code in that months are numbered 0 to 11.
The format uses arrays. The 'm' produces the three letter month name.I tried using local modules today in a project directory, great so far.
Thanks for the comments and tips.
-
Setting up a project in WebIDE
In the upper right click on the gear icon and then select project.
Click on Select Directory for Sandbox.
I created a directory called EP1.If you use Windows Explorer to examine the EP1 directory you will find several sub directories created by WebIDE named:
EP1
Projects
Modules
Binary
Firmware
Snippets
Testing
Testinglog
All are empty except for one txt file in snippets.Copy the attached file testAdd.js into the projects folder.
Copy the attached file Add.js into the modules folder.
Load the file testAdd.js from EP1/Projects into the right side of the WebIDE.
Connect to the Espruino device and the run the program.
The testAdd.js code:/*Playing with modules Comments Example of use of local module in code */ var r = new (require("Add"))(1,2); console.log(r.read());
The output:
>echo(0); 3 =undefined >
The module code in Add.js
/* Copyright (c) 2016 MyName See the file LICENSE for copying permission. */ /*Playing with modules Comments: Demo of a simple local module. Dependencies: no other modules are required Example of use code var r = new (require("Add"))(1,2); console.log(r.read()); */ function Add(a,b) { this.A=a; this.B=b; } exports = Add; /* Returns sum of A and B*/ Add.prototype.read = function() { return this.A+this.B; };
For you to try:
Also attached is an upgraded module timeobj.js and a project file testTimeObj.js.
http://forum.espruino.com/conversations/286378/
The output of testTimeObj.js>echo(0); Thu 5/5/2016 5/5/2016 2016/5/5 5/May/2016 May/5/2016 2016/May/5 5 5 May 2016 5-5 May 5 21/30/38 21:30:38 30/38:21 30:38 38 73.672 ms =undefined >
The code in TestTimeObj.js
//Code to test TimeObj function Testit(){ //Create an instance of TimeObj from module file var T= new (require("TimeObj"))(); //Get current time T.updateTime(); //Start stopwatch T.SWstart(); //Day of week console.log(T.getDay()); //Formats for Date var aa=[['D','/','M','/','Y'], ['M','/','D','/','Y'], ['Y','/','M','/','D'], ['D','/','m','/','Y'], ['m','/','D','/','Y'], ['Y','/','m','/','D'], ['D'],['M'],['m'],['Y'], ['D','-','M'],['m',' ','D'] ]; //Display the different Date formats for(var i=0;i in aa;i++) console.log(T.getDate(aa[i])); //Formats for time var bb=[['H','/','M','/','S'], ['H',':','M',':','S'], ['M','/','S',':','H'], ['M',':','S'], ['S'] ]; //Display the different Time formats for(i=0;i in bb;i++) console.log(T.getTime(bb[i])); //Stop and display stopwatch console.log(T.SWstop().toFixed(3)," ms"); }//end Testit Testit();
-
This Object implements a Date and Time formatting methods and a stopwatch function.
TimeObj.js listing follows://The constructor for TimeObj function TimeObj(){ this.Month=1; this.Day=1; this.Year=0; this.Hour=0; this.Minute=0; this.Second=0; this.t=0; this.t1=0; this.t2=0; this.et=0; }//end TimeObj //Methods for TimeObj TimeObj.prototype.updateTime=function(){ //setup date and time this.t = new Date(); this.Month=this.t.getMonth(); this.Day=this.t.getDate(); this.Year=this.t.getFullYear(); this.Hour =this.t.getHours(); this.Minute=("0"+this.t.getMinutes()).substr(-2); this.Second= ("0"+this.t.getSeconds()).substr(-2); };//end updateTime // a="M"; b="/"; c="D"; d="/"; e="Y"; TimeObj.prototype.getDate=function(a,b,c,d,e){ var sss=""; switch (a){ case 'D': sss+=this.Day;break; case 'M': sss+=this.Month;break; case 'Y': sss+=this.Year;break; default: console.log("getDate Error"); }//end switch a sss+=b; switch (c){ case 'D': sss+=this.Day;break; case 'M': sss+=this.Month;break; case 'Y': sss+=this.Year;break; default: console.log("getDate Error"); }//end switch c sss+=d; switch (e){ case 'D': sss+=this.Day;break; case 'M': sss+=this.Month;break; case 'Y': sss+=this.Year;break; default: console.log("getDate Error"); }//end switch e return sss; };//end getDate // a="H"; b="/"; c="M"; d="/"; e="S" TimeObj.prototype.getTime=function(a,b,c,d,e){ var sss=""; switch (a){ case 'H': sss+=this.Hour;break; case 'M': sss+=this.Minute;break; case 'S': sss+=this.Second;break; default: console.log("getTime Error"); }//end switch a sss+=b; switch (c){ case 'H': sss+=this.Hour;break; case 'M': sss+=this.Minute;break; case 'S': sss+=this.Second;break; default: console.log("getTime Error"); }//end switch c sss+=d; switch (e){ case 'H': sss+=this.Hour;break; case 'M': sss+=this.Minute;break; case 'S': sss+=this.Second;break; default: console.log("getTime Error"); }//end switch e return sss; };//end gettTime // Stopwatch functions TimeObj.prototype.SWstart=function(){ this.updateTime(); this.t1=this.t; };//end SWstart TimeObj.prototype.SWstop=function(){ this.updateTime(); this.t2=this.t; this.et=this.t2-this.t1; return this.et; };//end SWstop //end Methods for TimeObj //Code to test TimeObj function Testit(){ var T= new TimeObj(); T.updateTime(); T.SWstart(); var aa=[['D','/','M','/','Y'],['M','/','D','/','Y'],['Y','/','M','/','D']]; console.log(T.getDate.apply(T,aa[0])); console.log(T.getDate.apply(T,aa[1])); console.log(T.getDate.apply(T,aa[2])); var bb=[['H','/','M','/','S'],['H',':','M',':','S'],['M','/','S',':','H']]; console.log(T.getTime.apply(T,bb[0])); console.log(T.getTime.apply(T,bb[1])); console.log(T.getTime.apply(T,bb[2])); console.log(T.SWstop().toFixed(2)); }//end Testit Testit();
The output of the Testit()function as seen in the left pane of the WebIDE:
>echo(0); 5/4/2016 4/5/2016 2016/4/5 6/59/36 6:59:36 59/36:6 14.58 =undefined >
One question is about the elapsed time units. The value 14.58 seems a bit long for seconds. Perhaps it is milliseconds.
-
This project uses two files on the SD card to layout CSV file format for data logging and read analog inputs in engineering units.
Mylog.cfg
Mycalibrations.cal
The following link lays out the object used to calibrate the analog inputs into engineering units. It was used to create the file “Mycalibrations.cal” on the SD card.
http://forum.espruino.com/conversations/286215/The following code was used to create the log configuration file:
SaveLog.jsvar LogObj1={ Fname:"xxx.csv", Interval:1000, //milliseconds between samples items:[ // header is text at top of column in csv file // cmd source for data in each column // can be in a different order // and have more or fewer items {header:"N",cmd:"count"}, {header:"Date",cmd:"DMY",Args:["-","-"]}, {header:"Time",cmd:"HMS",Args:[":",":"]}, {header:"A0",cmd:"AR",Args:["A0",2]}, {header:"A0",cmd:"SAR",Args:["A0",2]}, {header:"Board Temp C",cmd:"BTC",Args:[2]}, {header:"Board Temp F",cmd:"BTF",Args:[2]} ], count:0, // Number of lines logged N:10 // stop logging after count>N }; //end LogObj1 function saveObject(myobject) { var Q; var sss=JSON.stringify(myobject); if (Q===undefined) { Q = E.openFile("Mylog.cfg", "w"); Q.write(sss); Q.close(); Q = undefined; }//endif }//end doLog saveObject(LogObj1); var a=require("fs").readFileSync("Mylog.cfg"); console.log(a); console.log(' ');
The left screen when the log configuration file is created:
>echo(0); {"Fname":"xxx.csv","Interval":1000,"items":[{"header":"N","cmd":"count"},{"header":"Date","cmd":"DMY","Args":["-","-"]},{"header":"Time","cmd":"HMS","Args":[":",":"]},{"header":"A0","cmd":"AR","Args":["A0",2]},{"header":"A0","cmd":"SAR","Args":["A0",2]},{"header":"Board Temp C","cmd":"BTC","Args":[2]},{"header":"Board Temp F","cmd":"BTF","Args":[2]}],"count":0,"N":10} =undefined >
The logging program. The test portion opens the calibration file and the configuration file and creates a calibration object and two instances of the configuration. The layout is identical (could be different) except that the filename to write, and logging interval in one is 1 second and the other is 3 seconds.
LogCSV3.js
The console output when the program executes:>echo(0); N,Date,Time,A0,Inlet psig,Board Temp C,Board Temp F N,Date,Time,A0,Inlet psig,Board Temp C,Board Temp F =undefined 1,4-4-2016,21:27:21,0.79,79.96,34.43,94.32 2,4-4-2016,21:27:22,0.79,79.96,34.62,93.93 3,4-4-2016,21:27:23,0.79,80.06,34.46,93.93 1,4-4-2016,21:27:23,0.79,79.98,34.84,93.93 4,4-4-2016,21:27:24,0.79,79.96,34.40,94.26 5,4-4-2016,21:27:25,0.79,79.96,34.25,94.32 6,4-4-2016,21:27:26,0.79,80.01,34.62,93.93 2,4-4-2016,21:27:26,0.79,79.96,34.46,93.93 7,4-4-2016,21:27:27,0.79,79.98,34.62,93.59 8,4-4-2016,21:27:28,0.79,79.98,34.22,93.93 9,4-4-2016,21:27:29,0.79,79.96,34.62,94.26 3,4-4-2016,21:27:29,0.79,79.98,34.59,93.93 10,4-4-2016,21:27:30,0.79,80.01,34.62,93.98 4,4-4-2016,21:27:32,0.79,80.01,34.25,93.93 5,4-4-2016,21:27:35,0.79,80.03,34.84,93.54 6,4-4-2016,21:27:38,0.79,79.98,35.02,94.26 7,4-4-2016,21:27:41,0.79,80.01,35.24,94.26 8,4-4-2016,21:27:44,0.79,80.06,34.40,93.93 9,4-4-2016,21:27:47,0.79,79.98,34.62,94.32 10,4-4-2016,21:27:50,0.79,79.96,34.25,94.26 >
The CSV files created are: xxx.csv and xx1.csv
-
**A Object to Calibrate and Scale Analog Inputs to Engineering Units( In three installments)
Part 3:
**
Let’s add two fields to the calibration file, name and units.var AnalogReadObj1={ calibrations:[ {name:"Inlet",units:"psig",pin:'A0',slope:10.0,Intercept:5.0}, {name:"Outlet",units:"psig",pin:'A1',slope:50.0,Intercept:-25.0}, {name:"Motor",units:"Amps",pin:'A2',slope:1.0,Intercept:0.0}, {name:"Supply",units:"Volts",pin:'A3',slope:1.0,Intercept:0.0}, {name:"Flow",units:"Liters/minute",pin:'A4',slope:1.0,Intercept:0.0} ] };//end AnalogReadObj1 function saveCalibration(calibration) { var Q; var sss=JSON.stringify(calibration); if (Q===undefined) { Q = E.openFile("Mycalibrations.cal", "w"); Q.write(sss); Q.close(); Q = undefined; }//endif }//end doLog saveCalibration(AnalogReadObj1); console.log(require("fs").readFileSync("Mycalibrations.cal"));
The left screen:
>echo(0); {"calibrations":[{"name":"Inlet","units":"psig","pin":"A0","slope":10,"Intercept":5}, {"name":"Outlet","units":"psig","pin":"A1","slope":50,"Intercept":-25}, {"name":"Motor","units":"Amps","pin":"A2","slope":1,"Intercept":0}, {"name":"Supply","units":"Volts","pin":"A3","slope":1,"Intercept":0}, {"name":"Flow","units":"Liters/minute","pin":"A4","slope":1,"Intercept":0}]} =undefined >
The code that knits the two objects together and implements a two point calibration.
The calibration is save to the calibration file on the SD card followed by the reading of the calibrated analog inputs in engineering units (EU).
Button 1 on the Espruino board is used to step through the steps. Entering A= selects the channel to calibrate. During the calibration entering B= is used to set the EU values needed for calibration. If A=-1 exits calibration, and proceeds to reading the analog inputs after each press of button1.var sss=(require("fs").readFileSync("Mycalibrations.cal")); //console.log(sss); //The constructor for AnalogReadObj function AnalogReadObj(ARO){ this.A=JSON.parse(ARO); }//end AnalogReadObj //Methods for AnalogReadObj AnalogReadObj.prototype.saveCalibration=function() { var Q; var sss=JSON.stringify(this.A); console.log("Saving calibrations"); console.log(this.A); if (Q===undefined) { Q = E.openFile("Mycalibrations.cal", "w"); Q.write(sss); Q.close(); Q = undefined; }//endif };//saveCalibration AnalogReadObj.prototype.SanalogRead=function(Arg){ for(var i=0;i in this.A.calibrations;i++){ // console.log(i,Arg,this.A.calibrations[i].pin); if(Arg===this.A.calibrations[i].pin){ return( analogRead(Arg)*this.A.calibrations[i].slope+this.A.calibrations[i].Intercept); }//endif }//next i return -99; };//end SanalogRead AnalogReadObj.prototype.getName=function(Arg){ for(var i=0;i in this.A.calibrations;i++){ // console.log(i,Arg,this.A.calibrations[i].pin); if(Arg===this.A.calibrations[i].pin){ return( this.A.calibrations[i].name); }//endif }//next i return -99; };//end SanalogRead AnalogReadObj.prototype.getUnits=function(Arg){ for(var i=0;i in this.A.calibrations;i++){ // console.log(i,Arg,this.A.calibrations[i].pin); if(Arg===this.A.calibrations[i].pin){ return( this.A.calibrations[i].units); }//endif }//next i return -99; };//end SanalogRead AnalogReadObj.prototype.SetAnalog=function(Arg){ for(var i=0;i in this.A.calibrations;i++) pinMode(this.A.calibrations[i].pin, 'analog'); };//end SetAnalog //end methods for AnalogReadObj //The constructor for LinRegObj function LinRegObj(a,nn){ this.Sx=0; this.Sy=0; this.Sxy=0; this.Sxx=0; this.Syy=0; this.N=0; this.A=a; this.NN=nn; this.slope=0; this.intercept=0; }//end LinRegObj //Methods for LinRegObj LinRegObj.prototype.TakeData=function(y){ //LinRegObj.prototype.TakeData=function(x,y){ for(i=0;i<this.NN;i++){ var x=analogRead(this.A.pin); this.Sx+=x; this.Sy+=y; this.Sxy+=(x*y); this.Sxx+=(x*x); this.Syy+=(y*y); this.N++; }//next i };//endTakeData LinRegObj.prototype.Calculate=function(){ this.slope=(this.N*this.Sxy-this.Sx*this.Sy)/ (this.N*this.Sxx - this.Sx*this.Sx); this.intercept=this.Sy/this.N-(this.slope*this.Sx)/this.N; this.A.slope=this.slope; this.A.Intercept=this.intercept; };//end Calculate LinRegObj.prototype.ListChannels=function(T){ for(var i=0;i in T;i++){ console.log(i,","+T[i].name+","+T[i].units); }//next i };//end List Channels //end LinRegObj methods var A=0;//used to select channel -1 exits calibration sequence var B=0;//used to input EU values during calibration //create an instance of AnalogReadObject using the string data contained in //the variable sss var ScaledAR=new AnalogReadObj(sss); //console.log(ScaledAR.calibrations); // setup the pins for analog ScaledAR.SetAnalog(); //test example calculation var Q=new LinRegObj(ScaledAR.A.calibrations[0],5);// 5 samples to take //SelectChannel(); console.log("Press button on Espruino board to start"); var state=10; function doButton(){ if (digitalRead(BTN1) === 1){ switch (state){ case 10: Q.ListChannels(ScaledAR.A.calibrations); console.log(' '); console.log("Select channel by number"); console.log("Type A=<number>; in left pane"); console.log("Type A=-1; in the left pane to exit calibrations"); console.log("Then press button on Espruino board"); console.log(' '); state=20; break; case 20: if(A<0){ console.log("Exit"); ScaledAR.saveCalibration(); state=200; return; } if(A in ScaledAR.A.calibrations){ console.log("Calibrating ",ScaledAR.A.calibrations[A].name); console.log("Apply low value (0.0) input to sensor"); console.log("Enter the engineering units (EU) by typing in left pane"); console.log("B=<EU>;"); console.log("Then press button on the Espruino board"); }//endif A in Q state=30; break; case 30: Q.TakeData(B); console.log("Calibrating ",ScaledAR.A.calibrations[A].name); console.log("Apply high value (80% FS) input to sensor"); console.log("Enter the engineering units (EU) by typing in left pane"); console.log("B=<EU>;"); console.log("Then press button on the Espruino board"); state=40; break; case 40: Q.TakeData(B); Q.Calculate(); console.log("Calibrating ",ScaledAR.A.calibrations[A].name); console.log("Slope= ",Q.slope); console.log("Intercept= ",Q.intercept); console.log("Press button on the Espruino board"); state=10; break; case 200: console.log(' '); // Now do a scaled read of pin A0 console.log(ScaledAR.getName('A0')," A0= ",ScaledAR.SanalogRead('A0').toFixed(2)+" ",ScaledAR.getUnits('A0')); // Now do a scaled read of pin A1 console.log(ScaledAR.getName('A1')," A1= ",ScaledAR.SanalogRead('A1').toFixed(2)+" ",ScaledAR.getUnits('A1')); // Now do a scaled read of pin A2 console.log(ScaledAR.getName('A2')," A2= ",ScaledAR.SanalogRead('A2').toFixed(2)+" ",ScaledAR.getUnits('A2')); // Now do a scaled read of pin A3 console.log(ScaledAR.getName('A3')," A3= ",ScaledAR.SanalogRead('A3').toFixed(2)+" ",ScaledAR.getUnits('A3')); // Now do a scaled read of pin A4 console.log(ScaledAR.getName('A4')," A4= ",ScaledAR.SanalogRead('A4').toFixed(2)+" ",ScaledAR.getUnits('A4')); console.log("Press button on Espruino board"); return; default: }//end switch }//end BTN1 }//end doButton setWatch(doButton, BTN1, true);
The left pane view:
Press button on Espruino board to start =undefined 0 ,Inlet,psig 1 ,Outlet,psig 2 ,Motor,Amps 3 ,Supply,Volts 4 ,Flow,Liters/minute Select channel by number Type A=<number>; in left pane Type A=-1; in the left pane to exit calibrations Then press button on Espruino board A=0; Calibrating Inlet Apply low value (0.0) input to sensor Enter the engineering units (EU) by typing in left pane B=<EU>; Then press button on the Espruino board >B=0; =0 Calibrating Inlet Apply high value (80% FS) input to sensor Enter the engineering units (EU) by typing in left pane B=<EU>; Then press button on the Espruino board >B=80; =80 Calibrating Inlet Slope= 100.13897386465 Intercept= -0.03419722673 Press button on the Espruino board 0 ,Inlet,psig 1 ,Outlet,psig 2 ,Motor,Amps 3 ,Supply,Volts 4 ,Flow,Liters/minute Xxxxxxxxxxxxxxxxxxxx A=-1; exits the calibration sequence. Xxxxxxxxxx >A=-1 =-1 Exit Saving calibrations { "calibrations": [ { "name": "Inlet", "units": "psig", "pin": "A0", "slope": 100.13897386465, "Intercept": -0.03419722673 }, { "name": "Outlet", "units": "psig", "pin": "A1", "slope": 50, "Intercept": -25 }, { "name": "Motor", "units": "Amps", "pin": "A2", "slope": 1, "Intercept": 0 }, { "name": "Supply", "units": "Volts", "pin": "A3", "slope": 1, "Intercept": 0 }, { "name": "Flow", "units": "Liters/minute", "pin": "A4", "slope": 1, "Intercept": 0 } ] } Inlet A0= 79.94 psig Outlet A1= 12.00 psig Motor A2= 0.72 Amps Supply A3= 0.69 Volts Flow A4= 0.82 Liters/minute Press button on Espruino board
Testing was performed using five 1k resistors wired in series. One end connects to GND and the other end to +3.3V pins. A wire from the analog input pin can then be connected to connections along the resistor chain.
-
A Object to Calibrate and Scale Analog Inputs to Engineering Units( In three installments)
Part 2 Linear Regression
Now to add a method to perform a calibration.
This is done by collecting data and performing a linear regression.
Look at the numerical example in the following link:
https://en.wikipedia.org/wiki/Simple_linear_regression
The X values are the raw analog readings and the Y values are the values from the calibration device. We need five sums:
Sx=Sum of Xi values,
Sy= Sum of Yi values,
Sxx= Sum of the product Xi*Xi,
Sxy= Sum of the product Xi*Yi, and the
Syy =Sum of the product Yi*Yi.The slope is given by:
Slope = (N*Sxy - Sx*Sy)/( NSxx - Sx Sx),
And the Intercept is given by:
Intercept = Sy/N - (Slope * Sx) / N, where
N = the number of samples.A linear regression object is defined as follows:
//The constructor for LinRegObj function LinRegObj(a,nn){ this.Sx=0; this.Sy=0; this.Sxy=0; this.Sxx=0; this.Syy=0; this.N=0; this.A=a; this.NN=nn; this.slope=0; this.intercept=0; }//end LinRegObj //Methods for LinRegObj //LinRegObj.prototype.TakeData=function(y){ LinRegObj.prototype.TakeData=function(x,y){ for(i=0;i<this.NN;i++){ // var x=analogRead(this.A.pin); this.Sx+=x; this.Sy+=y; this.Sxy+=(x*y); this.Sxx+=(x*x); this.Syy+=(y*y); this.N++; }//next i };//endTakeData LinRegObj.prototype.Calculate=function(){ this.slope=(this.N*this.Sxy-this.Sx*this.Sy)/ (this.N*this.Sxx - this.Sx*this.Sx); this.intercept=this.Sy/this.N-(this.slope*this.Sx)/this.N; //this.A.slope=this.slope; //this.A.Intercept=this.intercept; };//end Calculate //end LinRegObj methods //test example calculation var Q=new LinRegObj(1,5); Q.TakeData(0,0); Q.TakeData(1,80); Q.Calculate(); console.log("Slope= ",Q.slope); console.log("Intercept= ",Q.intercept);
The object is commented to omit actual analogRead() and the X values are simulated to test the math.
The left WebIDE screen displays the following correct answer:>echo(0); Slope= 80 Intercept= 0 =undefined > Part 3 will follow soon.
-
Part 1.
From the linear scaling perspective two values have to be supplied to scale the reading into Engineering Units. For example an analog input returns a value from 0.0 to 1.0. if it’s connected to a pressure sensor, the 0.0 may represent -.1 psig and the 1.0 represents 59.8 psig.
A field technician would secure a process and use a calibrated pressure to apply 0.0 psig, record the a number of analog input values and then apply a known calibration pressure and record a number of analog input values. Using two calibration values is the minimum required. Three point or more is sometimes used. Usually the software sets up a step by step procedure to do this and then performs a linear regression to calculate and record the slope and intercept in the logger. It would be valuable to record these values in one location in the data logger to be used to provide readings in engineering units, and be available for the next scheduled calibration.
Typically the sensor is selected so that typical values fall below 80% of the sensor’s full range. The resolution is determined by the number of bits in the analog to digital converter. Say a pressure sensor has a full scale range of 100 psig. Then using a 12-bit ADC the resolution is 100/4096 psig. 80% of 100 psig is 80 psig and is usually used as the largest calibration pressure. This avoids placing too much pressure on the sensor and damaging it.
Once the calibration has been performed an object method is used to read the analog input expressed in engineering units.
Let’s set up an object to calibrate and scale the analog inputs into engineering units and write it to a file on the SD card as a stringified JSON. I hope Flash GURUs can supply us with a way to do this using FLASH memory.
The exact entries will vary with the uP hardware and the pins you want to use.var AnalogReadObj1={ calibrations:[ {pin:'A0',slope:10.0,Intercept:5.0}, {pin:'A1',slope:50.0,Intercept:-25.0}, {pin:'A2',slope:1.0,Intercept:0.0}, {pin:'A3',slope:1.0,Intercept:0.0}, {pin:'A4',slope:1.0,Intercept:0.0} ] };//end AnalogReadObj1 function saveCalibration(calibration) { var Q; var sss=JSON.stringify(calibration); if (Q===undefined) { Q = E.openFile("Mycalibrations.cal", "w"); Q.write(sss); Q.close(); Q = undefined; }//endif }//end doLog saveCalibration(AnalogReadObj1); console.log(require("fs").readFileSync("Mycalibrations.cal"));
The left WebIDE screen:
>echo(0); {"calibrations":[{"pin":"A0","slope":10,"Intercept":5},{"pin":"A1","slope":50,"Intercept":-25},{"pin":"A2","slope":1,"Intercept":0},{"pin":"A3","slope":1,"Intercept":0},{"pin":"A4","slope":1,"Intercept":0}]} =undefined
Now that the pattern has been saved how to use it?
First read it as a string into a variable for example sss.var sss=(require("fs").readFileSync("Mycalibrations.cal")); console.log(sss);
Apply the string to the constructor and use the calibrations
var sss=(require("fs").readFileSync("Mycalibrations.cal")); //console.log(sss); //The constructor for AnalogReadObj function AnalogReadObj(ARO){ this.A=JSON.parse(ARO); }//end AnalogReadObj //Methods for AnalogReadObj AnalogReadObj.prototype.SanalogRead=function(Arg){ for(var i=0;i in this.A.calibrations;i++){ // console.log(i,Arg,this.A.calibrations[i].pin); if(Arg===this.A.calibrations[i].pin){ return( analogRead(Arg)*this.A.calibrations[i].slope+this.A.calibrations[i].Intercept); }//endif }//next i return -99; };//end SanalogRead AnalogReadObj.prototype.SetAnalog=function(Arg){ for(var i=0;i in this.A.calibrations;i++) pinMode(this.A.calibrations[i].pin, 'analog'); };//end SetAnalog //create an instance of AnalogReadObject using the string data contained in //the variable sss var ScaledAR=new AnalogReadObj(sss); //console.log(ScaledAR.calibrations); // setup the pins for analog ScaledAR.SetAnalog(); // Now do a scaled read of pin A0 console.log("A0= ",ScaledAR.SanalogRead('A0')); // Now do a scaled read of pin A1 console.log("A1= ",ScaledAR.SanalogRead('A1')); // Now do a scaled read of pin A2 console.log("A2= ",ScaledAR.SanalogRead('A2'));
The left side of the WebIDE:
>echo(0); A0= 12.72961013199 A1= 12.70809491111 A2= 0.72095826657 =undefined >
-
-
Updated Version 2 May 2016
allObjects gave me some pointers about object constructors, making the object methods exist only once for the object and not in each instance and the use of the keyword “bind” when starting intervals.
http://forum.espruino.com/conversations/286055/
These lessons are applied to this project.
It has been pointed out that using eval() is a security problem. Having commands in the object being shipped over an open channel is also a security problem. I hope to further rework this code to address this issue.
The output:>echo(0); N,Date,Time,A0,A1,A2,A3,Board Temp C,Board Temp F, Test End =undefined 1, 2 4 2016, 14:51:40, 0.81, 31.39, 0.73, 0.71, 35.39, 95.33 2, 2 4 2016, 14:51:41, 0.80, 32.57, 0.74, 0.72, 35.37, 95.33 3, 2 4 2016, 14:51:42, 0.82, 33.57, 0.75, 0.72, 35.37, 95.33 4, 2 4 2016, 14:51:43, 0.85, 34.69, 0.75, 0.72, 35.39, 94.88 5, 2 4 2016, 14:51:44, 0.87, 35.77, 0.76, 0.73, 35.18, 95.27 6, 2 4 2016, 14:51:45, 0.87, 35.79, 0.76, 0.73, 35.18, 95.33 7, 2 4 2016, 14:51:46, 0.88, 36.50, 0.76, 0.73, 35.18, 95.27 8, 2 4 2016, 14:51:47, 0.88, 36.94, 0.76, 0.73, 35.39, 96.10 9, 2 4 2016, 14:51:48, 0.88, 37.35, 0.77, 0.73, 35.18, 95.27 10, 2 4 2016, 14:51:49, 0.89, 37.26, 0.77, 0.73, 35.58, 95.66 >
Testlog8.js is attached.
-
On an ESP8266 running v1.85
var wifi = require("Wifi"); wifi.connect(Netname, {password:Key}, function(err){ console.log("connected? err=", err, "info=", wifi.getIP()); });
On an Espruino v1.85 board connected to an AT command ESP8266
var wifi = require("ESP8266WiFi_0v25").connect(serial, function(err) { if (err) throw err; wifi.reset(function(err) { if (err) throw err; console.log("Connecting to WiFi"); wifi.connect(SSID,key, function(err) { if (err) throw err; wifi.getIP(function(l,ip){console.log("IP= ",ip,"\n\r"+l);});
-
Here are three references that use the vector method for wind speed and direction.
http://www.webmet.com/met_monitoring/622.html
note the use of the atan2() function
and,
http://www.ndbc.noaa.gov/wndav.shtml
and,
https://en.wikipedia.org/wiki/Yamartino_methodThe method of bins is used to estimate wind turbine power from a histogram of wind speeds.
http://www.nrel.gov/wind/smallwind/pdfs/power_performance_testing.pdfFinally a wind rose:
https://en.wikipedia.org/wiki/Wind_rose -
We were first using Espruino at work when I retired.
The profile photo is a homemade wind turbine. It uses a NEMA 23 stepper motor as the generator and aluminum mini-blind slats for blades. The stepper motor goes through a full wave rectifier into a solar charge controller and into a battery. So far I know that the battery Voltage was higher after charging with the turbine.
Having worked a number of years in wind energy research, it seems reasonable to come up with a way to measure and log wind input and power generated.As far as memory limit experience, I started years ago on an Altair 8800 with 1K of memory.
I spent lots of time with embedded controllers such as PIC, TI and Stellaris. Do you say Big-Endian/Little-Endian or MAC/INTEL format?Average wind direction is a unique problem. If half the time the wind is at 355 degrees and the other half the wind is at 5 degrees. The average is 180 degrees which is wrong.
Thanks again for the help.
-
Thanks for taking the time to rework this code.
Thanks for the comments. I learned a lot.
Now for the hard part applying it all.WebIDE doesn't flag the extra comma you mentioned.
I've been wondering if this forum is the right place to post code that I've been working on?
I would like to think it's useful to others and that feedback such as yours would improve the code and instruct others. All the reference documents can't replace the experience of writing code and tracking down the gotchas and occasional bugs.
Would GitHub be a better place?
Perhaps for longer projects that start small and evolve over time? -
This project creates an object prototype using the setInterval function, and then creates four instances of the object with different intervals running concurrently.
The object prototype:
function testObj(name,mcount,interval){ this.Name=name; this.Mcount=mcount; this.Count=0; this.Interval=interval; this.ID=0; //Methods this.doCount=function(){ this.Count++; if(this.Count <= this.Mcount){ console.log(this.Name," ",this.Count); }else{ clearInterval(this.ID); console.log(this.Name," Finished"); }//endif },//end doCount }//end testObj
Two functions used to test the object:
// Test the testObj methods function Test(name,mcount, interval){ // create an instance of testObj var Y=new testObj(name,mcount,interval); Y.ID=setInterval(function(){Y.doCount();},Y.Interval); console.log(Y.Name," started"); console.log("Test End"); }//end Test function Test1(){ // create an instance of testObj var Y=new testObj("Third",10,1500); Y.ID=setInterval(function(){Y.doCount();},Y.Interval); console.log(Y.Name," started"); console.log("Test End"); var Y1=new testObj("Fourth",10,2000); Y1.ID=setInterval(function(){Y1.doCount();},Y1.Interval); console.log(Y1.Name," started"); console.log("Test End"); }//end Test
The code that calls the functions:
console.log(process.memory()); Test("first",10,500); console.log(process.memory()); Test("second",10,1000); console.log(process.memory()); Test1(); console.log(process.memory()); //When Fourth finishes type this into the left side // console.log(process.memory()); //Compare the Usage at the start, as each task is added and at the end
Some of the output in the left pane of WebIDE:
>echo(0); { "free": 2144, "usage": 106, "total": 2250, "history": 101, "stackEndAddress": 536910652, "flash_start": 134217728, "flash_binary_end": 223300, "flash_code_start": 134449152, "flash_length": 262144 } first started Test End { "free": 2076, "usage": 174, "total": 2250, "history": 108, "stackEndAddress": 536910652, "flash_start": 134217728, "flash_binary_end": 223300, "flash_code_start": 134449152, "flash_length": 262144 } second started Test End { "free": 2013, "usage": 237, "total": 2250, "history": 112, "stackEndAddress": 536910652, "flash_start": 134217728, "flash_binary_end": 223300, "flash_code_start": 134449152, "flash_length": 262144 } Third started Test End Fourth started Test End { "free": 1900, "usage": 350, "total": 2250, "history": 114, "stackEndAddress": 536910652, "flash_start": 134217728, "flash_binary_end": 223300, "flash_code_start": 134449152, "flash_length": 262144 } =undefined first 1 first 2 second 1 first 3 Third 1 first 4 second 2 Fourth 1 first 5 first 6 second 3 Third 2 first 7 first 8 second 4 Fourth 2 first 9 Third 3 first 10 second 5 first Finished
-
On the Client side HTML and script is used to construct and edit the following object:
/* LogObj1 on client sent via POST JSON*/ var LogObj1={ Fname:"xxx.csv", Interval:1000, //milliseconds between samples items:[ // header is text at top of column in csv file // cmd source for data in each column // can be in a different order // and have more or fewer items {header:"N",cmd:"return(x.LL.count)"}, {header:"Date",cmd:"return(x.Day+\" \"+x.Month+\" \"+x.Year)"}, {header:"Time",cmd:"return(x.Hour+\":\"+x.Minute+\":\"+x.Second)"}, {header:"A0",cmd:"return(analogRead(A0).toFixed(2))"}, {header:"A1",cmd:"return((analogRead(A1)*x.LL.items[4].slope+x.LL.items[4].offset).toFixed(2))",slope:100,offset:-50 }, {header:"A2",cmd:"return((analogRead(A2)*100-50).toFixed(2))"}, {header:"A3",cmd:"return(SanalogRead(A3).toFixed(2))"}, {header:"Board Temp C",cmd:"return(E.getTemperature().toFixed(2))"}, {header:"Board Temp F", cmd:"return((E.getTemperature()*9/5+32).toFixed(2))" } ], count:0, // Number of lines logged N:10, // stop logging after count>N }; //end LogObj1
Notice several methods are used to scale the analog inputs using a slope/offset (intercept).
The Client script stringifies the object and sends it after the HTTP POST header.
On the Server side the HTTP header is used to determine that the client request is a post and that the Filename in the HTTP header matches “LOG”.
The stringified object is buffered using the Length in the HTTP header.
At this point I’m testing the code to process the request. It writes to console.log instead of a file.//An object constructor function LogObj(){ this.Month=1, this.Day=1, this.Year=0, this.Hour=0, this.Minute=0, this.Second=0, this.LL={}, //LL is where the Posted object fits this.channels=[], // the cmds in LL are converted to functions in channels //Methods for this object this.doHeaders=function(){ var str=""; for(var j=0;j in this.LL.items;j++)str+=this.LL.items[j].header+","; console.log(str+"\n\r"); },//end OutputHeaders this.doData=function(id){ //openfile append //setup date and time var t = new Date(); this.Month=t.getMonth(); this.Day=t.getDate(); this.Year=t.getFullYear(); this.Hour =t.getHours(); this.Minute=("0"+t.getMinutes()).substr(-2); this.Second= ("0"+t.getSeconds()).substr(-2); //use the channels to get readings var readings=[]; this.LL.count++; if(this.LL.count <= this.LL.N){ for(i=0;i in this.channels;i++)readings[i]= this.channels[i](this); console.log(readings.join(", ")+"\n\r"); }else{ clearInterval(id); }//endif //close file };//end doData }//end LogObj
The JSON.stringify() doesn’t work with embedded methods. The transmitted LogObj1 object is JSON.parsed into the LL field of the LogObj object.
The code that executes the LogObj follows:// Test the LogObj methods using data in LogObj1 function Test(){ // The client stringifies and JSON POSTs LogObj1 var W=JSON.stringify(LogObj1);//the client does this and sends it over console.log(W); // The server creates an instance of LogObj var Y=new LogObj(); //The server then embeds the JSON data in the object Y.LL=JSON.parse(W); //how the cmd strings convert to functions for(var j=0;j in Y.LL.items;j++) Y.channels.push(Function("x",Y.LL.items[j].cmd)); console.log("Y= ",Y); console.log("channels\n\r",Y.channels); var ID; //check to see if file exists //open file overwrite here Y.doHeaders(); //close file ID=setInterval(function(ID){Y.doData();},Y.LL.Interval,ID); console.log("Test End"); }//end Test //Pretend the client has POSTed LogObj1 //Use the info to create a csv file, write headers and collect data Test();
The “eval()” command is not used and each Client request creates a new LogObj.
The left side screen using WebIDE:
N,Date,Time,A0,A1,A2,A3,Board Temp C,Board Temp F, Test End =undefined 1, 28 3 2016, 20:57:13, 0.79, 25.54, 21.68, 21.34, 35.74, 95.95 2, 28 3 2016, 20:57:14, 0.76, 22.49, 20.70, 20.92, 36.11, 96.33 3, 28 3 2016, 20:57:15, 0.76, 23.24, 20.87, 20.92, 35.96, 95.95 4, 28 3 2016, 20:57:16, 0.77, 24.07, 21.05, 20.89, 35.93, 96.28 5, 28 3 2016, 20:57:17, 0.77, 23.49, 20.80, 20.85, 35.74, 95.95 6, 28 3 2016, 20:57:18, 0.77, 23.97, 21.24, 21.05, 35.96, 95.95 7, 28 3 2016, 20:57:19, 0.77, 23.54, 20.80, 20.95, 35.93, 95.95 8, 28 3 2016, 20:57:20, 0.77, 23.83, 21.24, 21.19, 35.96, 96.28 9, 28 3 2016, 20:57:21, 0.77, 23.90, 21.22, 21.05, 35.96, 95.95 10, 28 3 2016, 20:57:22, 0.77, 23.78, 21.05, 21.02, 35.98, 96.67 >
Notice that the Test function ends after the CSV headers are printed but before the data are printed.
-
The anemometers use regular magnets and a coil about the diameter of a nickel wound with #30 wire. Output about 1 Volt p-p max. I've used the LM339 inputs connected to a transformer to get 60 Hz square waves. Just keep the peak Voltage within the LM339 specs.
Hall Effect switches are nice too, but the anemometer has a coil.