用开源硬件和软件进行能源领域内的数据记录

太阳能电池板、Arduino、PHP、MySQL 和 Flotr

探索在太阳能发电的环境中如何用 Arduino 监视简单的气候情况。具体而言,就是了解如何用 TMP36 芯片进行基本的温度感应、用发光二极管 (LED) 发光以及存储信息以备日后检索。然后整理选定的结果,以图表的形式显示结果,并使用 PHP 作为数据处理器、MySQL® 作为存储、Flotr 作为图表库。

Colin Beckingham, 研究人员, 自由职业者

Colin Beckingham 居住在加拿大东安大略省,是一位自由研究人员、作家和程序员。他拥有金斯顿皇后大学以及温莎大学的学位,对银行、园艺、赛马、教育、公共服务、零售和旅游/观光领域都有涉猎。他是数据库应用程序的作者,也是大量报纸、杂志和在线文章的撰稿人,他的研究兴趣包括 Linux 上的开源编程、VoIP 以及语音控制应用程序。



2011 年 9 月 20 日

概述

由于发电技术目前正向使用更清洁、更智能的原料的方向发展,因此小规模的光电板阵列如雨后春笋般涌现。其中有些安装很复杂,涉及到高度的监控和自调节功能;有些则没有自我感知 (self-awareness) 功能。在后一种情况下,对于所有者而言,若能有一种好的、经济的渠道来获得基本的运行数据,则很有帮助。

您可能想知道,在给定条件下,光电板能否有效工作。光和温度,外加风速和其他因素都能影响面板的输出。光越多产生的电就越多,但是发电越多,单元就会受热,会导致降低效率。气流有助于散热。理论而言,最理想的是明光低温的条件,外加凉风或其他的人工冷却方式。

Arduino 可充当传感器和数据存储器之间的媒介。制作永久性记录以及根据需要显示数据是再直白不过的事情了。本文将研究如何使用 TMP36 温度传感器记录温度数据,如何用一个简单的发光二极管 (LED) 进行发光。


Arduino

Arduino 是一种经济的、可调节和可编程的开源微处理器,可读取其模拟插脚处的电压形式的数据输入。请参考 参考资料 部分,以获得有关此单元的基本信息,以及对游戏环境下的该单元的 developerWorks 简介。传感器连接到特定的输入插脚后,此单元便可通过编程的方式读取这些插脚处的数据。而它处理该信息的方式则取决于硬件的配置。

从 Arduino 获得数据最简单的方式是让此单元直接连接到主机计算机的 Universal Serial Bus (USB) 接口,像串行连接那样读取数据。不过,如果给定供电电源和一个备用通信信道,Arduino 也可以独立于计算机工作。可以向 Arduino 添加扩展板(即 "shields"),将数据直接存储到一个 microSD 内存卡内,通过 mini Web 服务器在一个 cat5e 的网络电缆上传递数据,甚至可以将数据无线传输到一个兼容的接收器中。

图 1 显示了一个装了 Ethernet shield、cat5 Ethernet 及电源电缆的 Arduino 微处理器,此图可让您对此单元的规模有一个大致的了解。

图 1. 具有 Ethernet shield 的 Arduino
具有 Ethernet 组装的 Arduino

某些传感器自身就具有数据平滑内部电路。而有些较简单的传感器则不具备。因此,这里就会有问题出现,比如 Arduino 应该在存储或传输之前进行所有必需的处理,还是应该简单地按原始观测的数据那样报告数据,以便日后在另一台机器上处理这些数据。这两种方式都是完全可以接受的。本项目选择了只简单报告原始的观测数据供日后处理。这就提供了根据需要对算法进行更改的可能性。在应用平滑过程之后,就会固定该过程,且该过程可能是不可逆的。在实际的数据情况下,如果已经知道平滑的要求,那么让 Arduino 来做平滑处理就更为有意义了。


传感器

可用于 Arduino 的传感器种类繁多。在这里,TMP36 和 LED 的使用展示了一种简单、经济且容易重复的方式。

TMP36 是一个特殊构造的晶体管。给它一个电压,它会返回根据不同组合连接器上的外界温度而不断改变的另一个电压。只需记录输出电压并执行一些简单的计算,就能找到以摄氏度或华氏度为单位的温度。参见 参考资料 部分,以获得有关此芯片以及如何将其连接到 Arduino 的帮助。

在一个太阳能发电的环境中记录温度数据看起来再简单不过了,但还是有一些困难需要克服。第一个问题与外界温度有关,即决定将传感器放到哪里。为了避免错误的读数,一般要将其放置于直射阳光之外、不受天气影响且通风很好的一个地方。第二,太阳能板是由一些非同单元组成的,在相同的外界条件下,每个单元各自的运行温度不同。在理想条件下,可以测试所有的单元,然后采用平均数。另一种方式是试验一系列单元,以确定一个具有代表性的平均单元,然后再单独监测这个单元。

一个 LED 既可以在给定了电压且电流合适时亮起来,又可以在光照下生成电压(请参见 参考资料 部分给出的 Mike Cook 的文章)。光照越强,Arduino 在连接插脚处读取的电压越多。设计为可对应于某个特定温标的温度传感器有所不同,您必须通过试验来了解如何将 LED 的输出转变成数值范围已知的光强。

实用设置

假设您太阳能阵列的位置有电源,没有 Ethernet 的局域网 (LAN) 插口,但是处于无线接入点的范围之内。本地的电源可支撑这个无线接入点和 Arduino 的用电,且 Arduino 及 Ethernet shield 插入到中继器。模拟的插脚 3 到 6 可供传感器输入使用;假设插脚 1 和 2 保留给 Ethernet shield 使用(查看 参考资料,以了解有关插脚使用的更多信息)。四个插脚意味着可以有四个传感器,比如两个温度传感器(一个用于外界温度,一个用于面板温度)、一个光传感器和一个风速传感器。对于一个被动面板设置而言,这已经是一组丰富的数据集了。

Arduino 和传感器之间的短距离连接可以用常规的 cat3 电话线(两对未屏蔽的 24 美国线标的电线)就可以实现。这意味着您将能够使用现成可用的电话适配器和布线。

与只有 USB/串行连接的 Arduino 相比,Arduino 与 Ethernet shield 的组合使用并不那么容易。以下是一些需要特别考虑的细节:

  • 通常您无需轮询没有连接传感器的插脚处的电压。
  • 通过一个圆柱式连接器,Arduino 能够接受一定电压范围内的电源(有关更多细节,请参阅 参考资料)。模拟管脚电压读数紧密取决于 Arduino 的供电电压。请确保您进行传感器校准所用的供电单元与将来在现场使用的供电单元是相同的。高级用户一般还会使用 Analog Reference (AREF) 管脚来提供参考电压。
  • 请将温度传感器放在太阳能单元的背后。管带 (duct tape) 完全可以将该传感器固定在单元背后,在短时间温暖较高的情况下也没有问题,但需要确保所有裸露的电线和连接器都用良好的绝缘胶带保护好和绝缘好。在外界温度为 20 摄氏度(69 华氏度)、有亮光照射并且有稳定的微风吹动下,单元的正常运行单元温度 (NOCT) 上升到 50 摄氏度(124 华氏度)的情况并不罕见。
  • 附带 Ethernet shield 的 Arduino 对电源需求很大。如果您使用的是一组全充的 AA 电池来提供电压和电流,那么它们只能支撑几个小时。高级用户一般会用睡眠模式来控制用电。

图 2 显示了连接到太阳能架和面板的 TMP36 传感器。在右侧的黑色绝缘胶带下,是一个感应金属杆的外界温度的传感器。左侧是用布基胶带连接到太阳能单元背后的第二个传感器。与普通的绝缘胶带相比,布基胶带的粘性要更好一些,至少短期内如此。

图 2. 面板上的传感器
连接到太阳能面板的传感器

图 3 显示了位于一个倒转试管(用来做防雨保护)内的一个绿色 LED。

图 3. 光传感器
粘结到一个木桩上的光传感器

用来访问所收集数据的 Arduino 程序(或 sketch,Arduino 对它的称谓)是一个简单的 Web 服务器,可通过一个浏览器或其他脚本(参见 清单 1)访问它。基本代码由 Arduino 作为示例集合的一部分提供,并需要根据您自身的需求做一些修改(参见 参考资料)。

清单 1. Arduino web 服务器
// based on example web server sketch from http://arduino.cc/
#include <SPI.h>
#include <Ethernet.h>
//
byte mac[] = { 0x!!, 0x!!, 0x!!, 0x!!, 0x!!, 0x!! };
byte ip[] = { 192,168,0,xxx };
Server server(80);
//
void setup()
{
  // start the Ethernet connection and the server:
  delay(10000);
  Ethernet.begin(mac, ip);
  server.begin();
}
//
void loop()
{
  // listen for incoming clients
  Client client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println();
	  //
          // output the value of each analog input pin
          for (int analogChannel = 3; analogChannel < 7; analogChannel++) {
            //client.print("analog input ");
            client.print(analogChannel);
            client.print(" : ");
            client.print(analogRead(analogChannel));
            client.println("<br />");
          }
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } 
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
  }
}

清单 1 首先指定所需的 header 库、Ethernet shield 的 Media Access Control (MAC) 地址以及 Internet Protocol (IP) 地址(通过该地址标识 Arduino)。它还设置了一个服务器对象和一个变量来存储可能以后会丢弃的值。setup() 子例程只在启动时运行一次,以便初始化服务器。loop() 子例程无限循环,等待一个 Hypertext Transfer Protocol (HTTP) 请求,然后用所需数据进行回答。为了响应此请求,它发送一个标准的 HTTP 响应头、从相关管脚收集读数并以文本格式发送数据包。在本例中,所提供的数据是该请求瞬间在模拟管脚 3、4、5 和 6 上所经历的电压值。没有针对管脚 1 和 2 发送数据,因为这两个管脚由 Arduino 板和 Ethernet shield 之间的接口使用。可更改通道的范围来只读取使用中的管脚的值。

响应应该类似于 清单 2 所示的输出。

清单 2. Arduino 响应
3 : 292
4 : 288
5 : 286
6 : 280

上述代码给出了管脚的编号以及 0 至 1023 范围内的数值,两个数字之间以冒号分隔。输出也可能比仅指定模拟管脚编号更为具体,但是本机编号灵活性更好,因为它不会将任何一个管脚束缚到某一项作业上。


数据存储

接下来要考虑的事情是数据如何存储。在本例中,将使用 MySQL 作为后端,如 清单 3 所示。

清单 3. 后端表
CREATE TABLE IF NOT EXISTS `readings` (
  `readid` int(11) NOT NULL AUTO_INCREMENT,
  `tstamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `pin` int(11) NOT NULL,
  `value` float NOT NULL,
  PRIMARY KEY (`readid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

此代码会创建一个表,其中包含 ID 字段、默认为此项的系统时间的时间戳、读数所对应的管脚以及实际读数所对应的一个字段,在本例中,存储用一个浮点数表示。


数据收集和整理

定义好后端之后,现在就可以使用 PHP 来查询此 Arduino、分析响应以及在后端存储观察结果,如 清单 4 内所示。

清单 4. PHP 数据记录脚本
<?php
// reader for panel monitors
$fp = fopen("http://192.168.0.xxx","r");
$mysqli = new mysqli("$server",$username,$password,$mysolar);
while ($line = fgets($fp,32)) {
  $line = str_replace("
","",trim($line)); $key = substr($line,0,1); switch ($key) { case 3: // things to do break; case 4: // things to do break; case 5: // this is light from LED recorded as is $val = (int) substr($line,4); $sql = "insert into readings values(NULL,NULL,$key,$val)"; $result = $mysqli->query($sql); //echo "val = $val\n"; break; case 6: // temperature from a TMP36 attached to back of solar cell $val = (int) substr($line,4); $tmp = degc($val); // convert to Celsius/Centigrade $tmp -= 5; // calibration $sql = "insert into readings values(NULL,NULL,$key,$tmp)"; $result = $mysqli->query($sql); break; default: //echo "Found strange key $key!\n"; break; } } function degc($v) { $t = $v * (5/1024); $t -= 0.5; $t *= 100; return round($t,1); } ?>

脚本首先在一个文件指针内打开对 Arduino 的连接,以便读取它。然后打开对 MySQL 后端的连接,以便处理数据输出。之后它会等待来自 Arduino 的响应,并进入某个循环来读取所报告的行。这四个预期行中的任何一行都会包含一个 Hypertext Markup Language (HTML) 回车标记,这使得在测试的同时在浏览器中读取输出变得更为简单。本脚本会删除这个回车。结果行以管脚编号开始,后跟所找到的电压。以管脚编号为中心,然后再进入到一个 switch 结构,根据连接到此管脚的传感器来采取不同的措施。

校准也是在这一步中进行。也就是说,如果您发现某个传感器总是偏高于某个特定数值,那么就可以减去该数值后再报告最终的数值。有一个特定的函数可以将从 TMP36 测得的毫伏数转变为摄氏度,范围也将从 0-1023 转改为从 1-5,找到零点后即可将结果缩放 100。最后,它将处理好结果,并将四舍五入后的数值存储在数据库内。那么如何才能知道传感器是否需要校正呢?其中的一个方法是测量行为类似于针对某个点的单元,并记录辐射率温度读取器。

insert SQL 语句以两个 NULL 值开头,允许后端用第一个 NULL 值替代自增量的 ID 号,用第二个 NULL 值替换当前的时间戳,然后插入此管脚的编号以及该管脚处的读数。您从 crontab 列表使用此脚本进行常规的处理,也可以根据需要从命令行运行该脚本。


图表显示

后端填充好数据后,下一个任务就是让读者很容易读懂这些数据。可用的图表库有很多,各自有各自的优缺点。Flotr(参见 参考资料)是使用 JavaScript 在浏览器窗口中显示图表的一个例子。清单 5 中所示的代码可以读取存储在读数表中的数据,然后以不同的图表显示温度和光数据。

清单 5. 使用 Flotr 的 PHP 图表生成程序
<?php
// read solar data and display in flotr chart
$doctype = "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'>
<head>
<title>LOG reader</title>
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8' />
<script language=\"javascript\" type=\"text/javascript\"
		src=\"../flotr/lib/prototype.js\"></script>
<script language=\"javascript\" type=\"text/javascript\" 
		src=\"../flotr/lib/base64.js\"></script>
<script language=\"javascript\" type=\"text/javascript\"
		src=\"../flotr/lib/canvas2image.js\"></script>
<script language=\"javascript\" type=\"text/javascript\"
		src=\"../flotr/lib/canvastext.js\"></script>
<script language=\"javascript\" type=\"text/javascript\" 
		src=\"../flotr/flotr.js\"></script>
</head><body>";
$mysqli = new mysqli($server,$username,$password,$dbname);
// get temp and light data and chart it
$sensors = array('Temp'=>6,"Light"=>5);
foreach ($sensors as $label=>$sensor) {
  $sql = "select tstamp,value from readings 
	    where pin=$sensor order by tstamp asc";
  $result = $mysqli->query($sql) or die($mysqli->error);
  $mydata1 = "[ ";
  while ($row = $result->fetch_array()) {
        $cfdoy = $row[0];
        $cfdoy = (strtotime($cfdoy)-(5*60*60))*1000;
        $cfamt = $row[1];
        $mydata1 .= "[ $cfdoy , $cfamt ] ,";
  }
  $mydata1 = substr($mydata1,0,-2)." ]";
  $leg = ($sensor == 18) ? "dC" : "mV";
  $title = ($sensor == 18) ? "Cell Temperature" : "Light";
  $conth .= "<div>$label</div>\n
      <div id=\"container$sensor\" style='width:600px; height:250px;'>
      <script type='text/javascript'>
          var f = Flotr.draw(
              $('container$sensor'), [
              { // => first series
                  data: ".$mydata1.",
                  label: '$leg',   
                  htmlText: false,
                  lines: {show: true}
              }],
              {  
                xaxis: {
                  title: 'Time',
                  mode:'time', 
                  noTicks: 10,
                  labelsAngle:45 
                },
                yaxis: {
                  title: '$leg',
                  noTicks: 8
                },
                title: '$title',
                selection: {
                  mode: 'x', 
                  color: '#B6D9FF',
                  fps: 20
                },
                mouse: {
                  track: true,
                  relative: true,
                  margin: 5,
                  trackFormatter: function(obj) { 
                      var dd = parseInt(obj.x);   
                      var d = new Date(dd);
                      return (d.getHours()+4) + ':' 
			  + d.getMinutes() + ' | ' + obj.y; 
                  },
                  position: 'sw',
                  lineColor: '#FF3F19',
                  trackDecimals: 1,
                  sensibility: 2,  
                  fillOpacity: 0.4 
                }
              }  
      );
        </script></div>";
}
echo "$doctype"."<html><body>".$conth."</body></html>";
?>

清单 5 内的代码从定义一个字符串作为 Extensible Hypertext Markup Language (XHTML) 输出的开始,其中包括对 JavaScript Flotr 库的引用。然后它打开对数据存储于的 MySQL 服务器的连接,设置一个数组来包含所使用的管脚编号,遍历这些数组元素以获取相关传感器的数据,并将其代入 JavaScript 脚本。

脚本被设置成将两个图表中的 X 轴数据视为 24 小时的时间值,其中包含鼠标指导,允许您将鼠标悬停在所生成的线上并显示相关坐标。

在显示阶段,您可能会考虑到平滑。这可以是使用一个移动平均量或者使用其他基于权重的模式来中和极端值。

使用根据此设置生成的真实数据,每隔五分钟进行一次取样,此图表脚本生成了如 图 4 所示的未平滑化的输出。在安大略省的五月,天气晴朗,偶尔有风。您可以从 下载 部分获得此数据的一个副本。

图 4. 示例输出
示例输出

请注意光级和单元温度之间的紧密校正,这也是预期进行的操作。

结束语

开源 Arduino 可以作为太阳能发电环境中的传感器数据的一个简单且可调整的处理器,它非常有用。如今可供使用的传感器很多,只要传感器能生成合适的输出电压,或是一个可由 Arduino 明确登记的数字输出,它就能够收集并报告该数据。TMP36 和 LED 是您可以获得的最基本的传感器,高级用户将需要从这些传感器进行升级。

对变化条件下的面板的行为进行良好的编码,这对光电单元的管理者会很有帮助,因为就给定的数据记录而言,它们可以形成观察的基线。管理者以后可以根据推断和建议进行相应的更改,然后再评估面板操控是否已经产生了积极的效果。


下载

描述名字大小
数据示例readings.sql.zip1KB

参考资料

学习

获得产品和技术

  • Arduino:访问这个主站点。
  • MySQL 社区版:访问这个开源数据库的免费下载版。
  • PHP:了解这种通用脚本语言的最新发布版。
  • IBM 产品评估试用版软件:用试用软件改进您的下一个开源开发项目,这些软件可以通过下载获得。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=758627
ArticleTitle=用开源硬件和软件进行能源领域内的数据记录
publish-date=09202011