So, we have a home automation system, HAL, in our home (crazy place for it, huh?). Based on OpenHAB 2, it provides a number of user interfaces on various platforms. Smart phones, web browsers, tablets, etc. However, sometimes you need a good, old-fashioned physical interface. Do you really want to whip out your phone, open an app, navigate to the right screen and tap a widget just to turn on the garage lights so you can find a Phillips screwdriver? Sometimes, but not always!
In an effort to fortify our standing as uber-geeks, my sons and I set out to build a Star Trek the Next Generation (STNG) style control panel for our garage (not so old-fashioned, but a physical interface nonetheless). Our control panel is based on a Raspberry Pi 3, the official Pi touchscreen, and some custom web pages. Of course, the whole thing integrates with HAL (for more details on HAL, see this project write-up).
That's right, we're slamming together 2 classic science-fiction icons, 2001 A Space Odyssey meets Star Trek (yes, we went there!)
Grab your favorite obelisk, pull up a seat in Captain Jean-Luc Picard's command chair, and enjoy the ride!
NOTE: This project works hand-in-hand with Home Automation Laboratory (HAL) Part 4 - Custom Interfaces
OverviewThis project will cover the user interface for our custom sprinkler system controller. Our home has an in-ground sprinkler system for watering the yard. It is divided into 7 zones, each of which can be independently turned on and off.
Additional details on the sprinkler system controller (TBD). Basically, our user interface needs the ability to manually turn on a zone, leave it on for 5 minutes, then automatically turn it off. Also, it needs the ability turn off a zone manually. Finally, we need to be edit the schedule that determines when each zone will be turned on and off automatically.
Hardware
The hardware aspect of this project is quite simple. We just used a Raspberry Pi running the latest version of Raspbian. All we really need the Pi to do is run it's native web browser, pointed at some custom web pages on our Apache web server, in full-screen mode. Add an official Raspberry Pi 7" touchscreen, mount the whole kit-and-caboodle in Smarticase's mount so it all looks neat, tidy and professional, and we're good to go.
Software
Apache web server. We installed and configured an Apache web server on a Raspberry Pi. The details of that project are documented here.
The true meat-and-potatoes of this project lie in the custom web pages we built. In order to get the look and feel of the STNG computer operating system - Library Computer Access/Retrieval System (LCARS), we leaned heavily on Aricwithana's github repository for his LCARS SDK. Aric provides 2 ways of building LCARS web pages - javascript and CSS. We opted for the pure CSS methodology.
We've designed things such that HAL is the hub/system of record for everything. Therefore, the sprinkler controller will communicate with HAL. It'll receive commands from HAL and report status information back to HAL. Likewise, our LCARS panel will only communicate with HAL. This keeps things pretty simple for the custom web pages that comprise the LCARS screen.
So a bit of HTML, some CSS, an Apache web server coupled with OpenHAB's REST API and we have everything we need.
Hardware
Raspberry Pi - setting up the Pi is easy and well-documented. We didn't do anything fancy or extraordinary here. The Pi exists only to run a web browser with a wifi or hardwired network connection to an on-premise Apache server. The standard Raspbian install works perfectly.
We found that the instructions on the official Raspberry Pi web site were more than adequate. Hit the "instructions" link for step-by-step details. Here's the high-level run-down:
- Using another computer (Mac, PC, Unix/Linux), copy the OS installer "Noobs" onto a micro SD card.
- Insert the SD card into the Pi.
- Connect the Pi to a keyboard, mouse and monitor.
- Optionally connect a cat5 cable to your network (with internet access).
- Connect the Pi to power.
- The Pi will boot up and eventually display a dialog box containing various operating systems. Choose Raspbian. If you didn't hard-wire the Pi to the internet, configure wifi access, and let 'er rip!
- The install process will run for 15-20 minutes or so
- Once complete, the Pi should boot into its GUI OS.
- We enabled VNC and SSH so that we can access the Pi without it being connected to keyboard, mouse and monitor (aka "headless").
- At this point, we configure locale information, rename the device, and assign a static IP
Pi Touchscreen & Smarticase Mount
Connecting the touchscreen to the Pi is even easier. We chose the "official" Raspberry Pi touchscreen to keep hardware issues as simple as possible.
When I first discovered Smarticase's Pi mount, I knew I'd use it in a project (or many projects!) some day. Today is that day.
The Pi, the touchscreen, and the Smarticase chassis are all intertwined. However, assembly is fairly straightforward.
We found it easiest to connect the Y power cable that came with the Smarticase at this point. This will allow you to power both the Pi and the touchscreen from a single micro-USB power source.
Now it's time to attach the Smarticase base. The kit comes with 2 bases. We've chosen the larger of the 2. It has VESA mounting capability and is handy for bench work. We may chose the smaller, 2-piece base when we actually install the panel on the garage wall.
Time to fire this sucker up!
Software
Web Page
Ok, finally, the coup de gras! The magic behind the curtain. The bits and pieces that pull it all together!
But first, a disclaimer (the suspense is killing you, isn't it?). We're by no means expert web developers. We knew, and learned, just enough to hack our way through Aric's CSS files and example pages to put together what you see here. Odds are, there are better ways of accomplishing the same goal. Similarly, we are not attempting to provide a tutorial on HTML, CSS, or javascript programming.
NOTE: We will include snippets of code in the descriptions of our changes. The complete files are included in the attachments section below.
Ok, on with the show!
Aric provides a few sample pages with his SDK. We chose the "hardcoded" example as our leaping-off point.
This is basically an HTML page and some CSS files. From here, we hacked a bunch of stuff out of the index.html file to simplify the page.
- We eliminated the left 2 columns
- We shrunk the header area by eliminating the buttons under the title "LCARS HARDCODE - API DISABLED"
- We added a "footer" - the horizontal bar across the bottom of the main field.
At this point, it was time to add our specific content
Version Label - in the upper-left corner, "LCARS 7102". We tweaked the positioning of the text as well as the text itself. The version number, "7102", is just 2017 backwards :). We also added a hyperlink to the garage's main LCARS page. These required modifying the index.html file:
<a class="button bg-green-3" data-label="LCARS - 7102" href="../garage"></a>
Title - we modified the title text. We also tweaked the module.css file to adjust the character spacing and make it left-justified:
.header .title{
width: auto;
# margin-left:15px;
overflow: hidden;
text-overflow: clip;
white-space: normal;
# font-size:25px;
# letter-spacing: -1px;
}
The index.html changes:
<div class="title">Pod Bay - Irrigation Sub-System</div>
Sub-Title - this was a completely new addition. We added a new class to module.css:
.header .subtitle{
padding-top:10px;
padding-left:5px;
font-size:32px;
color:yellow;
}
The sub-title includes the "star date". This is just the current date and time reversed (yeah, that old trick again). We use a javascript web worker to update the star date every second.
Here's the the star date display from index.html:
<div class="subtitle">Star Date: <span id="StarDate"></span></div>
Here's the portion of index.html that instantiates the web worker and updates the StarDate value:
var sd = new Worker("../scripts/StarDate.js");
sd.onmessage = function(event)
{
document.getElementById("StarDate").textContent = event.data;
};
And the definition of the web worker itself, which is in a separate javascipt file "StartDate.js":
function StarDate() {
var today = new Date();
var rawYear = today.getFullYear().toString();
var year = rawYear.substr(3,1) + rawYear.substr(2,1) + rawYear.substr(1,1) + rawYear.substr(0,1);
var day = today.getDate().toString();
if (day.length < 2)
{
day = '0' + day;
}
var month = (today.getMonth()+1).toString();
if (month.length < 2)
{
month = '0' + month;
}
var StarDate = year + day + '.' + month;
var hour = today.getHours();
var ampm = 'am'
if (hour > 12)
{
hour = (hour - 12).toString();
ampm = 'pm'
}
else
{
hour = hour.toString();
}
var minutes = today.getMinutes().toString();
if (minutes.length < 2)
{
minutes = '0' + minutes;
}
var seconds = today.getSeconds().toString();
if (seconds.length < 2)
{
seconds = '0' + seconds;
}
StarDate = StarDate + ' ' + hour + ':' + minutes + ':' + seconds + ' ' + ampm;
postMessage(StarDate);
setTimeout("StarDate()",1000);
}
StarDate();
This function grabs the current date & time, formats it, then posts the formatted string back to index.html. Waits for 1,000 milliseconds, then does it all over again.
Left Side Bar - we customized this area to label the 7 zones of our sprinkler system. The SDK provides for these boxes to be buttons, however, we use them as simple labels. Here's the index.html snippet:
<div class="wrapper column flexv">
<div class="elbow top-left bg-green-4 step-two default horizontal">
<div class="bar">
<div class="block"></div>
</div>
</div>
<div class="block bg-blue-4 step-two" data-label="Zone 1"></div>
<div class="block bg-green-4 step-two" data-label="Zone 2"></div>
<div class="block bg-green-1 step-two" data-label="Zone 3"></div>
<div class="block bg-green-2 step-two" data-label="Zone 4"></div>
<div class="block bg-green-1 step-two" data-label="Zone 5"></div>
<div class="block bg-green-4 step-two" data-label="Zone 6"></div>
<div class="block bg-blue-4 step-two" data-label="Zone 7"></div>
<div class="elbow bottom-left bg-green-4 flexcv default horizontal">
<div class="bar">
<div class="block"></div>
</div>
</div>
</div>
Footer - we added a bar across the bottom of the page just to tidy things up a bit. We included a little call-sign to mark our territory as well. From index.html:
<div class="row frame flexh">
<div class="bar bg-blue-2"></div>
<div class="bar bg-blue-1"></div>
<div class="bar bg-blue-3"></div>
<div class="bar bg-green-1 flexch"></div>
<div class="bar bg-blue-3" data-label="A Hobbs Squad Joint"></div>
<div class="bar bg-blue-2"></div>
<div class="bar bg-blue-3"></div>
</div>
Now, all the window dressing was complete. It was time to populate the main area of the screen with our sprinkler system information and controls. For each of the 7 sprinkler zones, we wanted the following:
- Display the zone's current status: On or Off
- If the zone is currently on, display the time remaining until it turns off
- A button to manually turn a zone on. The sprinkler controller will leave the zone on for 5 minutes and then automatically turn it off
- A button to manually turn a zone off.
- A button to edit the automated schedule (still in progress)
Status
Since it is possible for our sprinkler system status to change independent of the LCARS panel (automated schedule, other UI's like the OpenHAB smart phone app, etc.), we need the web page to periodically poll HAL for current status. We chose to use web workers for this.
Here's the placeholder for zone 1's status from index.html:
<div class="column ZoneStatus" id="zone_1_status"></div>
Here's the index.html code that instantiates the web worker and updates the placeholder with current status for zone 1:
<!-- Zone 1 -->
var ws1 = new Worker("workers/poll_zone_1_status.js");
ws1.onmessage = function(event)
{
if (event.data == 'ON')
{
document.getElementById("zone_1_status").style.color = 'green';
}
else
{
document.getElementById("zone_1_status").style.color = 'red';
}
document.getElementById("zone_1_status").textContent = event.data;
};
In addition to updating the status text, you can see that we change the text color based on the status (On = green, Off = red).
And here's the web worker definition from poll_zone_1_status.js:
function poll_zone_1_status() {
var theUrl = "http://192.168.1.169:8080/rest/items/sprinkler_z1_switch/state";
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
postMessage(xmlHttp.responseText);
setTimeout("poll_zone_1_status()",1000);
}
poll_zone_1_status();
This javascript function formulates a call to the OpenHAB API to request current status of zone 1. It then posts the result back to index.html, waits 1,000 milliseconds, then does it all over again.
Rinse and repeat 6 more times!
Time Remaining
The strategy for displaying time remaining is the same. There's a placeholder and web worker initiation in the index.html file and a corresponding javascript file that calls the OpenHAB API once every second. If a zone is currently on, then HAL will report the number of seconds remaining until it is turned off. The web worker formats that as a "min:sec" string. This has the net effect of displaying a count-down timer. If a zone is currently off, HAL responds with a hyphen "-" character. See the files in the attachments for specifics.
On/Off Buttons
Since we're essentially repeating the same thing 14 times (7 on buttons, 7 off buttons), we pulled the button functionality into a javascript function. Each of the buttons calls this function, passing in parameters that indicate the zone to act upon and the action to take (on vs. off).
On and Off buttons for zone 1 from index.html:
<div class="column On"><a class="button left bg-blue-3" id="button_z1_on" data-altlabel="On" href="javascript:ZoneSwitch('1','ON')"></a></div>
<div class="column Off"><a class="button right bg-green-3" id="button_z1_off" data-altlabel="Off" style="text-align:right;" href="javascript:ZoneSwitch('1','OFF')"></a></div>
The function then uses the input values to formulate a call to the OpenHAB REST API whereupon HAL takes over and executes the requested action:
function ZoneSwitch(zone, command)
{
var req = new XMLHttpRequest();
req.open("POST", "http://192.168.1.169:8080/rest/items/sprinkler_z" + zone + "_switch", true);
req.setRequestHeader("Content-Type", "text/plain");
req.setRequestHeader("Accept", "application/json");
req.send(command);
}
Layout
The rest is merely using HTML constructs to arrange everything into neat and tidy rows and columns. We did create additional classes in module.css to assist with the layout of status labels, time remaining label and buttons. Those are all detailed in the attached file.
FinaleAll of the relevant LCARS SDK files, including our customized index.html and module.css file were placed in the appropriate location on our Apache server (see Home Automation Laboratory (HAL) Part 4 - Custom Interfaces). All of the javascript files that define web workers are also placed on the Apache server.
All that remains is to fire up the web browser on the Pi, point it to our Apache server's IP, and the folder containing our index.html file (e.g. "http://192.168.1.145/sprinklers
"). Then put the browser into full-screen mode. Et voila! You have a Star Trek style touch panel!
The sprinkler controller that this UI is designed to control is in the late prototype phase. However, it is advanced enough to fully test this LCARS panel. And it works brilliantly!
The combination of the OpenHAB API and Aric's LCARS SDK makes for a slick and easy to implement user interface. The Pi + touchsceen + Smarticase makes for a slick, professional-looking , easy to implement physical installation. We like this set-up so much, that we have several more panels planned.
Comments