-Raspberry Pi slow control suite v0.1
+Raspberry Pi slow control suite v0.2
by Michael Wiebusch
mwiebusch@stud.uni-frankfurt.de
"pwr" - a simple tool to monitor and control one or more laboratory power supplies of the type "GW Instek PSP-405"
+"pressure" - a tool that monitors, plots and logs data coming from a Vacom MVC-3 pressure readout controller
+
Installation:
Step 1:
Install an Apache2 webserver on your Raspberry Pi.
+(sudo apt-get install apache2)
+
+If you want to use the pressure tool also install gnuplot.
+(sudo apt-get install gnuplot)
Step 2:
and use the tools.
You can configure the monitoring/control tools (especially what serial device to use) on each webinterface.
+
+
+
+Change log:
+
+since v0.2: added the support for the pressure readout controller
--- /dev/null
+#!/usr/bin/perl -w
+use Cwd;
+print "Content-type: text/html\n\n";
+
+my $pwd = &Cwd::cwd();
+my $iframewidth = 800;
+my $iframeheight = 340;
+
+
+open(LESEN,"devices.conf")
+ or die "Fehler beim oeffnen von : $!\n";
+
+while(defined(my $i = <LESEN>)) {
+
+ if( $i =~ /^iframesize:(\d+)x(\d+)/g ) {
+ $iframewidth = $1;
+ $iframeheight = $2;
+ }
+
+ if( $i =~ /^DEVICE:([^:]+):([^:]+)/g ) {
+ my $ser_dev=$1;
+ my $dev_id=$2;
+
+print <<EOF;
+<p>
+<iframe name="inlineframe" src="device.htm?device=$ser_dev&id=$dev_id" frameborder="0" scrolling="auto" width="$iframewidth" height="$iframeheight" ></iframe>
+</p>
+EOF
+ }
+}
+
+#print "CWD: ".$pwd."<br>(for debug)\n";
+
+
+return true;
--- /dev/null
+#!/usr/bin/perl
+
+print "content-type: text/html \n\n"; #The header
+
+
+my $envstring = $ENV{'QUERY_STRING'};
+$envstring =~ s/%20/ /g;
+
+my @new_command = split('&',$envstring);
+my $ser_dev = shift(@new_command);
+$ser_dev = "/dev/null" unless defined $ser_dev;
+
+my $ser_dev_id = $ser_dev;
+$ser_dev_id =~ s/\///g;
+
+
+my $directory="shm/pressure_".$ser_dev_id."/";
+my $plotfile=$directory."plot.gif";
+my $datafile=$directory."data";
+
+#$file = "shm/pressure/data";
+if (unlink($datafile) == 1) {
+ print "File deleted successfully.";
+} else {
+ print "File was not deleted.";
+}
+
+
+
+
+return true;
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+<link href="styles.css" rel="stylesheet" type="text/css"/>
+<script src="scripts.js" type="text/javascript"></script>
+<title>Power Supply Monitor and Access</title>
+</head>
+<body style="font-family: sans-serif;" >
+
+
+<p style="bold" id="headline">Pressure Measurement Device</p>
+
+
+<table align=center id="layout" style="
+ border-width: 1px;
+ border-style: solid;
+ border-color: black;
+">
+<tr>
+<td>refresh interval [s]
+</td>
+<td >
+<input type="text" id="txt_interval" name="txt_interval" value="1" size=3 >
+</td>
+</tr>
+<tr>
+<td>show last n points: </td>
+<td><input type="text" id="txt_lastn" name="txt_lastn" value="40" size=3 ></td>
+</tr>
+
+<tr>
+<td><input type="button" onClick="record()" value="record"></td>
+<td><input type="button" onClick="stop_recording()" value="stop recording"></td>
+</tr>
+<tr><td colspan=3><hr /></tr>
+<tr>
+<td id="download_link">download_logfile</td>
+<td><input type="button" onClick="reset_log()" value="reset log"></td>
+</tr>
+<tr><td colspan=3><hr /></tr>
+<tr><td colspan=3 id="status_area" align=center>[recording stopped]</td></tr>
+<tr><td colspan=3 id="plot_area" align=center>...</td></tr>
+
+
+
+</table>
+
+
+
+
+
+
+
+<script language="javascript">
+var updateTask;
+var new_commands = new Array();
+var ser_dev = getParameterByName("device");
+var dev_id = getParameterByName("id");
+var running = false;
+var ser_dev_id_string;
+
+
+
+
+
+
+
+
+/*
+function set_v() {
+ var value = parseFloat(document.getElementById("form_v").value);
+ var command = "SV "+padZeros(value.toFixed(2),5);
+ new_commands.push(command);
+}
+
+function set_v_lim() {
+ var value = parseInt(document.getElementById("form_v_lim").value);
+ var command = "SU "+padZeros(value,2);
+ new_commands.push(command);
+}
+
+function set_c_lim() {
+ var value = parseFloat(document.getElementById("form_c_lim").value);
+ var command = "SI "+padZeros(value.toFixed(2),4);
+ new_commands.push(command);
+}
+
+function set_p_lim() {
+ var value = parseInt(document.getElementById("form_p_lim").value);
+ var command = "SP "+padZeros(value,3);
+ new_commands.push(command);
+}
+
+function turn_on() {
+ new_commands.push("KOE");
+}
+
+function turn_off() {
+ new_commands.push("KOD");
+}
+
+function save_to_eep() {
+ new_commands.push("EEP");
+}
+
+*/
+
+
+
+function refresh_rate(){
+ return parseInt(document.getElementById("txt_interval").value)*1000;
+}
+
+function last_n(){
+ return parseInt(document.getElementById("txt_lastn").value);
+}
+
+function record() {
+ updateTask = setInterval("communication()",refresh_rate());
+ document.getElementById("status_area").innerHTML = "<font color=red>[recording in progress ...]</font>";
+ running = true;
+}
+
+function stop_recording() {
+ clearInterval(updateTask);
+ document.getElementById("status_area").innerHTML = "[recording stopped]";
+ running = false;
+}
+
+function reset_log() {
+ getdata('delete_log.pl?'+ser_dev,alert);
+}
+
+function update(data) {
+ if(!document.getElementById("plot_area").innerHTML) return;
+ document.getElementById("plot_area").innerHTML = data;
+ if(running == true) {
+ updateTask = setInterval("communication()",refresh_rate());
+ }
+ }
+
+
+function communication() {
+ clearInterval(updateTask);
+// getdata('pwr.pl?/dev/ttyUSB0-KOD',update);
+// getdata('pwr.pl?'+ser_dev+'&'+new_commands.join('&'),update);
+ getdata('plot.pl?'+ser_dev+'&'+last_n(),update);
+// new_commands = new Array();
+}
+
+
+function padZeros(theNumber, max) {
+ var numStr = String(theNumber);
+
+ while ( numStr.length < max) {
+ numStr = '0' + numStr;
+ }
+
+ return numStr;
+}
+
+
+function getParameterByName(name)
+{
+ name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
+ var regexS = "[\\?&]" + name + "=([^&#]*)";
+ var regex = new RegExp(regexS);
+ var results = regex.exec(window.location.search);
+ if(results == null)
+ return "";
+ else
+ return decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+
+
+ser_dev_id_string = ser_dev.replace(/\//g, '');
+
+document.getElementById("headline").innerHTML = "Vacom MVC-3 "+'<b>'+dev_id+'</b>'+" [connected to "+'<b>'+ser_dev+'</b>'+"]" ;
+document.getElementById("download_link").innerHTML = '<a target="blank" href="shm/pressure_'+ser_dev_id_string+'/data">download logfile</a>'
+
+
+</script>
+</body></html>
--- /dev/null
+iframesize:800x1250
+
+
+//DEVICE:/path/to/device:Device_ID
+DEVICE:/dev/ttyUSB0:PressureReadoutController
\ No newline at end of file
--- /dev/null
+
+
+ <html>
+ <head>
+<script src="scripts.js" type="text/javascript"></script>
+ <title>Pressure Readout</title>
+
+ </head>
+
+ <body>
+
+<h2>Vacom MVC-3 Webinterface</h2>
+<p id="content">
+Platzhalter
+</p>
+
+<script language="javascript">
+
+function update(data) {
+ if(!document.getElementById("content").innerHTML) return;
+ document.getElementById("content").innerHTML = data;
+ }
+
+
+ getdata('build_index.pl',update);
+</script>
+
+<p id="conf_area">
+Note that you need to have libdevice-serialport-perl or perl-Device-SerialPort<br>
+installed and that the /dev/ttyUSBn need to be accessible by normal users.<br>
+Feel free to alter the config file to accommodate your needs!<br>
+Please don't use the # character to comment out lines<br>
+<TEXTAREA NAME="Address" id="config_field" ROWS=10 COLS=50 >
+[Platzhalter]
+</TEXTAREA>
+<br>
+<input type="button" onClick="save_conf()" value="save">
+<input type="button" onClick="reload_page()" value="reload page">
+
+<script language="javascript">
+
+function save_conf() {
+ var field_content = document.getElementById("config_field").value;
+ field_content = field_content.replace(/\n\r?/g, '&');
+ //alert(field_content);
+ getdata('save_conf.pl?'+field_content,alert);
+
+ //debug//document.getElementById("content").innerHTML = field_content;
+ }
+
+function reload_page() {
+ window.location.reload();
+}
+
+function write_field(data) {
+ if(!document.getElementById("config_field").value) return;
+ document.getElementById("config_field").value = data;
+ }
+
+ getdata('read_conf.pl',write_field);
+</script>
+
+</p>
+
+
+ </body>
+
+ </html>
+
+
+
+
+
+
+
+
--- /dev/null
+#!/usr/bin/perl
+print "Content-type: text/html\n\n";
+#print "Hello, World.";
+use Device::SerialPort;
+use Time::HiRes;
+
+
+
+
+
+# parse the environment string, set some variables
+
+my $envstring = $ENV{'QUERY_STRING'};
+$envstring =~ s/%20/ /g;
+my $port;
+
+my @new_command = split('&',$envstring);
+my $ser_dev = shift(@new_command);
+$ser_dev = "/dev/null" unless defined $ser_dev;
+my $last_n = shift(@new_command);
+
+my $ser_dev_id = $ser_dev;
+$ser_dev_id =~ s/\///g;
+
+my $directory="shm/pressure_".$ser_dev_id."/";
+#my $plotfile=$directory."plot.gif";
+my $datafile=$directory."data";
+my $random_number = rand();
+
+my %report_table;
+
+my $now = sprintf "%02d.%02d.%02d_%02d:%02d:%02d",(localtime)[3],((localtime)[4] +1),((localtime)[5] -100),(localtime)[2],(localtime)[1],(localtime)[0];
+
+# create directory in the shm (somewhere in RAM) for data and plot files, does not have to be stored permanently,
+# as user can download the logfile if needed
+
+unless( -e $directory or mkdir $directory) {
+ print "Unable to create directory for temporary files!\n";
+ exit;
+ }
+
+
+
+
+init_port();
+
+
+
+
+
+
+
+
+
+
+# open logfile for writing
+
+open(LOG,">>$datafile")
+ or print "Fehler beim oeffnen von : $!\n";
+
+
+
+# write random number in the log, for debug purpose
+#print LOG $now."\t".rand()."\t".rand()."\t".rand()."\n";
+#close(LOG);
+
+# read sensor data and write report tables
+my $pressure_unit = UNIT();
+
+if ( -z $datafile ) {
+ print LOG "#time [DD.MM.YY_HH:MM:SS]\t#Sensor 1 pressure [$pressure_unit]\t#Sensor 1 status\t#Sensor 2 pressure [$pressure_unit]\t#Sensor 2 status\t#Sensor 3 pressure [$pressure_unit]\t#Sensor 3 status\n";
+}
+
+# print current time to logfile
+print LOG $now;
+
+for (my $i=1;$i<=3;$i++){
+
+ my $sensor_id = RID($i);
+ my ($pressure, $status) = RPV($i);
+
+ $report_table{$i}= "<table>";
+ $report_table{$i}.= "<tr><td colspan=2 align=center><b>Sensor $i</b></td></tr>";
+ $report_table{$i}.= "<tr><td align=right>Pressure:</td><td>$pressure $pressure_unit</td></tr>";
+ $report_table{$i}.= "<tr><td align=right>Status:</td><td>$status</td></tr>";
+ $report_table{$i}.= "<tr><td align=right>Sensor ID:</td><td>$sensor_id</td></tr>";
+ $report_table{$i}.= "</table>";
+ # print pressure value to logfile
+ $status =~ s/\s/_/g;
+ print LOG "\t$pressure\t$status";
+
+}
+# print newline to logfile and close logfile
+print LOG "\n";
+close(LOG);
+
+
+# print reports and gnuplot pictures to html output
+print '<table>';
+for (my $i=1;$i<=3;$i++){
+
+ print "<tr>";
+ print "<td align=center>";
+ print $report_table{$i};
+ print "</td>";
+ print "<td>".gnuplot($i)."</td>";
+ print "</tr>";
+}
+print "</table>";
+
+
+
+
+# print the html code containing link to the plot
+
+
+#print "sending command RVN<br>";
+#print communicate("RVN")."<br>";
+#print communicate("RID2")."<br>";
+#print communicate("RPV2")."<br>";
+
+
+
+
+print "\n";
+
+
+
+
+
+
+
+
+
+
+
+
+### subroutines ...
+
+
+
+sub init_port {
+
+
+ # talk to the serial interface
+
+ $port = new Device::SerialPort($ser_dev);
+ unless ($port)
+ {
+ print "can't open serial interface $ser_dev\n";
+ exit;
+ }
+
+ $port->user_msg('ON');
+ $port->baudrate(19200);
+ $port->parity("none");
+ $port->databits(8);
+ $port->stopbits(1);
+ $port->handshake("xoff");
+ $port->write_settings;
+
+}
+
+
+### sensor communication subroutines ###
+
+sub UNIT {
+
+ my %unit_lookup = (
+ 0 => 'mbar',
+ 1 => 'Pa',
+ 2 => 'Torr' );
+
+ my $answer = communicate("RGP");
+
+ if ($answer =~ m/^(\d),\t/) {
+ return $unit_lookup{$1};
+ } else {
+ return "no answer";
+ }
+
+
+}
+
+sub RPV {
+
+ my $sensor_number = $_[0];
+ my $value;
+ my $status;
+
+
+ my %status_lookup = (
+ 0 => 'Messwert OK',
+ 1 => 'Messwert < Messbereich',
+ 2 => 'Messwert > Messbereich',
+ 3 => 'Messwert deutlich < Messbereich (Err Lo)',
+ 4 => 'Messwert deutlich > Messbereich (Err Hi)',
+ 5 => 'Sensor off (oFF)',
+ 6 => 'HV on (HU on)',
+ 7 => 'Sensor-Fehler (Err S)',
+ 8 => 'BA-Fehler (Err bA)',
+ 9 => 'kein Sensor (no Sen)',
+ 10 => 'kein Ein- oder Ausschaltpunkt (notriG)',
+ 11 => 'Druckwertüberschreitung (Err P)',
+ 12 => 'Pirani-Fehler ATMION (Err Pi)',
+ 13 => 'Ausfall Betriebsspannung (Err 24)',
+ 14 => 'Filamente defekt (FiLbr)' );
+
+ my $answer = communicate("RPV$sensor_number");
+
+ if ($answer =~ m/(\d),\t(\d\.\d\d\d\dE[+-]\d\d)/) {
+ my $status_number = $1;
+ $value = $2;
+ $status = $status_lookup{$status_number};
+ } else {
+ $status = "no answer";
+ $value = "NAN";
+ }
+
+ return ($value,$status);
+
+}
+
+
+
+sub RID {
+
+ my $sensor_number = $_[0];
+
+ my %status_lookup = (
+ 0 => 'kein Sensor',
+ 1 => 'Ptr',
+ 2 => 'ttr1',
+ 3 => 'ttr',
+ 4 => 'Ctr',
+ 5 => 'bA',
+ 6 => 'bEE',
+ 7 => 'At',
+ 8 => 'Ptr90' );
+
+ my $answer = communicate("RID$sensor_number");
+
+ if ($answer =~ m/^(\d)/) {
+ my $status_number = $1;
+ return $status_lookup{$status_number};
+ } else {
+ return "no answer";
+ }
+
+
+}
+
+sub communicate {
+
+ my $command = $_[0];
+
+
+
+ $port->are_match("\r");
+ $port->lookclear;
+ $port->write("\r$command\r\n");
+
+
+
+ # read what has accumulated in the serial buffer
+ # do 2 seconds of polling
+ for (my $i = 0; ($i<200) ;$i++) {
+ #print $i."\n";
+ while(my $a = $port->lookfor) {
+
+ unless( $a =~ m/^?\tX/) { ## discard the standard error string
+ return $a;
+ }
+
+ Time::HiRes::sleep(.01);
+ }
+
+ }
+
+
+ return "no answer";
+
+
+}
+
+
+
+sub gnuplot {
+
+ my $sensor_number = $_[0];
+ my $plotfile=$directory."plot_sensor_".$sensor_number.".gif";
+
+
+
+ my $col = $sensor_number*2;
+
+ my $gnuplot = "/usr/bin/gnuplot";
+ open(GNUPLOT,"|$gnuplot");
+ print GNUPLOT <<EOF;
+set title "Sensor $sensor_number"
+set terminal gif size 400,300
+set output "$plotfile"
+set xdata time
+set timefmt "\%d.\%m.\%Y_\%H:\%M:\%S"
+set format x " \%H:\%M"
+set xtics rotate
+set ylabel "pressure [$pressure_unit]"
+plot "< tail -$last_n $datafile" using 1:$col title "p" w linespoints
+
+EOF
+
+ return "<img src=$plotfile?$random_number>";
+ #print "<br>";
+
+}
+
+
+
+
+
+
+
+
--- /dev/null
+#!/usr/bin/perl -w
+print "Content-type: text/html\n\n";
+
+use Cwd;
+
+my $pwd = &Cwd::cwd();
+
+
+open(LESEN,"devices.conf")
+ or print "Fehler beim oeffnen von : $!\n";
+
+while(defined(my $i = <LESEN>)) {
+
+print $i;
+
+ }
+
+
+
+return true;
--- /dev/null
+#!/usr/bin/perl -w
+print "Content-type: text/html\n\n";
+
+
+my $envstring = $ENV{'QUERY_STRING'};
+$envstring =~ s/%20/ /g;
+$envstring =~ s/&/\n/g;
+##$envstring =~ s/&/\n/g;
+
+
+open(SCHREIBEN,">devices.conf")
+ or print "Fehler beim oeffnen von : $!\n";
+
+print SCHREIBEN $envstring;
+close(SCHREIBEN);
+
+print "saved!";
+
+
+return true;
--- /dev/null
+
+
+function getdata(command,callback) {
+ var xmlhttp = null;
+ var cb = null;
+ xmlhttp=new XMLHttpRequest();
+ cb = callback;
+
+ xmlhttp.onreadystatechange = function() {
+ if(xmlhttp.readyState == 4) {
+ if(cb)
+ cb(xmlhttp.responseText);
+ }
+ }
+ xmlhttp.open("GET",command,true);
+ xmlhttp.send(null);
+ }
+
+
+
+// function reload() {
+// xmlhttp=new XMLHttpRequest();
+// xmlhttp.onreadystatechange = function() {
+// if(xmlhttp.readyState == 4) {
+// document.getElementById("content").innerHTML=xmlhttp.responseText;
+// if(document.getElementById('logbox')) {
+// if(saveScrollTop) {
+// document.getElementById('logbox').scrollTop = saveScrollTop;
+// }
+// }
+//
+// document.getElementById("stop").style.background="#444";
+// reloadevery = setTimeout('reload()',$.($delay*1000).qq$);
+// }
+// };
+// if(document.getElementById('logbox')) {
+// saveScrollTop = document.getElementById('logbox').scrollTop;
+// if (saveScrollTop == 0) {saveScrollTop = 0.1;}
+// }
+// xmlhttp.open("GET","get.cgi?$.$ENV{'QUERY_STRING'}.qq$",true);
+// xmlhttp.send(null);
+// document.getElementById("stop").style.background="#111";
+// }
\ No newline at end of file
--- /dev/null
+/dev/shm/
\ No newline at end of file
--- /dev/null
+body {
+ background:#def;
+}
+
+
+table#content, table#contentregs {
+ border:1px solid #aaa;
+ border-collapse:collapse;
+}
+
+
+table#content td, table#content th, table#contentregs td, table#contentregs th{
+ border:1px solid #aaa;
+ width:100px;
+ text-align:right;
+ padding-right:15px;
+}
+
+table#content, table#contentregs {
+ border:1px solid #aaa;
+}
+
+
+div#bar1 {
+ width:900px;
+ overflow-x:scroll;
+ }
+
+div#bar1 div {
+ width:66500px;
+ height:0px;
+}
+
+div#bar2 {
+ width:900px;
+ overflow-x:scroll;
+ }
+
+div#bar2 div {
+ width:65536px;
+ height:0px;
+}
+
+
+#total {
+ text-align:center;
+ font-weight:bold;
+ }
\ No newline at end of file