Programming a web interface for UberFridge with jQuery UI

Posted on Jan 2, 2012 in JavaScript, PHP, Programming, UberFridge | 2 comments

jQuery UI is a CSS and JavaScript framework to quickly create a nice looking user interface. I used the Redmond theme to create the web interface for UberFridge.

If you are reading this, chances are you are a web developer. You probably just want to see the source code, so here you go.

I will only highlight some parts of the user interface, because the source code should be quite self-explanatory. I have discussed how I create the Google annotated timeline charts here and how I communicate with python here, so this article will be about the user interface only.

My PHP code contains almost no styling. All styling is done with CSS and JavaScript. I am NOT a web developer, so don’t judge me when some of the things here are weird. I just hacked together what I needed.

Tabbed content

I make a lot of use of tabbed content in my interface. The control panel and the maintenance panel are tabbed. The tabbing is done with the Tabs module of jQuery UI.

Normally the tabbed content should be only ul and li statements, but I also place some buttons in the ul. It is a bit dirty, but it is an easy way to use the nicely styled header the tabs module provides.

To load the control panel on the right tab, I receive the mode of operation from python and load the corresponding tab. I also set the temperature displayed on that tab to the actual value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function loadControlPanel(){
	$.post('socketmessage.php', {messageType: "getmode", message: ""}, function(mode){
		switch(parseInt(mode)){
			case 1:
				$.post('socketmessage.php', {messageType: "gettemp", message: ""}, function(temp){
					beerTemp=parseInt(temp);
					$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
				});
				$('#control-panel').tabs( "select" , 0);
				statusMessage("normal","Running in beer profile mode");
				break;
			case 2:
				$.post('socketmessage.php', {messageType: "gettemp", message: ""}, function(temp){
					beerTemp=parseInt(temp)
					$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
				});
				$('#control-panel').tabs( "select" , 1);
				statusMessage("normal","Running in beer constant mode");
				break;
			case 3:
				$.post('socketmessage.php', {messageType: "gettemp", message: ""}, function(temp){
					fridgeTemp=parseInt(temp)
					$("#fridge-temp span.temperature").text(String((fridgeTemp/10).toFixed(1)));
				});
				$('#control-panel').tabs( "select" , 2);
				statusMessage("normal","Running in fridge constant mode");
				break;
			default:
				statusMessage("error","Invalid mode ("+mode+") received");
		}
	});
}

When the ‘Apply’ button is clicked to save new settings, I check the CSS classes of the tabs to see which tab is open.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function applySettings(){
	//Check which tab is open
	if($("#profile_control").hasClass('ui-tabs-hide') == false){
		$.post('socketmessage.php', {messageType: "setprofile", message: ""}, function(){ 	});
		statusMessage("highlight","Mode set to beer profile");
	}
	else if($("#beer_constant_control").hasClass('ui-tabs-hide') == false){
		$.post('socketmessage.php', {messageType: "setbeer", message: String(beerTemp)}, function(){ 	});
		statusMessage("highlight","Mode set to beer constant");
	}
	else if($("#fridge_constant_control").hasClass('ui-tabs-hide') == false){
		$.post('socketmessage.php', {messageType: "setfridge", message: String(fridgeTemp)}, function(){ 	});
		statusMessage("highlight","Mode set to fridge constant");
	}
	setTimeout(loadControlPanel,5000);
}

Live OLED display

The OLED display in the user interface is a live version of the real OLED display on the fridge. It is updated every 10 seconds, by the following function:

1
2
3
4
5
6
7
8
9
10
11
function refreshLcd(){
	$.post('socketmessage.php', {messageType: "lcd", message: ""}, function(lcdText){
		if(lcdText != false){
			$('#lcd').html(lcdText);
		}
		else{
			$('#lcd').html("Error: script <BR>not responding");
		}
		window.setTimeout(checkScriptStatus,5000);
	});
}

The function calls the update function of the status button, so they alternate in updating and cannot get in sync, which would place additional load on the web server.

The display is styled with pure CSS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@font-face {
    font-family: '5x8LCDHD44780UA02Regular';
    src: url('../font/5x8_lcd_hd44780u_a02-webfont.eot');
    src: url('../font/5x8_lcd_hd44780u_a02-webfont.eot?#iefix') format('embedded-opentype'),
         url('../font/5x8_lcd_hd44780u_a02-webfont.woff') format('woff'),
         url('../font/5x8_lcd_hd44780u_a02-webfont.ttf') format('truetype'),
         url('../font/5x8_lcd_hd44780u_a02-webfont.svg#5x8LCDHD44780UA02Regular') format('svg');
    font-weight: normal;
    font-style: normal;
}
 
.lcddisplay {
	width: 240px;
	height: 80px;
	padding: 5px 16px;
	background: #000000; /* Old browsers */
	background: -moz-linear-gradient(top, #000000 2%, #2b2b2b 11%, #212121 54%, #212121 92%, #000000 100%); /* FF3.6+ */
	background: -webkit-gradient(linear, left top, left bottom, color-stop(2%,#000000), color-stop(11%,#2b2b2b), color-stop(54%,#212121), color-stop(92%,#212121), color-stop(100%,#000000)); /* Chrome,Safari4+ */
	background: -webkit-linear-gradient(top, #000000 2%,#2b2b2b 11%,#212121 54%,#212121 92%,#000000 100%); /* Chrome10+,Safari5.1+ */
	background: -o-linear-gradient(top, #000000 2%,#2b2b2b 11%,#212121 54%,#212121 92%,#000000 100%); /* Opera11.10+ */
	background: -ms-linear-gradient(top, #000000 2%,#2b2b2b 11%,#212121 54%,#212121 92%,#000000 100%); /* IE10+ */
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
	background: linear-gradient(top, #000000 2%,#2b2b2b 11%,#212121 54%,#212121 92%,#000000 100%); /* W3C */
	-webkit-box-shadow: inset 1px 1px 5px #333333;
	-moz-box-shadow: inset 1px 1px 5px #333333;
	box-shadow: inset 1px 1px 5px #333333;	color: #000;
	font-size: 16px;
	line-height: 1.2em;
	font-weight: normal;
    font-style: normal;
    font-size:nofrmal:
 
	border: 2px solid #333;
	font-family: '5x8LCDHD44780UA02Regular';
	color:#FFFF00;
	white-space:pre;
	-webkit-border-radius: 2px;
	-moz-border-radius: 2px;
	border-radius: 2px;
}

Status Button

The status button changes styling and function depending on the status of the script. It can be used to stop the script when it’s running and to start the script when it’s not. On mouse-over the function appears, otherwise the status is displayed. This prevents cluttering the interface with buttons.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function checkScriptStatus(){
	$.post('socketmessage.php', {messageType: "checkScript", message: ""}, function(answer){
		if(answer !=prevScriptStatus){
			if(answer==1){
				$(".script-status span.ui-icon").removeClass("ui-icon-alert").addClass("ui-icon-check");
				$(".script-status").removeClass("ui-state-error").addClass("ui-state-default");
				$(".script-status span.ui-button-text").text("Script running");
				$(".script-status").unbind();
				$(".script-status").bind({
						click: function(){
										 stopScript();
										},
						mouseenter: function(){
									$(".script-status p span#icon").removeClass("ui-icon-check").addClass("ui-icon-stop");
									$(".script-status").removeClass("ui-state-default").addClass("ui-state-error");
									$(".script-status span.ui-button-text").text("Stop script");
									},
						mouseleave: function(){
									$(".script-status p span#icon").removeClass("ui-icon-stop").addClass("ui-icon-check");
									$(".script-status").removeClass("ui-state-error").addClass("ui-state-default");
									$(".script-status span.ui-button-text").text("Script running");
								}
						});
			}
			else{
				$(".script-status span.ui-icon").removeClass("ui-icon-check").addClass("ui-icon-alert");
				$(".script-status").removeClass("ui-state-default").addClass("ui-state-error");
				$(".script-status span.ui-button-text").text("Script not running!");
				$(".script-status").unbind();
				$(".script-status").bind({
						click: function(){
										 startScript();
										},
						mouseenter: function(){
									$(".script-status span.ui-icon").removeClass("ui-icon-alert").addClass("ui-icon-play");
									$(".script-status").removeClass("ui-state-error").addClass("ui-state-default");
									$(".script-status span.ui-button-text").text("Start script");
									},
						mouseleave: function(){
									$(".script-status span.ui-icon").removeClass("ui-icon-play").addClass("ui-icon-alert");
									$(".script-status").removeClass("ui-state-default").addClass("ui-state-error");
									$(".script-status span.ui-button-text").text("Script not running!");
								}
						});
			}
		}
		prevScriptStatus = answer;
		window.setTimeout(refreshLcd, 5000); //alternate refreshing script and lcd
	});
 
}

Maintenance panel popup

The maintenance panel is displayed in a jQuery UI Dialog. The flash objects of the annotated timeline graphs come right though the dialog, so I have to hide them when the dialog is displayed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$('#maintenance-panel').dialog({
	autoOpen: false,
	title: 'Maintenance Panel',
	height: 700,
	width: 1000,
	open: function(){
	    // hide beer chart, because it displays through the panel in chrome
	    $('#beer-chart').css('visibility', 'hidden');
	    // show profile chart
	    $('#profileChartDiv').css('visibility', 'hidden');
	    },
	 close: function(){
	    // show beer-chart
	    $('#beer-chart').css('visibility', 'visible');
	    // show profile chart
	    $('#profileChartDiv').css('visibility', 'visible');
	    }
});

Temperature Setting buttons

To set a constant beer or fridge temperature, I have a large temperature display and up and down buttons. I wanted to have click and hold buttons to be able to quickly set the temperature. Below is the JavaScript that I used to achieve that. beerTemp is a global variable that is updated to the actual setting when the page is loaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
 
$("button#beer-temp-up").button({ 	icons: {primary: "ui-icon-triangle-1-n"}	}).bind({
	mousedown: function() {
		beerTemp=(beerTemp+1)%300; //keep bewteen 0 and 30.0 degrees
		$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
		beerTempUpTimeOut = window.setInterval(function(){
			beerTemp=(beerTemp+1)%300; //keep bewteen 0 and 30.0 degrees
			$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
		}, 100);
	},
	mouseup: function(){
		if(typeof(beerTempUpTimeOut)!='undefined')
			clearInterval(beerTempUpTimeOut);
	},
	mouseleave: function(){
		if(typeof(beerTempUpTimeOut)!='undefined')
			clearInterval(beerTempUpTimeOut);
	}
	});
 
$("button#beer-temp-down").button({ 	icons: {primary: "ui-icon-triangle-1-s"}	}).bind({
	mousedown: function() {
		beerTemp=(beerTemp+299)%300; //keep bewteen 0 and 30.0 degrees
		$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
		beerTempDownTimeOut = window.setInterval(function(){
			beerTemp=(beerTemp+299)%300; //keep bewteen 0 and 30.0 degrees
			$("#beer-temp span.temperature").text(String((beerTemp/10).toFixed(1)));
		}, 100);
	},
	mouseup: function(){
		if(typeof(beerTempDownTimeOut)!='undefined')
			clearInterval(beerTempDownTimeOut);
 
	},
	mouseleave: function(){
		if(typeof(beerTempDownTimeOut)!='undefined')
			clearInterval(beerTempDownTimeOut);
	}
	});

2 Comments

  1. Where do you put these files on the router?

    • The python scripts are in /mnt/uberfridge/
      The www files are in /opt/share/www/lighttpd
      The shell script can be anywhere, I have it in my home directory.

Leave a Reply

Your email address will not be published. Required fields are marked *