Projects Research & Design Hub

Serving Up HTML (Part 2)

Written by Jeff Bachiochi

Moving On Up to HTML5

Last month Jeff introduced HTML and described a project for serving up information to a web browser using an Arduino Mega 2560 and an inexpensive Wi-Fi chip. This month he covers HTML5 and takes the project to the next level.

While your favorite browser is a custom application that brings the world to you, it differs from all other applications in that it is not responsible for how the data is presented. Instead, all browsers take direction from the HyperText Markup Language (HTML) that encompasses the data and formats it in a predetermined way. Since this allows all devices to render the information in the same way, there is no requirement for a user of your data to have a specialized application to view it. This eliminates a need to write separate applications for each project and operating system on which your data will be viewed. There are plenty of tools to help you write, edit, render, HTML code. Last month, I introduced some background on how HTML got started and is used to format information. The project demonstrated how you might serve up some information to a web browser using an Arduino with Espressif ESP8266 interfaced. My Arduino of choice is a Mega 2560, which supports multiple hardware serial ports and lots of extra I/O through its extended form factor. The discussion included some gotchas about the present forms of the ESP8266 and lack of consistent documentation on the AT command set, which isn’t included with most modules.

Lately, there has been a rise in alternative languages for these modules, but I will stick with the AT set, as this is a simple interface and does not require reflashing the device. Since an HTML page can be a small text file that is served by an Arduino application through the ESP8266 Wi-Fi module, no additional hardware is required. When you think “server,” there is usually a storage device involved to hold the HTML files, which might include image, audio, or video files. If you keep them simple, webpages don’t necessarily require file handling capability.

Using HTML can really save money in keeping the hardware and software to an absolute minimum. And now thanks to HTML5, this point can be driven home more effectively. But wait, I’m getting ahead of myself here. Let’s start off by finishing up where we left off last month.


With the ESP8266 accepted onto my home network with its own IP address, it is primed for someone to visit. It is set to serve the present outside temperature here at Crystal Lake to anyone interested. The entire HTML file is 164 characters. It is split into three strings such that the first and last string contain static information, while the second string contains the actual temperature (see Listing 1). What started as static text, 78, will be substituted with a sting variable containing the actual measured temperature. We’ve finished void init() and are presently in void loop() waiting for a request. The ESP8266 always sends some reply when issued a command (i.e., OK). When it receives data to its IP address over the network, it passes this on with the prefix +IPD:

+IPD,0,313:GET / HTTP/1.1
Listing 1
Here a single HTML line is divided into three parts to segregate the static value of the temperature. This will allow us to easily replace the static value with a dynamic one.

String1=“<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>Temperature</title></head><body><h1>”
String3=“ degrees F</h1><h3>Outside Temperature</h3></body></html>”

As you might guess, this stands for IP Data. Additional information follows this prefix up to the : character. The 0 following this prefix identifies the connection or user, if more requests come while still servicing this one they are will have an incremental ID. Just before the colon is a number indicating how many characters the request consists of. All the text following the colon (313 bytes here) contains the actual GET request. The ESP8266 takes care of where to send the reply. We need to provide only the reply.

A reply consists of two parts the setup and the reply. The setup must contain the ID of the user we are replying to and the number of characters we will provide. The first string is 105 bytes long so we use the command AT+CIPSEND=0,105. When the ESP8266 is ready, it will reply with >. Then we can send the 105 characters. The ESP8266 will reply with SEND OK. The second and third strings are sent the same way. Since the first and third strings are static, their lengths will remain fixed. The second string holds the dynamic value and will therefore change depending on the string size (number of digits and possible – sign). If you want to try this with an Arduino and don’t have a “Weather Shield” (with an on-oard temperature sensor), I wrote this application to use a random temperature between –20° and 90°. On the receiving end, the browser renders the temperature as text as in Photo 1. OK, now let’s take 5. Or, better yet, let’s use 5.

Photo 1 The rendering of text to a web page requires little code to produce. This HTML file is less than 200 characters.
Photo 1
The rendering of text to a web page requires little code to produce. This HTML file is less than 200 characters.

The latest HTML incantation, HTML5, was released in late 2014, so it’s relatively new, but most browsers now support it. HTML5 adds many new syntactic features, including <video>, <audio>, and <canvas> elements. Many features of HTML5 have been designed with low-powered devices (e.g., smartphones and tablets) taken into consideration. For the remainder of this project, I will focus on the new element <canvas>.

Canvas consists of a drawable region defined in HTML code with height and width attributes. JavaScript code may access the area through a full set of drawing functions similar to those of other common 2D APIs, allowing for dynamically generated graphics. I’ll be using some simple functions to help build a library of pictorial representations of data that are a bit more exciting than plain text.

The canvas element defines a rectangular area on an HTML page. By default, a canvas has no border and no content. Like the other elements I discussed last month, opening and closing tags are used to define the element (i.e., <canvas></canvas>). Note there is actually nothing between the two tags; however, there are three attributes that must accompany a canvas element. These attributes are contained in the opening tag. The id attribute defines a name by which it will be referenced.

The width and height attributes define the boundaries or size of the canvas.

<canvas id="myCanvas" width="200" 

Here we’ve defined the myCanvas area to be 200 pixels wide by 100 pixels high. Refer to Table 1 to see other global attributes that are available like <style>, which defines CCS3 rendering. CSS is a stylesheet language that describes the presentation of an HTML document. It describes how elements must be rendered using specific fonts, borders, colors, and other properties. Multiple canvases can be defined on a webpage. Unless specifically placed they will be stacked horizontally and vertically just as if they were text.

Table 1  The Global attributes for many HTML elements has been expanded with HTML5. Many allow a user greater interaction with the elements.
Table 1
The Global attributes for many HTML elements has been expanded with HTML5. Many allow a user greater interaction with the elements.

You can use the canvas to draw colorful text, graphics, interact with user input, and even animations. Let’s start with a simple text guage displaying some value before we get into the graphic elements. That will give your feet some solid ground to stand on.


To place items on the blank canvas we’ve established, use the start and end script tags to surround those instructions. Remember the ID used to reference the canvas? The canvas is identified by assigning it to a variable using the getElementByID() method, like so:

var canvas1 = document.getElement

Next, the canvas is assigned a variable using the getContext(“2d”) object to identify a pallet of objects that can be used on the canvas:

var notice1 = canvas1.getContext

Now we can use any of the 2d properties and methods for decorating the canvas. Creating variables like notice1 enables us to use a shorthand version of the canvas. Each property like fillStyle, coloring the canvas with paint, is defined using a label or value. The value (size, font, color, alignment, text, and placement) defines the data to be displayed. The label (size, font, color, alignment, text, and placement) helps to put the value in perspective (see Listing 2).

Listing 2
The properties of the canvas defined as notice1 can be used to change the "look" of what’s being placed on the canvas.

notice1.font="60px Arial";
notice1.fillText("99.75", canvas.width/2, 45);
notice1.font="30px Arial";
notice1.fillText("KPH", canvas.width/2, 75);

When rendered, this canvas and associated script will display as in Photo 2. Note here that we really want to be able to assign a value, not just display “99.75” every time. Any properties that we want to make dynamic, like text value (or text color), should be assigned with a variable. All of these variables can be placed together rather than spread throughout the HTML file, which will make them easier to handle later when the page is served. For instance, the text value might be defined as:

var notice1text = “99.75”; 
		canvas.width/2, 45);
Photo 2 This final rendering demonstrates how multiple gauges can be arranged by using a table to place each gauge into a specific cell of the table's matrix.
Photo 2
This final rendering demonstrates how multiple gauges can be arranged by using a table to place each gauge into a specific cell of the table’s matrix.

Assigning variables has other benefits. Most of the script properties could be combined into a function that might be called by multiple canvases, reducing the text required to display a number of similar canvases. Just reassign the variable before using the function (see Listing 3).

Listing 3
Reassigning variables allows a function to be reused by providing it with alternate input.
var notice1text = “99.75”;
var notice1label = “KPH”;
var notice2text = “61.98”;
var notice2label = “MPH”;

var noticetext = notice1text; // use notice1text
var noticelabel = notice1lable; // use notice1label 
notice(“notice1”); // function notice uses canvas notice1

var noticetext = notice2text; // use notice2text
var noticelabel = notice2label; // use notice2label
notice(“notice2”); // function notice uses canvas notice2

The second kind of display we’ll look at is the vertical or horizontal gauge. A simple horizontal gauge may be used to indicate some amount as it relates to other values. Here max and min data will be presented along a horizontal bar labeled with graduations and a pointer to the present amount. So we’ll start with a broader canvas, 325 × 80 pixels. The bar will extend from pixel 30–290 for a width of 260 pixels, with 11 labeled tick marks. The step size (distance between ticks) equals 260/(11 – 1). Graphic items are drawn in the order they are presented. You must pay attention to the order in which you present items. You have control over what gets hidden by other items. After painting the background on the HorGauge canvas, the horizontal bar is drawn, followed by the tick marks, the pointer, and label (see Listing 4).

Listing 4
The function to draw a horizontal gauge loops an additional function to add each tick mark and label to the guage.

for (s=0; s<=10;s++)

This function is shown simplified slightly by eliminating properties such as font, color, and alignment. Note the familiar for next loop that uses a separate function we’ve designed, drawHorTick, to draw each tick mark and a text label for each. Variables passed to the HorGauge function include, valueColor=”blue”, maxValue=1000, minValue=0 for the bar, and (present) valueText=500, labelColor=’black”, and labelText=”Gallons” for the gauge. Photo 2 shows a rendering of this horizontal gauge.

The vertical gauge is similar in construction to the horizontal gauge with a few added visuals. The VertGauge canvas has a width of 80, a height of 260 pixels, and is modeled after a thermometer. In addition to its ‘black”vertical bar, it has a “red” mercury bulb and bar to indicate the temperature. Ticks are every 10° and is based on the variables maxValue and minValue as temperature extremes. Refer to Photo 2 for a rendering of this vertical gauge.

The final example is the essence of engineering measurements—the analog meter movement. The variable meter will define a canvas of the size 160 × 80 pixels. In addition to the previous text and label variables, there are a number of others that will be useful, the starting and ending angles of the pointer’s swing and the labels for these extremes. Angles are based on 0° being to the right (east, if you will). I’ll use 210° and 330° to define the extents. An arc between these extents is drawn using meter.arc(x,y,r,b,e), where x and y is the center point and r is the radius of the arc. The arc begins at b and ends at e. The beginning and ending points must be defined in radians. The degree conversion to radians is radian=degree*Math.PI / 180. Math.PI is the math object p (a constant of approximately 3.14).

There are a few translation methods useful to drawing objects. Translate(x,y) remaps the canvas origin to x,y. The origin of a canvas begins in the upper left corner (0,0). With translate we can move this somewhere else, in this case (80,70), which is the center for the arc we just drew (and axis of the meter’s needle movement). We can draw some tick marks from the new origin to the arc. The center tick mark is easy. It begins at the new origin and is drawn to a point one radius in the x direction. Using the old origin, we’d have to figure out the x,y coordinates of the center and each point on the arc. The translation method lets us reference the center as 0,0 and the draw to point radius,0 (straight up). The other coordinates on the arc are a bit more difficult to calculate. Using the rotate method allows this to become a no brainer. You can think of rotate(radian) as spinning the whole canvas about the origin to a particular angle. In a for-next loop similar to the other tick marks, we can rotate the canvas, draw a radius, repeat. With the correct rotations, all tick marks are simply drawn in the same place. Once drawn, they become part of the canvas and rotate with it.

I added an extra graphic to indicate a partial area on the scale where the reading is nominal. For instance, on an AC voltmeter, we could notate an area where the voltage is acceptable, perhaps 105 to 120 V. This is much like drawing the arc, except instead of just the arc, the object includes the origin as well and boundaries filled in like a pie wedge. Meter.fillStyle=”yellow” and Meter.fill() complete the object. The last item added based on the new origin is the pointer or needle. This is simply a longer tick mark of an appropriate color. The rotate method positions the canvas based on the angle calculated from value=ValueText.valueOf() and b and e (discussed earlier).

valuespan= maxValue-minValue;

Note that the labels for the tick marks could have been added along with the tick marks if you wanted the text to be angled and aligned with each mark. I’ll translate back to the old origin here as the remaining objects are based on the upper-left corner of the canvas. A black rectangle covers the nonexistent guts of the meter. Since the original arc was divided into 10 areas using 11 tick marks, the labels for odd ticks become b, (e-b)*0.2, (e-b)*0.4, (e-b)*0.6, (e-b)*0.8, and e. Finally, labelText is placed to identify the meter. The final rendering is shown in Photo 2.


As I mentioned previously, if you plan to present multiple gauges of various sizes on a single webpage, you may not be satisfied with just letting them render wherever based on screen sizes. A better approach is to preplan the presentation. This can be done using a table. You might have noticed that the canvas sizes I chose are based on approximately 80 pixel increments. That is so they can be arranged into a 80-pixel-based table. The minimum canvas would therefore be 80 × 80 pixels. Let’s use a 6 column × 3 row table for our example. We have seven gauges to display. A 6 × 3 table looks like Figure 1a. Four gauges use a 2 × 1 size, two gauges use 1 × 3, and one gauge uses 4 × 1. The placement of these is shown in Figure 1b.

Figure 1 Here you see a 6 × 3 display (a) and the gauge placement (b)
Figure 1
Here you see a 6 × 3 display (a) and the gauge placement (b)

The table, td, tr, and th elements are used to create a table with its rows and columns. The table element is part of the <body> element in the <html> element. In this case, the table consists of three rows identified by the <tr> element tags. Within each row, the <td> tags identify column data. The properties of a data entry default to colspan=”1″ and rowspan=”1″, but here you can see that some of our data spans multiple columns or rows. The data elements hold the canvas definitions for each gauge—normally with ID, width, height, and other properties—but here abbreviated with a simple gauge number (see Listing 5).

Listing 5
Adding a table to the page defines anchor points which can be used to define the position of each gauge. Note within each row (<TR></TR>), each gauge defines its boundaries using the columnspan and rowspan properties.

		<td colspan=”2”>
			<canvas id=”1” ...>
		<td colspan=”2”>
			<canvas id=”2” ...>
		<td rowspan=”3”>
			<canvas id=”3” ...>
		<td rowspan=”3”>
			<canvas id=”4” ...>
		<td colspan=”2”>
			<canvas id=”5” ...>
		<td colspan=”2”>
			<canvas id=”6” ...>
		<td colspan=”4”>
			<canvas id=”7” ...>

The complete tab-formatted HTML file (including all gauge routines) is 10,000 characters in length. Strip the formatting and it’s down to approximately 7,000. This text file is broken into approximately 55 strings and added to the Arduino server application used last month. That column demonstrated the process using a much simpler three-string HTML file. When I tried to compile this, I found out I had run out of RAM space. But wait. Why couldn’t these be stored as constants in program space? Indeed they could. Listing 6 is last month’s HTML file as string data. Here is the first line using the new approach:

// Start of Pages
const PROGMEM char Page1[] = "<!DOCTYPE 
colspan='2'><canvas id='meter 
gauge1' width='160' 	height='80' 
style='border:1px solid #000000;'>";
Listing 6
This is the simple three-line (String) HTML code from the Arduino application program.

// Start of Page
String String1="<!DOCTYPE HTML PUBLIC '-//IETF//DTD HTML 2.0//EN'><html><head><title>Temperature</title></head><body><h1>";
String String2="-20";
String String3=" degrees F</h1><h3>Outside Temperature</h3></body></html>";
// End of Page

Note that these are assigned as an array of characters. The pages of HTML text ends with a new array of pointers that can be used to retrieve the Page arrays later on (see Listing 7).

Listing 7
Once embellished with graphical elements, the Arduino does not have sufficient RAM space for the 54 lines (Strings) (7000 characters). The HTML text is therefore stored in PROGMEM (static text). It will be read into a 1,024-character buffer one page at a time and served.

const PROGMEM char Page54[] = "</script></body></html>";
const PROGMEM char* const ArrayOfPointers[] = {Page1, Page2, Page3, Page4, Page5, Page6, Page7, Page8, Page9, Page10, Page11, Page12, Page13, Page14, Page15, Page16, Page17, Page18, Page19,  Page20, Page21, Page22, Page23, Page24, Page25, Page26, Page27, Page28, Page29, Page30, Page31, Page32, Page33, Page34, Page35, Page36, Page37, Page38, Page39, Page40, Page41, Page42, Page43, Page44, Page45, Page46, Page47, Page48, Page49, Page50, Page51, Page52, Page53, Page54};
// End of Pages 

There are a few other alterations made to the original Arduino application. Since the text is now part of the program memory, it requires a special routine to retrieve it into a RAM buffer so its length can be measured and served using the AT+CIPSEND command to the ESP8266, where it gets shipped out as TCP packets to the requesting IP. The page is sent via the function sendESP8266HTMLPage() (see Listing 8).

Listing 8
The page is sent via the function  sendESP8266HTMLPage().

boolean sendESP8266HTMLPage()
  HTMLPage(0,7);  // 879
  HTMLData(); // 215
  HTMLPage(8,14);  // 1013
  HTMLPage(15,23);  // 978
  HTMLPage(24,31);  // 923
  HTMLPage(32,38);  // 876
  HTMLPage(39,49);  // 887
  HTMLPage(50,53);  // 323
  return true;

Note that static pages are sent using the function HTMLPage(from,to). This function will read pages from and to loading up the 1,024-byte string buffer. The comment indicates the total number of characters in the from/to character arrays (must be less than the buffer size). The variable data is sent using the function HTMLData(). The data comes from DataArray, which was defined along with other program variables.

float DataArray[] = {33.3,00.
		00,4,120,55,89,310}; // Meter1, 
		Meter2, Gauge1, Gauge2, Vert1, 
		Vert2, Hor1

This array can easily be updated by reading real-time sensors (or, for this example, a random number generator). While the HTML text file was developed so that many of the attributes can be modified, only one value was passed for each of the gauges.


I am a firm believer in using a browser as a universal way to display data without having to deal with different applications that might be needed depending on each user’s OS. With a way to serve up data with a little more pizzazz, I’ll be able to eventually replace my failed weather station with one that can be called up from most of my portable devices from anywhere. Photo 2 shows the final rendering of the table-centric display of the seven gauges. Not bad for a non-artist.

It won’t take much to revise any of these gauges for use as a barometer, or any of the sensors I might want to display for my web-based weather station. The ESP8266 is an amazing little package that provides enough power for many types of Wi-Fi related projects. There are many aspects of the ESP8266 that I have not touched on and I invite you to explore these on your own. Let me know if there are any other areas you’d like me to explore in a future column. There’s more to explore out there than we could possibly cover in 100 lifetimes. You’re never too old to learn something new. 

Read Part 1 Here


Arduino Mega 2560
Arduino |
ESP8266 Wi-Fi Chip
Espressif, Inc. |
HTML-Kit |
Expression Web 4 (Free Version)
Microsoft Corp. |


Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.

Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
Website | + posts

Jeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: or at:

Supporting Companies

Upcoming Events

Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2024 KCK Media Corp.

Serving Up HTML (Part 2)

by Jeff Bachiochi time to read: 16 min