Remote temperature monitor using ZKit-ARM-1769
Remote temperature monitor is a device to push temperature sensor data from a remote location to a central server. The data is then made available through a web interface. This article shows how to build a simple remote temperature monitor.
This requires a microntroller board which is capable of reading a temperature sensor and sending the data to a central server over an IP network. The ZKit-ARM-1769 microncontroller is suitable for this project, since it has an I2C interface and Ethernet interface. Read on, to find out how to build a remote temperature monitor using the ZKit-ARM-1769.
Overview
The application running in the ZKit-ARM-1769 reads the temperature from the temperature sensor and pushes the data to a webserver, by invoking a callback URL. The central server stores the temperature data in a database. The data can be later queried and viewed from the web server.
Hardware Components
-
ZKit-ARM-1769
-
Temperature Sensor Board
Hardware Setup
-
Connect the Temperature Sensor Board to ZKit-ARM-1769 board, through the I2C header.
-
Connect the ZKit-ARM-1769 board to the network, using an Ethernet cable.
Software Frameworks
NuttX
NuttX is a Unix like realtime operating system. NuttX has been ported to ZKit-ARM-1769. We will use the I2C and Socket API in NuttX to send the temperature to the server.
Flask
Flask is a web framework for Python based on Werkzeug, Jinja 2. Flask will be used to implement the central server to collect and display the data.
Software
We have following software components in this project
-
Reading the temperature.
-
Sending the temperature to the server.
-
Storing the temperature to database.
-
Displaying the temperature from database.
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <nuttx/config.h>
#include <nuttx/i2c.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
/* I2C Device Address */
#define TEMP_SENSOR 0x48
/* Register Addresses */
#define LTHB 0x00
#define I2C_PORT 0
#define FREQUENCY 100000
#define ADDRESS_BITS 7
#define NET_DEVNAME "eth0"
#define PORT_NO 5000
#define IPADDR "172.16.0.10"
#define NETMASK "255.255.0.0"
#define GATEWAY_IP "172.16.0.2"
#define SERVER_IP "172.16.0.133"
static char buf[64];
struct i2c_dev_s *i2c_init(int port, uint32_t frequency, int addr, int nbits)
{
uint32_t result;
struct i2c_dev_s *i2c;
i2c = up_i2cinitialize(I2C_PORT);
if (i2c == NULL) {
printf("nuttx-i2c: Error initializing I2C: %d\n", i2c);
exit(0);
}
printf("nuttx-i2c: I2C Successfully initialized\n");
I2C_SETFREQUENCY(i2c, frequency);
result = I2C_SETADDRESS(i2c, addr, nbits);
if (result != OK) {
printf("nuttx-i2c: Wrong I2C Address.\n");
exit(0);
}
return i2c;
}
int temperature_read(struct i2c_dev_s *i2c)
{
int result;
uint8_t lthb_reg = LTHB;
uint8_t temp;
result = I2C_WRITE(i2c, <hb_reg, 1);
if (result < 0) {
printf("nuttx-i2c: Error Writing. Terminating\n");
return 1;
}
result = I2C_READ(i2c, &temp, 1);
if (result < 0) {
printf("nuttx-i2c: Error Reading. Terminating\n");
return 1;
}
return temp;
}
int network_setup(char *ipaddr, char *gatewayip, char *netmask)
{
struct in_addr hostaddr;
uint8_t mac[6];
int ret;
printf("Assigning MAC\n");
mac[0] = 0x00;
mac[1] = 0xe0;
mac[2] = 0xde;
mac[3] = 0xad;
mac[4] = 0xbe;
mac[5] = 0xef;
ret = uip_setmacaddr(NET_DEVNAME, mac);
if (ret < 0) {
printf("Error setting MAC address\n");
return -1;
}
/* Set up our host address */
printf("Setup network addresses\n");
ret = inet_pton(AF_INET, ipaddr, &hostaddr.s_addr);
if (ret == 0) {
printf("Invalid IPv4 dotted-decimal string\n");
return -1;
} else if (ret < 0) {
printf("inet_pton: af argument unknown\n");
return -1;
}
ret = uip_sethostaddr(NET_DEVNAME, &hostaddr);
if (ret < 0) {
printf("Error setting IP address\n");
return -1;
}
/* Set up the default router address */
ret = inet_pton(AF_INET, gatewayip, &hostaddr.s_addr);
if (ret == 0) {
printf("Invalid GATEWAY_IP dotted-decimal string\n");
return -1;
} else if (ret < 0) {
printf("inet_pton: af argument unknown\n");
return -1;
}
ret = uip_setdraddr(NET_DEVNAME, &hostaddr);
if (ret < 0) {
printf("Error setting GATEWAY IP address\n");
return -1;
}
/* Setup the subnet mask */
ret = inet_pton(AF_INET, netmask, &hostaddr.s_addr);
if (ret == 0) {
printf("Invalid NETMASK dotted-decimal string\n");
return -1;
} else if (ret < 0) {
printf("inet_pton: af argument unknown\n");
return -1;
}
ret = uip_setnetmask(NET_DEVNAME, &hostaddr);
if (ret < 0) {
printf("Error setting NETMASK\n");
return -1;
}
return 0;
}
int app_main(void)
{
int fd;
int ret;
int temperature;
int len;
struct i2c_dev_s *i2c;
struct sockaddr_in addr;
/* Initialize I2C interface */
i2c = i2c_init(I2C_PORT, FREQUENCY, TEMP_SENSOR, ADDRESS_BITS);
/* Setting up the network */
ret = network_setup(IPADDR, GATEWAY_IP, NETMASK);
if (ret < 0) {
printf("nuttx-ethernet: Terminating!\n");
return 1;
}
/* Creating the Socket */
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0) {
printf("nuttx-ethernet: Error creating socket:%d\n", errno);
return 1;
}
/* Connect the socket to the server */
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NO);
inet_pton(AF_INET, (const char *)SERVER_IP, &addr.sin_addr.s_addr);
printf("nuttx-ethernet: client: Connecting...\n");
ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
printf("nuttx-ethernet: Error connecting socket:%d\n", errno);
return 1;
}
ret = fcntl(fd, F_SETFL, O_NONBLOCK);
if (ret < 0) {
printf("nuttx-ethernet: Error setting non-block: %d\n", errno);
return 1;
}
/* Write the data sent. */
while (1) {
temperature = temperature_read(i2c);
snprintf(buf, sizeof(buf),
"GET /update?temperature=%d HTTP/1.1\r\n\r\n", temperature);
len = strlen(buf);
ret = send(fd, buf, len, 0);
if (ret == -1) {
printf("nuttx-ethernet: error writing socket:%d\n",
errno);
goto error_out;
} else if (ret == 0) {
break;
}
while (1) {
ret = read(fd, buf, sizeof(buf));
if (ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
break;
} else if (ret == -1) {
printf("nuttx-ethernet: error reading socket: %d\n",
errno);
return 1;
}
}
sleep(2);
}
error_out:
/* Close the connection. */
printf("nuttx-ethernet: Closing the connection\n");
close(fd);
return 0;
}
Reading the Temperature
i2c_init()
initializes the I2C interface and temperature_read()
reads temperature from the Temperature Sensor Board. This is discussed
in detail in the article
Temperature Sensor Board
With NuttX.
Sending the temperature to the Flask Server
To send the temperature to the server we need to setup the network
parameters. This is done in network_setup()
.
-
For the purpose of this demo project, a bogus MAC address is configured, using
uip_setmacaddr()
. A real product will need a unique MAC address allocated by IANA (Internet Assigned Numbers Authoriy). -
A free IP address in the network must be obtained from the local network administrator. The IP address is converted and assigned using
inet_pton()
anduip_sethostaddr()
. -
The gateway is not if the central server and the ZKit-ARM-1769 are on the same network. But if this is not the case, the gateway IP should be obtained from the local network administrator. The IP address is converted and assigned using
inet_pton()
anduip_setdraddr()
. -
The subnet mask of the current network is obtained from the local network administrator. The mask is converted and assigned using
inet_pton()
anduip_setnetmask()
.
In app_main()
, the temperature data is sent to the server. The
procedure is described below.
-
Setup the network interface.
-
Open a socket to the central web server, with the server’s IP address and port no.
-
In an infinite loop,
-
Read the temperature from the temperature sensor
-
Fire a HTTP GET request with the temperature data to the server. An example HTTP request:
GET /update?temperature=40 HTTP/1.1
-
If any error break the loop
-
-
Close the connection
remote_temp.py
"""Flask application to receive, store and display temperature
updates."""
import sqlite3
from flask import Flask
from flask import request
from flask import g
from flask import render_template
from contextlib import closing
from time import mktime
from time import localtime
from time import strftime
DATABASE = '/tmp/test.db'
DEBUG = True
updatequery = "UPDATE temperature set temperature = ?, Time = ? WHERE Id = ?"
app = Flask(__name__)
app.config.from_object(__name__)
def init_db():
"""Initialise the database."""
with closing(connect_db()) as db:
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.execute("INSERT INTO temperature VALUES(1,0,0)")
db.commit()
def connect_db():
"""Establsih database connection"""
return sqlite3.connect(app.config['DATABASE'])
@app.before_request
def before_request():
"""Establish database connection before http request."""
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
"""Close the database connection."""
db = getattr(g, 'db', None)
if db is not None:
db.close()
@app.route('/update', methods=['GET'])
def update():
"""Update the database with temperature and time."""
temperature = request.args.get('temperature')
g.db.execute(updatequery, [temperature, mktime(localtime()), 1])
g.db.commit()
return 'ok'
@app.route('/display')
def display():
"""Render a html file with temperature and time stamp."""
cur = g.db.execute("select * from temperature")
row = cur.fetchall()
_, temp, time = row[0]
dayname = strftime("%a", localtime(time))
day = strftime("%d", localtime(time))
year = strftime("%Y", localtime(time))
month = strftime("%b", localtime(time))
hrs = strftime("%H", localtime(time))
mins = strftime("%M", localtime(time))
sec = strftime("%S", localtime(time))
entries = dict(temperature=temp,
dayname=dayname,
year=year,
month=month,
day=day,
hrs=hrs,
mins=mins,
sec=sec)
return render_template("show_entries.html", entries=entries)
if __name__ == '__main__':
init_db()
app.run(host="0.0.0.0")
Storing the Temperature Data
In init_db()
, the database is created with table name temperature
and columns id
, temperature
and time
. The SQL script is stored
in a separate file schema.sql
.
schema.sql
drop table if exists temperature;
create table temperature(
id integer primary key autoincrement,
temperature integer not null,
Time integer not null
);
The host
argument in app.run(host="0.0.0.0")
, configures the Flask
application to listen on all network interfaces, so that server can be
accessed from any system on the network.
The Flask application server listens for incoming HTTP requests. When
the server receives a GET /update
request, the update()
funtion is
invoked. In update()
, the temperature data is extracted from the
GET
request and updated in the database.
The mapping from the GET
request to the update()
function is done
using route
decorator.
Displaying the Temperature Data
When the server receives a GET /display
request, the display()
function is invoked. In display()
, the data is retreived from the
database and rendered as a HTML page and returned back to the client.
The template files used for rendering the HTML page
show_entries.html
{% extends "layout.html"%}
{% block body %}
<ul class=entries>
<li><h2>The temperature is recorded {{ entries.temperature }}<sup>o</sup> C at
{{entries.hrs}}:{{entries.mins}}:{{entries.sec}} on {{entries.dayname}},{{entries.month}}
{{entries.day}} {{entries.year}}</h2>
</ul>
{% endblock %}
layout.html
<!doctype html>
<title></title>
<head>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<META HTTP-EQUIV="refresh" CONTENT="15"></head>
<h1>Remote Temperature Monitoring Using ZKit-ARM-1769</h1>
{% block body %}{% endblock %}
</div>
The templates are located as shown in the following directory structure.
/remote_temp.py
/templates
/layout.html
/show_entries.html
For more details, on templates, see Rendering HTML files using Flask
Running the Flask Application
Flask has a built-in webserver for development purpose. The webserver has several limitations. One limitation that concerns us is that, HTTP keep-alive is not supported. Once a request has been served the server closes the connection. But in our little project we would like to open a HTTP connection for the temperature update and use the same connection for further updates.
So instead we use Twisted, a production quality HTTP server that
supports WSGI. Twisted have a command line utility called twistd
.
Run the flask application using Twisted, using the following command.
twistd -n web --port 5000 --wsgi remote_temp.app
References
Credits
Thanks to Visual Pharm for the Thermometer Icon.