Adjustable Color-temperature Desk Lamp

Posted on
  • Purpose:
    To act as a desk and project lamp, where the color temperature can be adjusted ranging from a very cool white, to very warm white, and the brightness can be varied. It will be controlled by a free-standing Espruino, and capable of loading “preset” colors, as well as adjusting the color channels individually. Additionally, the controller will display current temperature and humidity.

    I decided to use 1 and 3W luxeon clones (commonly called LED beads) for the lighting, in 5 channels. All LEDs used are phosphor based, even the yellow.

    • Water/Ice Blue 2x 1W
    • Cool White 1W + 3W
    • Warm White 1W + 3W
    • Yellow 2x 1W
    • Pink 2x 1W

    Hardware:

    The hardware can be broken down into 4 parts:

    The Arm: The first problem was to obtain an articulating arm that can clamp to the table and hold the light head in the desired location. Luckily, I had vintage-1960’s flourescent desk lamp, which I gutted, and removed the head from, and then ran 6-conductor wire down it. Tricky wire running job, as there are also springs and straps inside the arm. The base of the arm has sufficient space for the MOSFETs and and resistors, and a terminal strip at the back (removable) for easy maintenance.

    The Head: The LEDs must be mounted on a heatsink to avoid burning out. The heatsink used here was about 4”x3.5”, obtained from one of the audio amplifiers from an old projection screen TV (these are readily available for free on classifieds/freecycle/craigslist, at least in the US - worth scrapping for the huge fresnel lens, trapazoidal (often front surface) mirror, speakers (sell on ebay), and some heatsinks).

    I soldered the positive side of all the LEDs together, and thermal-epoxied them to the heatsink. This way sucked, and I recommend epoxying all the LEDs down in the right pattern first, and then connecting to them. And being more careful so the frame isn't at +5 volts. For running the 1W and 3W white LEDs off the same voltage, a 0.68 ohm resistor was necessary in series with the 1W LEDs.

    The heatsink is screwed onto the backing plate (which is in turn affixed to the arm using the same mount that was used for the original lighting head). To allow the head to be removed for maintenance, the LEDs are connected to a pair of 3 wire terminal strips on the head backing plate. Finally, the shroud is affixed using 4 screws (not installed in above picture).

    Design and planning regarding the diffuser mounting system is ongoing; presently, I am using a somewhat rudimentary mounting system as shown in the main picture.

    LED drivers: The ballast resistors and MOSFET driver board are located in the base of the arm. The ballast resistor values are low (1.3, 2.8 and 3 ohms), and have to be picked fairly close to the targeted value to get full brightness from all channels without exceeding the manufacturers’ specifications*.

    The board itself was prepared from single-sided copper-clad board from Amazon, using the Toner Transfer method. Design and layout was done in Eagle. The board in there now was created using HP Advanced Photo Paper; it sucks, don’t use it. I got better results with the front cover of Bloomberg BusinessWeek, though (I didn’t use those because I was expecting to have more trouble than I did drilling the holes; this was a practice run. - but this one turned out okay, so I figured I might as well use it). I have dextrin-coated paper in the mail for next time I make boards.

    The MOSFETs are DMN2075-U's, rated for 4.2A continuous at 2.5v on the gate.

    Controller: Espruino, with a PCD8544 screen (on SPI2, shared with micro SD card), 4x4 matrix keypad, and a DHT22 temperature and humidity sensor.

    I plan to add a BMP180 pressure sensor, and switch the keypad out for a 4x5 unit, which will enable a richer UI.


    Pink only on!

    Colors don't come out so well in photos. I'm not entirely happy with the warm whites, though - they don't seem as warm as I'd like them to be. Investigations are ongoing.

    *Or what I believe to be their specifications - datasheets for the Luxeon clones seem non-existent. There are dozens of distinct and different LEDs, many of which are not copies of actual Luxeon LEDs (colors are available which Lumileds never sold), but rather similar 1W LEDs in the same package. If data sheets exist, the clueless vendors don't seem to know about them. Note also that the ebay vendors don’t know the specs either, and, if pressed, they are won’t to provide inaccurate specs.

  • Software:

    var rh=-1;
    var t=-1;
    var statusview=1;
    var menustate=0;
    var menuopt=0;
    var inval="";
    var colors=["BLUE","COOL","WARM","YELL","PINK"];
    var ledstate=[0.4,1,0.8,0,0.2];
    var presets=[[0.9,1,0.6,0,0],[0,0.6,1,0.8,0.4],[0.5,0.5,0.5,0.5,0.5]];
    var prenames=["COLD\nWHIT","VERY\nWARM","HALF\nHALF"];
    var menu=[0,4,presets.length+1];
    var ledpins=[C6,C9,C8,A2,C7];
    var gw=new Uint8Array([5,7,7,5,5]);
    var kp=require("KeyPad").connect([C12,C15,C0,C1],[C2,C3,A0,A1], function(e) {onKey(e);});
    require("Font8x12").add(Graphics);
    
    function onKey(e){  //sdfsdfsdf
    	var k="123A456B789C*0#D"[e];
    	if (e==12){
    		if (menustate==3){
    			menustate=0;
    		} else {
    			menustate++;
    		}
    		menuopt=0;
    		inval="";
    		uplcd();
    	} else if (e==14 && menustate) {
    		if (menustate==1 && inval) {
    			var v=E.clip(parseInt(inval),0,100);
    			v=v/100;
    			ledstate[menuopt]=v;
    			inval="";
    			uplcd();
    			upled();
    		} else if (menustate==2 && menuopt) {
    			ledstate=presets[menuopt-1];
    			menuopt=0;
    			menustate=0;
    			uplcd();
    			upled();
    		}
    	} else if (e==3 && menustate) {
    		if (menuopt>=menu[menustate]) {
    			menuopt=0;
    		} else {
    			menuopt++;
    		}
    		inval="";
    		uplcd();
    	} else if ((e&0x03)==3) {
    		console.log(k);
    	} else if (menustate==1) {
    		if (inval.length < 3) {
    			inval=inval+k;
    		} else {
    			inval="";
    		}
    		uplcd();
    	}
    
    	g.backlighton();
    }
    
    digitalWrite(A13,0);
    var g=require("PCD8544").connect(SPI2,C4,C5,B1, function() {
            g.clear();
            g.setFont8x12();
            g.drawString("LCD OK",0,0);
            g.flip();
            g.setContrast(0.45);
            g.bktim=0;
            setTimeout("g.backlighton();",1000);
    });
    
    g.backlighton = function() {
    	digitalWrite(B0,1);
    	if (this.bktim) {
    		clearTimeout(this.bktim);
    		this.bktim=0;
    	}
    	this.bktim=setTimeout("digitalWrite(B0,0);g.bktim=0;",5000);
    };
    
    
    var e=require("DHT22").connect(A6);
    
    setInterval("e.read(function(a){rh=a.rh;t=a.temp;});",30000);
    
    setTimeout("setInterval(function(){uplcd();},30000)",15000);
    
    function uplcd() {
    	g.clear();
    	g.setFont8x12();
    	g.drawString(t.toFixed(1)+" C "+rh.toFixed(1)+"%",0,0);
    	if (menustate < 3) {
    		var vals=ledstate.slice(0);
    		if (menustate==2 && menuopt){
    			vals=presets[menuopt-1];
    		} else if (menustate==1 && inval) {
    			vals[menuopt]=(parseInt(inval)/100);
    		}
    		var x=3;
    		for (var i=0;i<5;i++) {
    			var nx=x+gw[i];
    			g.drawRect(x,14,nx,39);
    			g.fillRect(x,(39-ledstate[i]*25),nx,39);
    			if (vals[i] != ledstate[i] ) {
    				if (vals[i] > ledstate[i]) {
    					g.fillRect(x+1,(39-vals[i]*25),nx-1,40-vals[i]*25);
    				} else {
    					g.setColor(0);
    					g.fillRect(x+1,(39-vals[i]*25),nx-1,38-vals[i]*25);
    					g.setColor(1);
    				}
    			}
    			x=nx+4;
    		}
    		g.setColor(1);
    		g.setFontBitmap();
    		g.drawString("B",4,41);
    		g.drawString("C",14,41);
    		g.drawString("W",25,41);
    		g.drawString("Y",35,41);
    		g.drawString("P",44,41);
    	}
    	if (menustate==1) {
    		g.setFont8x12();
    		g.drawString(colors[menuopt],52,13);
    		g.drawString((100*ledstate[menuopt]).toFixed(),60,24);
    		if (inval) {
    			g.drawString(inval,60,35);
    		}
    	} else if (menustate==2) {
    		g.setFont8x12();
    		if (menuopt) {
    			g.drawString(prenames[menuopt-1],52,13);
    		} else {
    			g.drawString("LOAD",52,13);
    		}
    	}
    	g.flip();
    }
    
    function upled() {
    	for (var i=0;i<5;i++) {
    		analogWrite(ledpins[i],ledstate[i]);
    	}
    }
    
    
  • Hello DrAzzy,

    Very nice project!

    Sacha

  • Yeah that's cool.. and warm and yellow and so on!

  • That's awesome! Thanks for posting it up!

    If you get time it'd be really neat to see a video of it working...

  • New board, process came out better, and with a better connector and screw terminals...

  • I see some ancient carbon comp resistors in this build. I approve! :-)

  • I like the little graphs! That's really neat :)

  • New hardware:
    128x64 digole display, with the reflective backing removed and replaced with EL panel. The backlight draws about 250 mils, so it needs to be switched using a mosfet (in this case, a dangling mosfet on a standalone breakout board with tape wrapped around it ).

    Previously, the 5.6v operating voltage was supplied by an external PSU, and the 3.3v was supplied with a buck converter, which was excessive considering the low nominal load.

    Now, the 5.6v operating voltage is stepped down from 12v from the 12v 10A supply that powers low voltage stuff in my room, inside the control box, using a 5A buck converter (from ebay - one of the ones that says to use heatsinks if you go over 2.5A. I had to heatsink both the chip and the underside of the board below it to keep the temperatures in my comfort zone. But hell, it was $2.50!), and I replaced the 3.3v converter with an LDO regulator.

    Electronics now contained (mostly) within a blue bin turned upside down (with wire holes in it, ofc).

    Finally, I added an 8 KB I2C eeprom module, which I use to save presets to.

    New software:

    
    function onInit() {
    	rh=-1;
    	t=-1;
    	pr=0;
    	usrmsg="";
    	statusview=1;
    	menustate=0;
    	menuopt=0;
    	inval="";
    	msg="";
        setBusyIndicator(A13);
    	fargosturl="http://192.168.2.163/fargostatus.php";
    	fargourl="http://192.168.2.14/api/relay/";
    	fargo=new Uint8Array(8);
    	colors="BLUECOOLWARMYELLPINK";
    	gw=new Uint8Array([7,9,9,7,7]);
    	fargostrs="Colored,White,Wizard,Tentacle,Desk,Micro,Fan, ";
    	ledstate=new Float32Array([0.0,0.0,0.0,0.0,0.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"];
    	
    	ledpins=[C6,C7,C8,A8,C9];
    	upled();
    	I2C2.setup({scl:B10,sda:B11});
    	kp=require("KeyPad").connect([C2,C3,A0,A1],[C1,C0,C15,C12,B12], function(e) {onKey("AB#*123U456D789CL0RE"[e]);});
    	bmp=require("BMP085").connect(I2C2,3);
    	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("bmp.getPressure(function(b){pr=b.pressure;});e.read(function(a){if(a.rh != -1) {rh=a.rh;t=a.temp;}});",30000);
    	setTimeout("setInterval(function(){uplcd();},30000);",15000);
    	setTimeout("setInterval('getfargostatus();',30000);",20000);
    	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.bktim=0;
        g.backlighton = function(tim_) {
    		digitalWrite(B0,1);
    	   	var tim=(tim_) ? tim_ :5000;
    		if (this.bktim) {
    			try {
    				clearTimeout(this.bktim);
    			}
    			catch (err) {
    				this.bktim=0;
    			}
    			this.bktim=0;
    		}
    		this.bktim=setTimeout("digitalWrite(B0,0);g.bktim=0;",tim);
    	};
    	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=[0,4,presets.length-1];
    }
    
    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";
    		if (query.BLUE != undefined) {
    			ledstate[0]=E.clip(query.BLUE,0,1);
    		} else {ledstate[0]=0;}
    		if (query.COOL != undefined) {
    			ledstate[1]=E.clip(query.COOL,0,1);
    		} else {ledstate[1]=0;}
    		if (query.WARM != undefined) {
    			ledstate[2]=E.clip(query.WARM,0,1);
    		} else {ledstate[2]=0;}
    		if (query.YELL != undefined) {
    			ledstate[3]=E.clip(query.YELL,0,1);
    		} else {ledstate[3]=0;}
    		if (query.PINK != undefined) {
    			ledstate[4]=E.clip(query.PINK,0,1);
    		} else {ledstate[4]=0;}
    		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;
    			menustate=10;
    			setTimeout("uplcd();",100);
    		} else {rd.code=400; rd.body="400 Bad Request: No message supplied";}
    	} else if (path=="" || path=="index.html") {rd.code=403; rd.body="403 Forbidden";}
    	return rd;
    }
    
    function getLedStr() {
    	var rtnstr="";
    	for (var it=0;it < 5; it++ ) {
    		rtnstr+=String.fromCharCode(Math.round(ledstate[it]*100));
    	}
    	return rtnstr;
    }
    
    function getT9() {
    	if (t9num==-1) {
    		return "";
    	} 
      var bas=58;
      var maxmen=4;
      if (t9num==9 || t9num==7) {
        maxmen++;
      }
      if (t9num>7) {
        bas+=1;
      }
      if (t9num===0) {
        if (t9cnt==1) {
          return " ";
        } else {
          return "0";
        }
      }
      if (t9cnt==maxmen) {
        return String.fromCharCode(t9num+48);
      } else {
        return String.fromCharCode(t9num*3+bas+t9cnt);
      }
      return "";
    }
    
    function onKey(k){
    	msg="";
    	if (menustate==5) {
    		if (k=="L" && t9str.length > 0) {
    			t9str=t9str.substr(0,t9str.length-1);
    			t9cnt=0;
    			t9num=-1;
    			if (t9watch!=-1) {
    	    		clearTimeout(t9watch);
    	    		t9watch=-1;
    	    	}
    		} else if ( k=="C" ) {
    			menustate=0;
    			inval="";
    			menuopt=0;
    			t9str="";
    			t9cnt=0;
    			t9num=-1;
    			if (t9watch!=-1) {
    	    		clearTimeout(t9watch);
    	    		t9watch=-1;
    	    	}
    		} else if ( k =="E" && t9str != "") {
    			prenames[prenames.length]=t9str;
    			presets[presets.length]=new Float32Array(ledstate);
    			var outstr=t9str;
    			while (outstr.length <= 15) {
    				outstr+=" ";
    			}
    			outstr+=getLedStr();
    			while (outstr.length <= 31) {
    				outstr+=" ";
    			}
    			rom.writes((presets.length-2)*32,outstr);
    			menu=[0,4,presets.length-1];
    			msg="Saved";
    			menustate=0;
    			menuopt=0;
    			t9str="";
    			t9cnt=0;
    			t9num=-1;
    			if (t9watch!=-1) {
    	    		clearTimeout(t9watch);
    	    		t9watch=-1;
    	    	}
    		} else  if (t9str.length < 12) {
    			var a=parseInt(k);
     			if (a != NaN) {
        			if (t9watch!=-1) {
    		    		clearTimeout(t9watch);
    		    		t9watch=-1;
    		    	}
    		    	if (t9cnt==0) {
    		    		t9num=a;
    		    		t9cnt=1;
    		    	} else if (t9num==1) {
    		    		t9str+="1";
    		    		t9num=-1;
    		    	t9cnt=0;
    		    	} else if (t9num==a) {
    		    		if (((t9num==9 || t9num==7) && t9cnt==5) || (t9num==0 && t9cnt==2) || (t9num!=9 && t9num!=7 && t9num!=0 && t9cnt==4) ) {
    		        	t9cnt=1;
    		    	} else {
    		        	t9cnt++;
    		      	}
    		    } else {
    		    	t9str+=getT9();
    		    	t9num=a;
    		    	t9cnt=1;
    		    }
    		    t9watch=setTimeout("t9str+=getT9();t9num=-1;t9cnt=0;t9watch=-1;;uplcd();",2000);
    		  }
    		}
    	} else if (k=="*"){
    		if (menustate>=4){
    			menustate=0;
    		} else {
    			menustate++;
    		}
    		menuopt=0;
    		inval="";
    	} else if ((menustate==2 || menustate == 1) && k=="#"){
    		if (menuopt==0 || menustate ==1) { 
    			menustate=5;
    			t9cnt=0;
    			t9num=-1;
    			t9str="";
    		} else {
    			presets[menuopt]=new Float32Array(ledstate);
    			rom.writes((menuopt-1)*32+16,getLedStr());
    			msg="Saved";
    			menuopt=0;
    			menustate=0;
    		}
    
    	} else if (k=="E" && menustate) {
    		if (menustate==1 && inval) {
    			var v=E.clip(parseInt(inval),0,100);
    			v=v/100;
    			ledstate[menuopt]=v;
    			inval="";
    			upled();
    		} else if (menustate==2) {
    			ledstate=new Float32Array(presets[menuopt]);
    			menuopt=0;
    			menustate=0;
    			upled();
    		} else if (menustate==3) {
    			var ex=fargo[menuopt];
    			setFargo(menuopt,!ex);
    		}
    	} else if ((k=="L"||k=="R") && (menustate==1||menustate==2)) {
    		if (k=="R") {
    			if (menuopt>=menu[menustate]) {
    				menuopt=0;
    			} else {
    				menuopt++;
    			}
    		} else {
    			if (menuopt==0) {
    				menuopt=menu[menustate];
    			} else {
    				menuopt--;
    			}
    		}
    		inval="";
    	} else if ((k=="U"||k=="D") && menustate==1) {
    		if (k=="U") {
    			ledstate[menuopt]=E.clip(100*ledstate[menuopt]+4,0,100)/100;
    		} else {
    			ledstate[menuopt]=E.clip(100*ledstate[menuopt]-4,0,100)/100;
    		}
    		inval="";
    		upled();
    	} else if (menustate==3) {
    		if (k=="D") {
    			menuopt=(menuopt&4)|((menuopt+1)&3);
    		} else if (k=="U") {
    			menuopt=(menuopt&4)|((menuopt-1)&3);
    		} else if (k=="R"||k=="L") {
    			menuopt=menuopt^4;
    		}
    	} else if (k=="A") {
    		ledstate=new Float32Array([0,0,0,0,0]);
    		upled();
    		uplcd();
    	} else if ( k=="C" ) {
    		menustate=0;
    		inval="";
    		menuopt=0;
    	} else if (menustate==1) {
    		if (inval.length < 3) {
    			inval=inval+k;
    		} else {
    			inval="";
    		}
    	}
    	uplcd();
    	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(menustate==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 textWrap(str,len) {
    	var l=str.length;
    	if (l < len) { return str; }
    	else {
    		var o="";
            for (var i=0; i < l; i+=len) 
            {
    			if (i+len > l) {
    				o+=str.substring(i,l);
    			} else { 
    				o+=str.substr(i,len)+"\n";
    			}
    		}
    		return o;
    	}
    }
    
    
    function uplcd() {
    	g.clear();
        var tpr=0.000295333727*pr;
    	g.drawString(t.toFixed(1)+" C "+rh.toFixed(1)+"% "+tpr.toFixed(1),0,0);
    	if (menustate < 3 || menustate==5) {
    		usrmsg="";
    		var vals=new Float32Array(ledstate);
    		if (menustate==2){
    			vals=new Float32Array(presets[menuopt]);
    		} else if (menustate==1 && inval) {
    			vals[menuopt]=(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",57,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 (menustate == 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+(menuopt>>2)*64,15+(menuopt&3)*12,61+(menuopt>>2)*64,24+(menuopt&3)*12);
    		g.setFont8x12();
    	} else if (menustate == 10) {
    		if (usrmsg==""){
    			menustate=0;
    			menuopt=0;
    		} else {
    			g.drawString(usrmsg,1,17);
    			g.backlighton(30000);
    		}
    	}
    	if (menustate===0 && msg) {
    		
    		g.drawString(msg,70,17);
    	}else if (menustate==1) {
    		g.drawString(colors.substr(menuopt*4,4),70,17);
    		g.drawString((100*ledstate[menuopt]).toFixed(),80,32);
    		if (inval) {
    			g.drawString(inval,80,46);
    		}
    	} else if (menustate==2 || menustate==5) {
    		var tstr=(menustate==2 ? prenames[menuopt] : t9str);
    		if (menustate==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 upled() {
    	for (var i=0;i<5;i++) {
    		analogWrite(ledpins[i],ledstate[i]);
    	}
    }
    
    
    
  • Nice... So not only ethernet controlled, but looking at it, you can turn something else on and off via the network connection?

  • Yeah, I also have a Fargo controlling most general lighting in my room. This thing gets the current status of it, shows it on the display, and lets you change them on the third menu screen

    Oh, and to do text entry for making preset lighting settings, I implemented t9

  • Do you actually mean T9 (the predictive one?) that would be pretty cool :)

    There's a phone-style text entry thing for the morse code texter too. It's fun to see the different way of implementing it... It never occurred to me not to use a lookup table - I'm really not sure which way ends up more efficient :)

    That kind of thing would make quite a good module though. I was also wondering about some kind of menu library (a scrollable list with simple up/down to select) that might save people a bit of work... It looks like yours does a bunch of other stuff though.

  • Er, no, sorry, not predictive text. It's been so long since I've used phones like that that I forgot that T9 was the step in between the old 1/2/3 presses on each key.

    I'm talking about just doing the 1/2/3 presses of a key to pick the number, not full on T9. It does seem like the sort of thing that's ripe for some sort of library, and I was thinking about that, but I'm not sure how to really package it up, since I think in most cases, people would need to get into that bit of code to make it work with the rest of their project.

    Frankly, I've also been thinking about how to rewrite my menu to make it more extensible. It's got those two massive functions onkey and uplcd, and I actually can't get them to send to the Espruino successfully without minification now!

    I need to do something to tame those functions - but I'm not really sure how to approach that.

  • One way to tidy up uplcd and onKey might be to have a separate function for each menustate? You could either then keep the 'if' statements, or could probably even look the functions up in an array...

  • 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.

    
     
    //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]);
    	}
    }
    
    

    Edit: Updated code to use % and deg C symbols.

  • Hi @DrAzzy, I just found this project, very nice!
    Some of your links are gone though; I was wishing to see your updates :)

  • Images fixed - no updated pictures of the lamp itself though.

  • Thanks @DrAzzy! I hope to do something like this when I get some time :)

    Oh wait! Just one more broken link...search for "LDO regulator".

  • Weird color (pink + blue on), time on nixies.

    Warm White, temp on nixies

    Cool White, humidity on nixies

    (No, I don't know why the last one shows up on it's side! My desktop applications all show it in the correct orientation, but Chrome gets it wrong)

  • Thanks! I never considered storing electronics in one of those bins! Very interesting idea and they come in many sizes too...

  • The plastic bin made into electronics case? It works pretty well; that size just happens to be perfect for both the LCD and keypad I'm using. It's not a perfect solution, though - you still need a cover if it's not going to be sitting on a table all the time. Putting it flat on the table is kind of like putting a hat on medusa, as well (it doesn't help that it's a godawful mess inside. I need to make things more graceful.

    Also - I spoke to a friend who was a meteorology major, who confirmed my fears. The BMP180 is giving hopelessly bogus numbers.

    (just noticed this had been sitting here typed but unposted for over a week....)

  • I'm going to remake the drivers for the LEDs to use constant current drivers, AMC7140 linear adjustable current regulators. ~$1 each, but they've got builtin OE pin, current handling up to 700ma.
    So, I'm gonna crank the voltage on the LEDs up so I'm running them in series instead of parallel, and have:
    2x3W Cool/Warm White, 700ma (at ~4.1v forward voltage)
    2x3W Very cold white (replacing the current blue) at 350-700ma, depending on power constraints and how it looks.
    3x1W Yellow, 350ma. These will either be phosphor yellow, or a mix of phosphor yellow and 585nm yellow.
    3x1W Red, 350ma. Will likely use different shades of red to get better color rendition. The pink LEDs look like utter shit, and I'm not making that mistake again, and I can't find any phosphor based leds that give reds (not pinks polluted with a horrifying amount of royal blue, such that uv-reactive stuff around fluoresces)

    I'm also making a base plate for the Espruino to sit on that will include regulators with caps on them (like always should have been). The espruino will fit on some pin strip that connects to all pins, and routes those to connectors with power and ground, to simplify wiring (right now it's a total rats nest in there, and very difficult to work on). I've been having problems with the LCD freezing, and i think it happens because of a voltage dip/spike confusing it, as it's running off a 3.3v regulator with no cap on the output or input, along with the Wiznet and an EL inverter.

    I'm hopeful that the better power distribution system will solve the problems I've had with the BMP180 - I've got my fingers crossed that the problem is a poor connection, since the connector felt loose when I was last poking at it, and the BMP180 works on the bench, just not installed.

    I had the first version of it together and soldered up on sunday, but it failed the smoke test - If I ever find the person at microchip who decided to use two different pinouts on their positive regulators, I'll wire him up backwards and pass a current through him.

    Edit:
    New LED loadout will be:
    Vled=9.4v, leaving ~9v to drive the LEDs after the dropout on the AMC7140.

    3x 1W (350mA @ 3.0) Ice Blue (couldn't get 3W ones)
    2x 3W (700mA @ 3.8) Cold White, nice bright LEDs.
    2x 3W (700mA @ 3.4 - chinese watts) - I've been trying to source better ones without luck.
    3x1W (350mA @ 2.9) yellow LEDs with phosphor (these seem very hard to get now, unfortunately)
    1x5W 630nm (1A@4.5-5.5V) red + 1x 3W 660nm (350mA @ 4V - chinese watts again) red dual chip, run at 350mA in series. The 5W one is pretty excessive, but I'm hoping I'll be able to get 350mA through it with the voltage I have available. It will be tight.

    This will give me about the same total power consumption at full brightness that I have now, but with almost 50% more of that power going to the LEDs - 22.3W

    New driver:

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Adjustable Color-temperature Desk Lamp

Posted by Avatar for DrAzzy @DrAzzy

Actions