measuring temperature from Linux
One of the things i wanted for a long time is measuring temperature (room and outside) and display this via a graph on my website. I didn't want to pay a huge amount of money for the sensors so that stalled things for a while. A couple of weeks ago a collegue of mine found these nifty RS232 thermometers from a company called Papouch for a very reasonable price (the 14 euro is a bit deceptive since the total costs incl. BTW (VAT) and shipping will be around 20 euro per sensor). The fun thing about these sensors is that the are "intelligent", they present temperature directly on the serial line, so you don't have to do any fancy calculations. Also, since they are RS232 based it will work on any operating system. I ordered two probes for my computer at home, one to measure the room temperature and one for the outside temperature. The arrived after a week or two and i quickly plugged them in the serial ports, on Linux they are represented by:
/dev/ttyS0
/dev/ttyS1
There is a small program which comes with the sensors, tm.c, which is supposed to read from a sensor and show the results on screen, but that does not seem to work on my Fedora Core 3 machines. So i made a small kermit init script:
set modem type none ; There is no modem set carrier-watch off ; If DTR CD are not cross-connected set line /dev/ttyS0 ; Device name set speed 9600 ; Desired speed set parity none ; No parity set stop-bits 2 ; 2 stop bits set flow none ; no flow-control connect ; Enter Connect (terminal) state
and saved this under .kermrc in my home directory, also i made sure i had all the necessary rights on the serial port devices (as root: chown my-userid /dev/ttyS[01]).
When i fired up kermit, i immediatly got some output, so the sensors worked fine.
I decided to write a program in perl which would be flexible enough to cater for all my future temperature needs. After a short quest on google i found that serial port communcations from perl are best done using: Device::SerialPort.
Since this module was not on my system yet, i had perl fetch it from CPAN and install it for me:
perl -MCPAN -e 'install Device::SerialPort'
This didn't complete since there was a problem with one of the tests and perl reported it wouldn't install without force. Well since i am the lazy type i thought i would try the module anyway, so:
cd ~/.cpan/build/Device-SerialPort-1.002/ make install
And that did the job. The first thing you always should do after installing a new module is read the documentation, it most often has ample examples in there to help you getting started:
perldoc Device::SerialPort
Since perl reads like a novel, i'll just give the code straight away:
-
#!/usr/bin/perl
-
#
-
# (c) 20060318 by Ewald
-
# http://www.oiepoie.nl
-
-
-
use Device::SerialPort; # to read serial port data
-
use Getopt::Std; # to handle cmdline options
-
-
my $V="0.9 20060318"; # version and date of latest modification
-
my $DEBUG = 0; # do we want debugging output?
-
my $WAIT = 300; # how many seconds to wait between reads
-
my $ONCE = 0; # run only once?
-
my $UT = 0; # display timestamp in Unix format?
-
my $DONE = 0;
-
my $now;
-
my $TS=1; # do we want timestamps at all? (yes)
-
my $C=1; # display C to denote Celsius? (yes)
-
my $P = "/dev/ttyS0"; # default serial port to monitor
-
-
my %Options;
-
$ok = getopts('t:w:1uhdnc', \%Options);
-
-
print "logger.pl version: $V\n";
-
print "Program to read temperature data from Papouch serial temp probe\n";
-
print "Possible options:\n";
-
print "-h Help (this output)\n";
-
print "-d print debugging output\n";
-
print "-c Don't print C for celsius after values?\n";
-
print "-n Don't display timestamps\n";
-
print "-1 Output temperature and exit\n";
-
print "-u show timestamp in Unix format (seconds since epoch)\n";
-
print "-t x read data from /dev/ttySx (default: $P) \n";
-
print "-w N wait N seconds between readouts (default $WAIT)\n";
-
exit 0;
-
}
-
-
-
-
$P = "/dev/ttyS";
-
$P .= $Options{t};
-
}
-
-
-
# Serial Port initialization
-
-
$port->user_msg(ON);
-
$port->error_msg(ON);
-
$DEBUG && $port->debug($DEBUG);
-
-
-
# unset RTS and DTR
-
$port->rts_active(0);
-
$port->dtr_active(0);
-
-
if (($port->can_modemlines) && $DEBUG) {
-
$ModemStatus = $port->modemlines;
-
print "\n";
-
}
-
-
# by setting RTS and DTR high we supply power to the device
-
# and in return it will supply us with data
-
$port->rts_active(1);
-
$port->dtr_active(1);
-
-
if (($port->can_modemlines) && $DEBUG) {
-
$ModemStatus = $port->modemlines;
-
print "\n";
-
}
-
-
$port->read_char_time(0); # don't wait for each character
-
$port->read_const_time(1000); # 1 second per unfulfilled "read" call
-
-
while (!$DONE) {
-
my ($count,$saw)=$port->read(8); # we should get 8 chars
-
if ($count> 0) {
-
-
$port->rts_active(0); # power-off device for now
-
$port->dtr_active(0);
-
-
# so we want to strip of the ^M (\r)
-
if ($TS) {
-
if ($UT) {
-
}
-
else {
-
}
-
}
-
else {
-
}
-
-
if ($ONCE) { # are we done?
-
$DONE = 1;
-
}
-
else {
-
$port->rts_active(1); # power-on
-
$port->dtr_active(1);
-
}
-
}
-
else {
-
print "ERR: we got $count bytes of data\n";
-
}
-
}
-
-
$port->rts_active(0); # power-off device
-
$port->dtr_active(0);
-
undef $port;
You can download the code from here: logger.pl
So now we got something which displays the output from the temperature sensors, but i wanted a nice graph of the information. Most people with a little Linux experience will see where this is going, rrdtool to the rescue!
Rrdtool is a very handy utility if you want to put anything (and i mean ANYthing) in a graph. It's name stands for "Round Robin Database Tool" and that is wat it uses. In a round robin database values are stored untill the specified storage is used up and then the oldest data gets overwritten with new entries.
So to get a graph of one day of temperature data which i sample every 5 minutes, i would have to define a round robin archive of 288 (24 x 12) entries. Apart from the day graph you also want week, month and year graphs and thats easilly specified in one go.
On Fedora Core 3 we can install rrdtool using yum like this:
yum install rrdtool
All dependencies are automagically resolved. Debian users of course have theire trusty apt-get to do this, there is emerge for Gentoo users, Yast for Suse, and so on. If you are a die-hard, you can fetch the code and compile it yourself but there is a lot to be said for package management. When you install the software, the perl modules are automatically installed with it.
In the code below you can see that there are two DataSources (DS) defined, one in-temp for the temperature sensor in the room, and out-temp for the outdoor sensor. For both DataSources we have 4 Round Robin Archives (RRA). The DS is of type GAUGE which is what you would use for temperature readout. Other options are COUNTER, DERIVE, ABSOLUTE and COMPUTE.
If you want to get started yourself with rrdtool, the best thing to do is read the rrdtutorial. Or of course get somebody elses code and adapt it to your own needs.
Here is the perl code to create all the rrd stuff:
-
#!/usr/bin/perl
-
#
-
# (c) 20060318 by Ewald
-
# http://www.oiepoie.nl
-
-
-
use RRDs;
-
-
sub CreateGraph
-
{
-
# creates the graph
-
# input: $_[0]: interval (ie, day, week, month, year)
-
# $_[1]: $rrd
-
# $_[2]: $img
-
-
my $rrd = $_[1];
-
my $img = $_[2];
-
-
RRDs::graph "$img/logger-$_[0].png",
-
"--lazy",
-
"-s -1$_[0]",
-
"-t Leiden temperature ",
-
"-h", "200", "-w", "600",
-
"-a", "PNG",
-
"-v degrees C",
-
-
"DEF:intemp=$rrd/logger.rrd:in-temp:AVERAGE",
-
"LINE2:intemp#FF0000:Room Temperature",
-
"GPRINT:intemp:MIN: Min\\: %2.lf",
-
"GPRINT:intemp:MAX: Max\\: %2.lf",
-
"GPRINT:intemp:AVERAGE: Avg\\: %4.1lf",
-
"GPRINT:intemp:LAST: Current\\: %2.lf degrees C\\n",
-
-
"DEF:outtemp=$rrd/logger.rrd:out-temp:AVERAGE",
-
"LINE3:outtemp#0000FF:Outside Temperature",
-
"GPRINT:outtemp:MIN: Min\\: %2.lf",
-
"GPRINT:outtemp:MAX: Max\\: %2.lf",
-
"GPRINT:outtemp:AVERAGE: Avg\\: %4.1lf",
-
"GPRINT:outtemp:LAST: Current\\: %2.lf degrees C\\n";
-
-
}
-
-
# location of rrdtool databases
-
my $rrd = '/var/rrd';
-
# location where the images should go
-
my $img = '/oiepoie/rrdtool';
-
-
# get temp for inside and outside sensors
-
-
# if rrdtool database doesn't exist, create it
-
if (! -e "$rrd/logger.rrd")
-
{
-
print "creating rrd database ...\n";
-
RRDs::create "$rrd/logger.rrd",
-
"-s 300",
-
"DS:in-temp:GAUGE:600:-20:100",
-
"DS:out-temp:GAUGE:600:-20:100",
-
"RRA:AVERAGE:0.5:1:576",
-
"RRA:AVERAGE:0.5:6:672",
-
"RRA:AVERAGE:0.5:24:732",
-
"RRA:AVERAGE:0.5:144:1460";
-
}
-
-
# insert value into rrd
-
RRDs::update "$rrd/logger.rrd", "-t", "in-temp:out-temp", "N:$tempIN:$tempOUT";
-
-
# create graphs
-
&CreateGraph("day",$rrd,$img);
-
&CreateGraph("week",$rrd,$img);
-
&CreateGraph("month",$rrd,$img);
-
&CreateGraph("year",$rrd,$img);
Of course you have to make sure that you have write priviledge in the apropriate directories. Both programs do not need root priviledges (and hence should not be run as root just from a security perspective).
You can download the code from here: rrd_logger.pl
Add an entry in cron to run the stuff every 5 minutes:
*/5 * * * * /usr/local/bin/rrd_logger.pl > /dev/null
If you're curious what the output looks like,
it's right on my website










May 7th, 2008 at 12:12 pm
Hi! Thanks for sharing this code. I tried it on my FreeBSD system, but i get the following error:
TIOCMBIC(2147775595) ioctl failed: Inappropriate ioctl for device at tm__srvroom_reading_castagnara.pl line 72
rts_active(0) ioctl: Inappropriate ioctl for device
TIOCCDTR(536900728) ioctl failed: Inappropriate ioctl for device at tm__srvroom_reading_castagnara.pl line 73
dtr_active(0) ioctl: Inappropriate ioctl for device
Any ideas?
Thanks!
Daniel
May 29th, 2008 at 11:17 pm
Are you sure you are connected to the right serial port? Maybe the naming (/dev/ttyS0) is different on FreeBSD. Have a look at: FreeBSD Serial Communications.
Also make sure that there isn’t monitored by the OS to present a login prompt when a terminal is attached (usually configured through /etc/ttys on FreeBSD).
The trick with kermit at the beginning of the article should help you to confirm the right connection.
If everything else fails you could boot knoppix from cdrom and see if it works from there (which would make the problem FreeBSD specific).
August 21st, 2008 at 2:41 pm
Thanks a lot for sharing this code. _logger.pl_ works like a charm with our TM from Papouch. For the USB version it needs a little tweaking. The minimum changes are along the following _diff_ (comments stripped off for readability):
19c19
my $P = “/dev/ttyUSB0″;
67c67
stopbits(2)
—
> $port->stopbits(1)
103c103
read(8); # we should get 8 chars
—
> my ($count,$saw)=$port->read(12); # we should get 8 chars/12 from a TMU
109c109
$saw =~ s/\*….([\+\-]\S+C?)\r/$1/e; # value will be something like: *B1E1+020.9^M or *B1E1-001.3^M
Maybe we can put up a _logger.pl_ for the TMU version.