I've added functionality to display a graph of the past 24 hr temp + humidity, and it now knows the time as well. It drives a nixie display (SmartNixie) that displays the time or temp/humidity (I've got the tube with % and degree C symbols on it, but I haven't had a chance to install it. I plan to replace the 6th digit with that.
One annoyance had always been the slow refresh time for the LCD. This was mostly from the whole frame buffer being run through E.reverseByte() and then sent out over I2C. However, the digole displays have a lot of functionality (almost enough that I don't need the double-buffer) implemented in hardware. So I use a few functions from the digoleHW module, modified a bit - to quickly make simple updates when navigating the fargo control screen, and adjusting the brightness of individual LEDs. It makes a huge difference in the user experience.
I still haven't done that cleanup, naturally, but I've made a few improvements. Some efforts were made to shrink code size, but nothing to change the nasty structure.
//Pins:
//keypad to A1-B12, jumper to B7
//Serial for nixies on B6
//Desk Lamp on C6-A8
//I2C2 used for the big long I2C chain
//B0 = backlight, B1 = DHT22
//A2-A5 used for
function onInit() {
rh=-1;
t=-1;
pr=0;
temh=new Uint8Array(48);
rhh=new Uint8Array(48);
prh=new Uint8Array(48);
usrmsg="";
MnuS=0;
MnuO=0;
inval="";
msg="";
lastSen=0;
setBusyIndicator(A13);
fargosturl="http://192.168.2.12/fargostatus.php";
dateurl="http://192.168.2.12/date.php";
fargourl="http://192.168.2.14/api/relay/";
fargo=new Uint8Array(8);
colors="BLUECOOLWARMYELLPINK";
gw=new Uint8Array([7,9,9,7,7]);
gwx=new Uint8Array([3,14,27,40,51]);
fargostrs="Colored,White,Wizard,Tentacle,Desk,Micro,Fan, ";
ledstate=new Float32Array([0.0,0.0,0.0,0.0,0.0]);
nixs=0;
nixr=0;
//presets=[new Float32Array([0.0,0.0,0.0,0.0,0.0]),new Float32Array([0.9,1,0.6,0,0]),new Float32Array([0,0.6,1,0.8,0.4]),new Float32Array([0.5,0.5,0.5,0.5,0.5])];
//prenames=["OFF","COLD\nWHIT","VERY\nWARM","HALF\nHALF"];
Clock = require("clock").Clock;
ledpins=[C6,C7,C8,A8,C9];
upled();
I2C2.setup({scl:B10,sda:B11});
kp=require("KeyPad").connect([C2,C3,A0,A1],[C1,C0,B7,C12,B12], function(e) {onKey("AB#*123U456D789CL0RE"[e]);});
bmp=require("BMP085").connect(I2C2,3);
Serial1.setup(115200,{tx:B6});
nixie=require("SmartNixie2").connect(Serial1,6);
e=require("DHT22").connect(A6);
require("Font8x12").add(Graphics);
rom=require("AT24").connect(I2C2,32,64);
eth=require("WIZnet").connect();
eth.setIP();
setTimeout("var s=require('http').createServer(function (req, res) {var par=url.parse(req.url,true);var q=par.query; var nam=par.pathname; nam=nam.split('/');nam=nam[1];var rd=procreq(nam,q);res.writeHead(rd.code,{'Content-Type': 'text/plain'}); res.write(rd.body);res.end();}).listen(80);",15000);
setInterval("upSensors();",15000);
setTimeout('setInterval("upHist()",60000*30);',59000);//every 1 min for testing
setTimeout("setInterval(function(){uplcd();},30000);",15000);
setTimeout("setInterval('getfargostatus();',30000);",20000);
setTimeout("getDate();setInterval('getDate();',1800000);",2000);
setInterval("delete onInit",500);
g = require("DigoleBuf").connect(I2C2,128,64);
g.clear();
g.setFont8x12();
g.drawString("LCD OK",0,0);
g.flip();
g.setContrast(0x26);
g.bktm=0;
g.HWs= function(c) {console.log(c);this.i2c.writeTo(0x27,c);};
g.HWdR = function(x1,y1,x2,y2,col) {this.HWs("SC"+(col?1:0));this.HWs("DR"+String.fromCharCode(x1,y1,x2,y2));};
g.HWfR = function(x1,y1,x2,y2,col) {this.HWs("SC"+(col?1:0));this.HWs("FR"+String.fromCharCode(x1,y1,x2,y2));};
g.backlighton = function(tim_) {
digitalWrite(B0,1);
if (this.bktm) {
try {
clearTimeout(this.bktm);
}
catch (err) {
this.bktm=0;
}
this.bktm=0;
}
this.bktm=setTimeout("digitalWrite(B0,0);g.bktm=0;",tim_?tim_:10000);
};
g.backlighton();
presets=[new Float32Array([0.0,0.0,0.0,0.0,0.0])];
prenames=["OFF"];
var i=1;
while (rom.reads(32*(i-1),1) != "\xFF") {
prenames[i]=rom.reads(32*(i-1),12);
var temp=rom.read(32*(i-1)+16,5);
presets[i]=new Float32Array([temp[0]/100.0,temp[1]/100.0,temp[2]/100.0,temp[3]/100.0,temp[4]/100.0]);
i++;
}
menu=[2,4,presets.length-1];
}
function getDate() {
var date="";
require("http").get(dateurl, function(res) {
res.on('data',function (data) {date+=data;});
res.on('close',function() {clk=new Clock(date);});
});
//delete getDate;
}
function procreq(path,query) {
var rd={};
rd.code=404;
rd.body="";
if (path=="status.json") {
rd.code=200;
var r='{"lamp":{';
for (var i=0;i<5;i++) {
r+='"'+colors.substr(i*4,4)+'":'+ledstate[i].toFixed(2);
if (i<4) {r+=",";}
}
r+='},\n"sensors":{"rh":'+rh.toFixed(1)+',"temp":'+t.toFixed(1)+',"pressure":'+pr.toFixed(2)+'}}';
rd.body=r;
} else if (path=="lamp.cmd") {
rd.code=200;
rd.body="Command applied";
ledstate[0]=query.BLUE==undefined ? 0:E.clip(query.BLUE,0,1);
ledstate[1]=query.COOL==undefined ? 0:E.clip(query.COOL,0,1);
ledstate[2]=query.WARM==undefined ? 0:E.clip(query.WARM,0,1);
ledstate[3]=query.YELL==undefined ? 0:E.clip(query.YELL,0,1);
ledstate[4]=query.PINK==undefined ? 0:E.clip(query.PINK,0,1);
setTimeout("uplcd(); upled();",100);
} else if (path=="code.run") {
if (query.code) {
rd.code=200;
rd.body=eval(query.code);
} else {
rd.code=400;
rd.body="400 Bad Request: No code supplied!";
}
} else if (path=="usrmsg.cmd") {
if (query.msg && query.msg.length > 1) {
rd.code=200;
usrmsg=query.msg;
rd.body="Message set: "+query.msg;
MnuS=10;
setTimeout("uplcd();",100);
} else {rd.code=400; rd.body="400 Bad Request: No message supplied";}
} else {rd.code=403; rd.body="403 Forbidden";}
return rd;
}
function getLedStr() {
var rtst="";
for (var it=0;it < 5; it++ ) {
rtst+=String.fromCharCode(Math.round(ledstate[it]*100));
}
return rtst;
}
function getT9() {
if (KeyN==-1) {
return "";
}
var bas=58;
var maxm=4;
if (KeyN==9 || KeyN==7) {
maxm++;
}
if (KeyN>7) {
bas+=1;
}
if (KeyN===0) {
if (KeyC==1) {
return " ";
} else {
return "0";
}
}
if (KeyC==maxm) {
return String.fromCharCode(KeyN+48);
} else {
return String.fromCharCode(KeyN*3+bas+KeyC);
}
return "";
}
function t9rst(){
KeyS="";
KeyC=0;
KeyN=-1;
}
function upSensors() {
if (lastSen==0) {
bmp.getPressure(function(b){if (pr==-1){pr=b.pressure;} else {pr=pr*0.8+b.pressure*0.2;}});
lastSen=1;
} else if (lastSen==1){
e.read(function(a){if(a.rh != -1) {if (rh==-1){rh=a.rh;t=a.temp;} else {rh=rh*0.8+a.rh*0.2;t=t*0.8+a.temp*0.2;}}});
lastSen=0;
}
}
function upHist() {
for (i=47;i>0;i--) {
temh[i]=temh[i-1];
rhh[i]=rhh[i-1];
prh[i]=prh[i-1];
}
temh[0]=E.clip(Math.round(t-10)*2,0,50);
//prh[0]=pr;
rhh[0]=E.clip(Math.round(rh/2),0,50);
}
/*
MnuS:
0 - Bars, blank space, fargo status
1 - Bars, per-color adjust, fargo status
2 - Bars, preset select, fargo status
3 - Fargo control
4 - Blank (???)
5 - Save dialog - not normally accessible.
6 -
KeyW=Text entry timeout
KeyN=Text entry number pressed
KeyC=Text entry keypress count
KeyS=Text entry string
*/
function onKey(k){
msg="";
var ulcd=1;
if (MnuS==5) { // Save dialog
if (k=="L" && KeyS.length > 0) {
KeyS=KeyS.substr(0,KeyS.length-1);
KeyC=0;
KeyN=-1;
if (KeyW!=-1) {
clearTimeout(KeyW);
KeyW=-1;
}
} else if ( k=="C" ) {
MnuS=0;
inval="";
MnuO=0;
t9rst();
if (KeyW!=-1) {
clearTimeout(KeyW);
KeyW=-1;
}
} else if ( k =="E" && KeyS != "") {
prenames[prenames.length]=KeyS;
presets[presets.length]=new Float32Array(ledstate);
var outstr=KeyS;
while (outstr.length <= 15) {
outstr+=" ";
}
outstr+=getLedStr();
while (outstr.length <= 31) {
outstr+=" ";
}
rom.writes((presets.length-2)*32,outstr);
menu[2]=presets.length-1;
msg="Saved";
MnuS=0;
MnuO=0;
t9rst();
if (KeyW!=-1) {
clearTimeout(KeyW);
KeyW=-1;
}
} else if (KeyS.length < 12) {
var a=parseInt(k);
if (a != NaN) {
if (KeyW!=-1) {
clearTimeout(KeyW);
KeyW=-1;
}
if (KeyC==0) {
KeyN=a;
KeyC=1;
} else if (KeyN==1) {
KeyS+="1";
KeyN=-1;
KeyC=0;
} else if (KeyN==a) {
if (((KeyN==9 || KeyN==7) && KeyC==5) || (KeyN==0 && KeyC==2) || (KeyN!=9 && KeyN!=7 && KeyN!=0 && KeyC==4) ) {
KeyC=1;
} else {
KeyC++;
}
} else {
KeyS+=getT9();
KeyN=a;
KeyC=1;
}
KeyW=setTimeout("KeyS+=getT9();KeyN=-1;KeyC=0;KeyW=-1;;uplcd();",2000);
}
}
} else if ( k=="B" ) { //nixie toggle
nixs=!nixs;
ulcd=3; //don't redraw LCD
} else if (k=="A") { //all off
ledstate=new Float32Array([0,0,0,0,0]);
upled();
ulcd=3;//Don't redraw LCD
} else if ( k=="C" ) {
MnuS=0;
inval="";
MnuO=0;
} else if (k=="*"){ //advance to next top level menu
if (MnuS>=3){
MnuS=0;
} else {
MnuS++;
if (MnuS==1){
nixr=MnuO;
}
}
MnuO=0;
inval="";
} else if ((MnuS==2 || MnuS == 1) && k=="#"){ //save command
if (MnuO==0 || MnuS ==1) { //new
MnuS=5;
t9rst();
} else { //modift
presets[MnuO]=new Float32Array(ledstate);
rom.writes((MnuO-1)*32+16,getLedStr());
msg="Saved";
MnuO=0;
MnuS=0;
}
} else if (k=="E" && MnuS) { //enter
if (MnuS==1 && inval) { //apply single led changes
var v=E.clip(parseInt(inval),0,100);
v=v/100;
ledstate[MnuO]=v;
inval="";
upled();
} else if (MnuS==2) { //apply preset led changes
ledstate=new Float32Array(presets[MnuO]);
MnuO=0;
MnuS=0;
upled();
} else if (MnuS==3) { //fargo
var ex=fargo[MnuO];
setFargo(MnuO,!ex);
ulcd=6+(ex?0:16);
}
} else if ((k=="L"||k=="R") && (MnuS==1||MnuS==2||MnuS==0)) { //left/right arrows
if (k=="R") {
if (MnuO>=menu[MnuS]) {
MnuO=0;
} else {
MnuO++;
}
} else {
if (MnuO==0) {
MnuO=menu[MnuS];
} else {
MnuO--;
}
}
inval="";
} else if ((k=="U"||k=="D") && MnuS==1) { //up/down arrows for individual LED adjust
ledstate[MnuO]=E.clip(100*ledstate[MnuO]+(k=="U"?4:-4),0,100)/100;
inval="";
ulcd=8;
upled();
} else if (MnuS==3) { //2-way navigation for fargo screen.
ulcd=5+(MnuO<<4); //hardware update
if (k=="D") {
MnuO=(MnuO&4)|((MnuO+1)&3);
} else if (k=="U") {
MnuO=(MnuO&4)|((MnuO-1)&3);
} else if (k=="R"||k=="L") {
MnuO=MnuO^4;
} else { ulcd=3; } //Don't update lcd
} else if (MnuS==1) {
if (inval.length < 3) {
inval=inval+k;
} else {
inval="";
}
}
uplcd(ulcd);
g.backlighton();
}
function getfargostatus() {
var fargost="";
require("http").get(fargosturl, function(res) {
res.on('data',function (data) {fargost+=data;});
res.on('close',function() {var tfs=JSON.parse(fargost); vtfs=tfs; for (var i=0;i<8;i++) { fargo[i]=tfs.relaystate[i].state;} if(MnuS==3){uplcd();}});
});
}
function setFargo(relay,state) {
var postfix = (state) ? "/on":"/off";
require("http").get(fargourl+(relay+1).toString()+postfix, function(res) {
res.on('close',function () {
if(this.code!=200) {
fargo[relay]=state;
uplcd();
}
});
});
}
function slcd() {
if (MnuS == 3) {
var fst=fargostrs.split(',');
g.setFontBitmap();
for (var j=0;j<8;j++) {
if (fargo[j]==0) {
g.drawRect(4+(j>>2)*64,17+(j&3)*12,11+(j>>2)*64,22+(j&3)*12);
} else {
g.fillRect(4+(j>>2)*64,17+(j&3)*12,11+(j>>2)*64,22+(j&3)*12);
}
g.drawString(fst[j],14+(j>>2)*64,18+(j&3)*12);
}
g.drawRect(2+(MnuO>>2)*64,15+(MnuO&3)*12,61+(MnuO>>2)*64,24+(MnuO&3)*12);
g.setFont8x12();
} else if (MnuS < 6) {
usrmsg="";
var vals=new Float32Array(ledstate);
if (MnuS==2){
vals=new Float32Array(presets[MnuO]);
} else if (MnuS==1 && inval) {
vals[MnuO]=(parseInt(inval)/100);
}
var x=3;
for (var i=0;i<5;i++) {
var nx=x+gw[i];
g.drawRect(x,15,nx,47);
g.fillRect(x,(47-ledstate[i]*32),nx,47);
if (vals[i] != ledstate[i]) {
if (vals[i] > ledstate[i]) {
g.fillRect(x+1,(47-vals[i]*32),nx-1,48-vals[i]*32);
} else {
g.setColor(0);
g.fillRect(x+1,(47-vals[i]*32),nx-1,46-vals[i]*32);
g.setColor(1);
}
}
x=nx+4;
}
g.drawString("B",4,50);
g.drawString("C",16,50);
g.drawString("W",29,50);
g.drawString("Y",41,50);
g.drawString("P",55,50);
for (var j=0;j<8;j++) {
if (fargo[j]==0) {
g.drawRect(122,15+j*6,126,19+j*6);
} else {
g.fillRect(122,15+j*6,126,19+j*6);
}
}
} else if (MnuS == 10) {
if (usrmsg==""){
MnuS=0;
MnuO=0;
} else {
g.drawString(usrmsg,1,17);
g.backlighton(30000);
}
}
if (MnuS===0) {
if (MnuO==0){
if(msg) {
g.drawString(msg,66,17);
} else {
g.drawString(getDstr(),66,17);
}
} else { //draw a graph
for (var i=47;i>=0;i--) {
g.setPixel(120-i,62-(MnuO==1?temh[i]:rhh[i]),1);
}
}
}else if (MnuS==1) {
g.drawString(colors.substr(MnuO*4,4),70,17);
g.drawString((100*ledstate[MnuO]).toFixed(),80,32);
if (inval) {
g.drawString(inval,80,46);
}
} else if (MnuS==2 || MnuS==5) {
var tstr=(MnuS==2 ? prenames[MnuO] : KeyS);
if (MnuS==5 && tstr.length < 12 ) {
if (getT9() == "" ) {
tstr+="_";
} else {
tstr+=getT9();
}
}
if (tstr.length > 8 ) {
g.drawString(tstr,70,17);
} else {
g.drawString(tstr.substring(0,7),70,17);
g.drawString(tstr.substring(8,tstr.length),70,32);
}
}
g.flip();
}
function uplcd(isky) {
g.clear();
var tpr=0.000295333727*pr;
g.drawString(t.toFixed(1)+" C "+rh.toFixed(1)+"% "+tpr.toFixed(1)+" "+getTStr(":"),0,0);
if ((isky&14)==2) { //do nothing
//console.log("donothing");
} else if ((isky&14)==4) {
//console.log("hwrender")
var oldO=isky>>4;
g.HWdR(2+(oldO>>2)*64,15+(oldO&3)*12,61+(oldO>>2)*64,24+(oldO&3)*12,0);
g.HWdR(2+(MnuO>>2)*64,15+(MnuO&3)*12,61+(MnuO>>2)*64,24+(MnuO&3)*12,1);
} else if ((isky&14)==6) {
//console.log("hwrender")
g.HWfR(5+(MnuO>>2)*64,18+(MnuO&3)*12,10+(MnuO>>2)*64,21+(MnuO&3)*12,isky>>4);
if (!(isky>>4)) {g.HWs("SC1");}
} else if ((isky&14)==8) {
g.HWfR(gwx[MnuO]+1,16,gwx[MnuO]+gw[MnuO]-1,46,0);
g.HWfR(gwx[MnuO],(47-ledstate[MnuO]*32),gwx[MnuO]+gw[MnuO],47,1);
} else {
slcd(); //full LCD update
}
nixie.setAllLED(0,0,0);
if (nixs){
var tp=(MnuS===0?MnuO:nixr);
if (tp===0) {
nixie.setString(getTStr(" ")+" ");
nixie.setLED(2,128,32,0);
if((isky&1)==0){nixr++;}
} else if (tp==1) {
nixie.setString(" "+t.toFixed(1)+" ");nixie.setLED(3,96,0,0);nixie.setLED(4,96,0,0);nixie.setLED(2,96,0,0);if((isky&1)==0){nixr++;}
} else if (tp==2) {
nixie.setString(" "+rh.toFixed(1)+" ");nixie.setLED(3,16,24,64);nixie.setLED(4,16,24,64);nixie.setLED(2,16,24,64);if((isky&1)==0){nixr=0;};
}
} else {
nixie.setString(" ");
}
nixie.send();
}
function getTStr(sep){
var hrs=clk.getDate().getHours();
var mins=clk.getDate().getMinutes();
return (hrs>9?hrs:" "+hrs)+sep+(mins>9?mins:"0"+mins);
}
function getDstr(){
var mon=clk.getDate().getMonth()+1;
var day=clk.getDate().getDate();
var year=clk.getDate().getFullYear();
return (mon+"/"+day+"/"+year);
}
function upled() {
for (var i=0;i<5;i++) {
analogWrite(ledpins[i],ledstate[i]);
}
}
Espruino is a JavaScript interpreter for low-power Microcontrollers. This site is both a support community for Espruino and a place to share what you are working on.
I've been doing some work on this.
I've added functionality to display a graph of the past 24 hr temp + humidity, and it now knows the time as well. It drives a nixie display (SmartNixie) that displays the time or temp/humidity (I've got the tube with % and degree C symbols on it, but I haven't had a chance to install it. I plan to replace the 6th digit with that.
One annoyance had always been the slow refresh time for the LCD. This was mostly from the whole frame buffer being run through E.reverseByte() and then sent out over I2C. However, the digole displays have a lot of functionality (almost enough that I don't need the double-buffer) implemented in hardware. So I use a few functions from the digoleHW module, modified a bit - to quickly make simple updates when navigating the fargo control screen, and adjusting the brightness of individual LEDs. It makes a huge difference in the user experience.
I still haven't done that cleanup, naturally, but I've made a few improvements. Some efforts were made to shrink code size, but nothing to change the nasty structure.
Edit: Updated code to use % and deg C symbols.