本文最后由 森林生灵 于 2017/06/03 16:14:36 编辑
曾用过 Yeelink 做树莓派的环境温度采集监测,唯一不足之处是 Yeelink 平台的用户数据是公开的,缺乏私密性。刚好,最近有一个课程设计的题目也与此有关,于是便想趁此机会做一个和 Yeelink 功能差不多的私有平台。Google 搜索得知 Yeelink 平台使用了 RESTful 架构,简单了解 REST 后,感觉似乎对于一个小型信息监测平台用此有些小题大做了,想起之前学习的 MQTT 协议和开发过的几个Z-BlogPHP 的插件,索性使用了 MQTT + Z-BlogPHP 来做这个平台。
NodeMCU节点
数据采集节点使用的是 NodeMCU,在 NodeMCU 上可以像 Arduino 一样操作硬件 IO,用 Node.js 类似语法写网络应用,而且具有GPIO、PWM、I2C、1-Wire、ADC等功能,开发起来比较方便。NodeMCU 节点使用 DHT11 定时读取环境温湿度,并通过 MQTT 将数据发布给订阅端的服务器。
配置文件:config.lua
-- config.lua local module = {} module.HOST = "127.0.0.1" -- MQTT Server Host module.PORT = 1883 -- MQTT Server Port module.USER = "mosquitto" -- MQTT User Name module.PWD = "" -- MQTT User Password module.ID = node.chipid() module.ENDPOINT = "/nodemcu/" module.DELAY = 600000 -- 10 minutes module.DHTPIN = 5 -- DHT11 Pin -- module.PMPIN = 6 return module
传感器:sensor.lua
-- sensor.lua local module = {} function sensor_data() status, temp, humi, temp_dec, humi_dec = dht.read(config.DHTPIN) if status == dht.OK then --print("DHT Temperature:" .. temp .. "; Humidity:" .. humi) pm = 0 -- TODO: v = temp + humi + pm ok, json = pcall(cjson.encode, {tdata=temp, hdata=humi, pdata=pm, verify=v}) if ok then --print(json) return json else print("JSON Encode Failed") end elseif status == dht.ERROR_CHECKSUM then print("DHT Checksum Error") elseif status == dht.ERROR_TIMEOUT then print("DHT Read Timeout") end return 0 end return module
Wi-Fi连接:network.lua
-- network.lua local module = {} local function wifi_setup() print('Configuring Wifi ...') wifi.setmode(wifi.STATIONAP) wifi.ap.config({ssid = 'DataEndpoint_' .. config.ID, auth = wifi.OPEN}) -- print('AP MAC:' .. wifi.ap.getmac()) enduser_setup.manual(true) enduser_setup.start( function() enduser_setup.stop() wifi.setmode(wifi.STATION) print('Connected to wifi as:' .. wifi.sta.getip()) -- print('STA MAC:' .. wifi.sta.getmac()) mosquitto.start() end, function(err, str) print('Enduser Setup Error #' .. err .. ':' .. str) end ) end local function wifi_wait_ip() print('Connecting Wifi ...') wifi.setmode(wifi.STATION) tmr.alarm(6, 8000, tmr.ALARM_SINGLE, function() if wifi.sta.getip() == nil then print('IP unavailable, Waiting for Enduser Mode...') wifi_setup() else print('Connected to wifi as:' .. wifi.sta.getip()) print('STA MAC:' .. wifi.sta.getmac()) mosquitto.start() end end ) end function module.start() wifi_wait_ip() end return module
MQTT通信:mosquitto.lua
-- mosquitto.lua local module = {} m = nil -- TODO: local function register_device() m:subscribe(config.ENDPOINT .. config.ID .. "/control", 1, function(client) print("Subscribe Success") end ) end local function push_data() jsondata = sensor_data() m:publish(config.ENDPOINT .. config.ID .. "/sensor", jsondata, 1, 0, function(client) print("Publish Success") sensor.DATA = nil end ) end local function mqtt_start() m = mqtt.Client(config.ID, 120, config.USER, config.PWD) -- Registers a callback function for an event m:on("connect", function(client) print("MQTT Connected") end ) m:on("message", function(client, topic, data) if data ~= nil then print(topic .. ":" .. data) -- TODO: end end ) -- Connect to broker m:connect(config.HOST, config.PORT, 0, 1, function(client) register_device() tmr.stop(5) tmr.alarm(5, config.DELAY, tmr.ALARM_AUTO, push_data) end, function(client, reason) print("MQTT Connect Failed:" .. reason) end ) end function module.start() mqtt_start() end return module
启动脚本:init.lua
-- init.lua config = require('config') sensor = require('sensor') network = require('network') mosquitto = require('mosquitto') network.start()
Z-BlogPHP插件
注册插件
<?php // include.php require dirname(__FILE__) . DIRECTORY_SEPARATOR .'function/dbconfig.php'; RegisterPlugin('ENVChart', 'ActivePlugin_ENVChart'); function ActivePlugin_ENVChart() { Add_Filter_Plugin('Filter_Plugin_Zbp_CheckRights', 'ENVChart_CheckRights'); Add_Filter_Plugin('Filter_Plugin_ViewPost_Template', 'ENVChart_Request'); Add_Filter_Plugin('Filter_Plugin_Index_Begin', 'ENVChart_Request'); Add_Filter_Plugin('Filter_Plugin_Admin_TopMenu', 'ENVChart_Admin_TopMenu'); } function ENVChart_DatabaseTable($table, $option) { global $zbp; if (1 == $option) { if (!$zbp->db->ExistTable($zbp->table[$table])) { $s = $zbp->db->sql->CreateTable($zbp->table[$table], $zbp->datainfo[$table]); $zbp->db->QueryMulit($s); return true; } } else if (0 == $option) { if ($zbp->db->ExistTable($GLOBALS['table'][$table])) { $s = $zbp->db->sql->DelTable($GLOBALS['table'][$table]); $zbp->db->QueryMulit($s); return true; } } return false; } function ENVChart_CheckRights() { global $zbp; $arr = array( array('action' => 'ViewChart', 'level' => 1, 'lang' => '查看图表') ); foreach ($arr as $key => $value) { $zbp->actions[$arr[$key]['action']] = $arr[$key]['level']; $zbp->lang['actions'][$arr[$key]['action']] = $arr[$key]['lang']; } } function ENVChart_Request() { global $zbp; if (isset($_GET['chart'])) { chart_page(); die(); } } function ENVChart_Admin_TopMenu(&$topmenus) { global $zbp; $topmenus[] = MakeTopMenu('ViewChart', '查看图表', $zbp->host .'?chart', '', 'topmenu6'); } function chart_page() { global $zbp; $article = new Post; $article->Title = '环境温湿度曲线图'; $article->IsLock = true; $article->Type = ZC_POST_TYPE_PAGE; $article->Content .= '<style type="text/css">text.highcharts-credits {display: none;}rect.highcharts-background {fill-opacity: 0.9;}</style>'; if (!$zbp->CheckRights('ViewChart')) { $article->Content .= '<div class="textbox_red">没有权限!请 <a href="'. $zbp->host .'?signin">登录</a> 后查看。</div>'; } else { if (!$zbp->CheckPlugin('ENVChart')) {$zbp->ShowError(48);die();} $article->Content .= '<script src="'. $zbp->host .'zb_users/plugin/ENVChart/code/highstock.js"></script><script src="'. $zbp->host .'zb_users/plugin/ENVChart/code/modules/exporting.js"></script><div id="container" style="height: 400px; min-width: 310px;text-align: center;"></div><script src="'. $zbp->host .'zb_users/plugin/ENVChart/code/script.js"></script>'; } $zbp->template->SetTags('title', $article->Title); $zbp->template->SetTags('article', $article); $zbp->template->SetTemplate($article->Template); $zbp->template->SetTags('comments', array()); $zbp->template->Display(); } function InstallPlugin_ENVChart() { global $zbp; if (ENVChart_DatabaseTable('datachart', 1)) { $zbp->SetHint('good', '创建datachart数据表成功!'); } else { $zbp->SetHint('bad', '创建datachart数据表失败!可能是数据表已存在。'); } } function UninstallPlugin_ENVChart() {}
数据表结构
<?php // dbconfig.php $table['datachart'] = '%pre%datachart'; $datainfo['datachart'] = array( 'ID' => array('ID', 'integer', '', 0), 'Time' => array('Time', 'integer', '', 0), 'Hdata' => array('Hdata', 'float', '', 0), 'Tdata' => array('Tdata', 'float', '', 0), 'Pdata' => array('Pdata', 'float', '', 0) );
订阅 MQTT 主题
该脚本需要在命令行中执行。phpMQTT 使用的是 https://github.com/bluerhinos/phpMQTT 。Mysqli 使用的是https://git.oschina.net/lanseyujie/codes/1m9oqs4bhwlfka6v7cdiu62 。
<?php if (!isset($_SERVER['SHELL'])) { header("HTTP/1.0 404 Not Found"); die('Please run this script with bash'); } require dirname(__FILE__) . DIRECTORY_SEPARATOR .'function/Mysqli.php'; require dirname(__FILE__) . DIRECTORY_SEPARATOR .'function/phpMQTT.php'; $mqtthost = '127.0.0.1'; $mqttport = 1883; $mqttclientid = 'MQTTTEST'; $mqttusername = ''; $mqttuserpwd = ''; $mqttsubtopic = '/nodemcu/10732666/sensor'; $dbhost = '127.0.0.1'; $dbport = 3306; $dbuser = 'root'; $dbpwd = ''; $dbname = ''; // 需要跟 Z-Blog 使用同一数据库 $mqtt = new phpMQTT($mqtthost, $mqttport, $mqttclientid); //$mqtt->debug = true; if (!$mqtt->connect(true, NULL, $mqttusername, $mqttuserpwd)) { exit(1); } $topics[$mqttsubtopic] = array('qos'=>0, 'function'=>'procmsg'); $mqtt->subscribe($topics, 0); while ($mqtt->proc()) {} $mqtt->close(); function procmsg($topic, $msg) { echo date('r') ."\n"; echo 'SubTopic:'. $topic ."\n"; echo 'MSG:'. $msg ."\n"; $array = json_decode($msg, true); if (!is_null($array) && array_key_exists('verify', $array)) { $sum = $array['hdata'] + $array['tdata'] + $array['pdata']; if ($sum == $array['verify']) { //var_dump($array); $db = new Mysqli; //$db->debug(); $db->Connect($dbhost, $dbport, 'utf8', $dbuser, $dbpwd, $dbname); $time = time(); $hdata = $array['hdata']; $tdata = $array['tdata']; $pdata = $array['pdata']; $db->Insert("INSERT INTO `zbp_datachart` (Time,Hdata,Tdata,Pdata) VALUES ('$time','$hdata','$tdata','$pdata')"); $db->Close(); } else { echo 'Verify Value is '. $array['verify']; echo 'But Sum is '. $sum .'!Data Verify Fail'; } } }
JSON 数据输出
<?php // SELECT `Time`,`Hdata` FROM `zbp_datachart` ORDER BY `ID` ASC // SELECT `Time`,`Tdata` FROM `zbp_datachart` ORDER BY `ID` ASC // SELECT `Time`,`Pdata` FROM `zbp_datachart` ORDER BY `ID` ASC header("Access-Control-Allow-Origin: https://lanseyujie.com"); require('../../../zb_system/function/c_system_base.php'); $zbp->Load(); if (!$zbp->CheckRights('ViewChart')) { $zbp->ShowError(6); die(); } if (!$zbp->CheckPlugin('ENVChart')) { $zbp->ShowError(48); die(); } $action = GetVars('name', 'GET'); if ('humi' == $action || 'temp' == $action || 'pm' == $action) { if ('humi' == $action) { $column = 'Hdata'; } else if ('temp' == $action) { $column = 'Tdata'; } else if ('pm' == $action) { $column = 'Pdata'; } $sql = $zbp->db->sql->Select($zbp->table['datachart'], array('Time', $column), null, array('ID' => 'ASC'), null, null); $array = $zbp->db->Query($sql); //echo $array[0]['Time']; //echo $array[1]['Hdata']; //echo '<pre>'; //var_dump($array); $array_humi = array(); foreach ($array as $key => $value) { $array_humi[] = array(intval($array[$key]['Time'].'000'), floatval($array[$key][$column])); } //var_dump($array_humi); echo json_encode($array_humi); } //echo '[[1495043031000,19.3],[1495102348000,20.7],[1495110003000,19.31],[1495115556000,19.32],[1495115842000,15.32],[1495115993000,16.03],[1495116342000,20.32]]';
HighStock 配置
HighStock 下载地址:https://www.highcharts.com/products/highstock
// script.js Highcharts.setOptions({ global: { useUTC: false } }); var seriesOptions = [], seriesCounter = 0, names = ['humi', 'temp']; /** * Create the chart when all data is loaded * @returns {undefined} */ function createChart() { Highcharts.stockChart('container', { rangeSelector: { selected: 4 }, yAxis: { labels: { formatter: function() { return (this.value > 0 ? ' + ' : '') + this.value + '%'; } }, plotLines: [{ value: 0, width: 2, color: 'silver' }] }, plotOptions: { series: { compare: 'percent', showInNavigator: true } }, tooltip: { pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>', valueDecimals: 2, split: true }, series: seriesOptions }); } $.each(names, function(i, name) { $.getJSON('https://www.lanseyujie.com/zb_users/plugin/DataChart/json.php?name=' + name.toLowerCase(), function(data) { seriesOptions[i] = { name: name, data: data }; // As we're loading the data asynchronously, we don't know what order it will arrive. So // we keep a counter and create the chart when all the data is loaded. seriesCounter += 1; if (seriesCounter === names.length) { createChart(); } }); });
硬件电路
原理图
PCB
引脚定义
Serial
FlashMode
Relay
Power
DHT11
WakeUp
Extension
本文标题:用Z-Blog构建简易NodeMCU环境监测平台
版权声明:本文使用「署名 4.0 国际」创作共享协议,转载或使用请遵守署名协议。
上一篇:NodeMCU入门