Представим себе, что у нас есть простой датчик температуры, который измеряет температуру воды котла каждые несколько минут, и нам необходимо сохранить историю измерений в журнале для последующего анализа данных и внесения корректировок в систему отопления. Также допустим, что нам необходимо работать удаленно, через Интернет. Данные должны быть доступны в любое время, круглосуточно. Нужно где-то хранить показания датчика. Для этого попробуем соединить Arduino и Firebase.
Один из альтернативных способов, для таких задач — это Xively. Изначально это был Pachube, потом Cosm и теперь Xively. С Xively клиентом для Arduino вы просто подключаетесь к их API и посылаете им показания датчика. Это довольно быстрый и простой способ. Есть бесплатный тариф использования сервиса. Но есть несколько вещей, которые могут не устраивать наши потребности. Одной из них является предел показаний, которые вы можете хранить. В данный момент при бесплатном тарифе вы можете хранить до 3-х месяцев истории. Второе, вы можете посылать показания раз в 1 сек, таким образом невозможно хранить данные в реальном времени. Третье, нужно учесть то, что вы храните свои данные в базе данных, которая вам не принадлежит.
Что можно еще использовать? Иметь свой собственный сервер базы данных SQL — излишество. И не только из-за стоимости, но и по причине технического обслуживания. Для таких простых проектов вам не нужно тратить целое состояние. Вот здесь и приходит на помощь Firebase. Firebase позволяет подключаться к вашей собственной firebase-базе данных и делать все операции CRUD с помощью простых обращений к их API. Вы можете посетить их сайт, и вам предоставят базу данных 100mb бесплатно. Ее будет достаточно, чтобы хранить журнал за несколько лет. Это то, что нам нужно. Но тут есть большая проблема. Firebase использует только HTTPS по соображениям безопасности, а микропроцессоры Arduino UNO не имеют достаточно мощности для этого. По этой ссылке вы можете прочитать (англ.яз.), как член команды firebase говорит о поддержке SSL. Идея в том, что для решения этой проблемы можно использовать прокси-запросы через сервер, который поддерживает SSL. Для достижения этой цели используем библиотеку Firebase PHP, которую вы можете найти здесь. Т.е. система такая: Arduino-> Php website-> Firebase. После того как данные перешли в базу Firebase, тут вы можете их использовать по-разному. Давайте начнем строить наш проект, и вы увидите, как все это работает.
Аппаратная часть. Используем популярный датчик температуры DS1820. Подключив его к Arduino Uno получим показания температуры в помещении. Также нужен модуль Ethernet. Используем плату на основе контроллера W5100, Схема такая:
А вот устройство со всеми частями в работе:
Следующим шагом является создание бесплатной базы данных firebase на http://www.firebase.com. Затем войдем в базу данных и перейдем на последний пункт меню “Secrets”. Оттуда получаем наш секретный маркер. Запишем его, так как он понадобится нам позже. Теперь у нас есть аппаратное обеспечение и база данных готова. Если у вас уже есть домен или поддомен, то размещаем у себя Firebase файл библиотеки PHP на хостинге.
Затем с помощью FTP загружаем firebase lib.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
<?php /** * Firebase PHP Client Library * * @link https://www.firebase.com/docs/rest-api.html * */ /** * Firebase PHP Class * * @link https://www.firebase.com/docs/rest-api.html * */ class Firebase { private $_baseURI; private $_timeout; private $_token; /** * Constructor * * @param String $baseURI Base URI * * @return void */ function __construct($baseURI = '', $token = '') { if (!extension_loaded('curl')) { trigger_error('Extension CURL is not loaded.', E_USER_ERROR); } $this->setBaseURI($baseURI); $this->setTimeOut(10); $this->setToken($token); } /** * Sets Token * * @param String $token Token * * @return void */ public function setToken($token) { $this->_token = $token; } /** * Sets Base URI, ex: http://yourcompany.firebase.com/youruser * * @param String $baseURI Base URI * * @return void */ public function setBaseURI($baseURI) { $baseURI .= (substr($baseURI, -1) == '/' ? '' : '/'); $this->_baseURI = $baseURI; } /** * Returns with the normalized JSON absolute path * * @param String $path to data */ private function _getJsonPath($path) { $url = $this->_baseURI; $path = ltrim($path, '/'); $auth = ($this->_token == '') ? '' : '?auth=' . $this->_token; return $url . $path . '.json' . $auth; } /** * Sets REST call timeout in seconds * * @param Integer $seconds Seconds to timeout * * @return void */ public function setTimeOut($seconds) { $this->_timeout = $seconds; } /** * Writing data into Firebase with a PUT request * HTTP 200: Ok * * @param String $path Path * @param Mixed $data Data * * @return Array Response */ public function set($path, $data) { return $this->_writeData($path, $data, 'PUT'); } /** * Pushing data into Firebase with a POST request * HTTP 200: Ok * * @param String $path Path * @param Mixed $data Data * * @return Array Response */ public function push($path, $data) { return $this->_writeData($path, $data, 'POST'); } /** * Updating data into Firebase with a PATH request * HTTP 200: Ok * * @param String $path Path * @param Mixed $data Data * * @return Array Response */ public function update($path, $data) { return $this->_writeData($path, $data, 'PATCH'); } /** * Reading data from Firebase * HTTP 200: Ok * * @param String $path Path * * @return Array Response */ public function get($path) { try { $ch = $this->_getCurlHandler($path, 'GET'); $return = curl_exec($ch); curl_close($ch); } catch (Exception $e) { $return = null; } return $return; } /** * Deletes data from Firebase * HTTP 204: Ok * * @param type $path Path * * @return Array Response */ public function delete($path) { try { $ch = $this->_getCurlHandler($path, 'DELETE'); $return = curl_exec($ch); curl_close($ch); } catch (Exception $e) { $return = null; } return $return; } /** * Returns with Initialized CURL Handler * * @param String $mode Mode * * @return CURL Curl Handler */ private function _getCurlHandler($path, $mode) { $url = $this->_getJsonPath($path); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->_timeout); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $mode); return $ch; } private function _writeData($path, $data, $method = 'PUT') { $jsonData = json_encode($data); $header = array( 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonData) ); try { $ch = $this->_getCurlHandler($path, $method); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); $return = curl_exec($ch); curl_close($ch); } catch (Exception $e) { $return = null; } return $return; } } |
А теперь самый важный файл, который вы также должны загрузить на ваш хостинг каталог. Это файл, который будет отвечать на запросы Arduino. Назовем его firebase test.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php require_once 'firebaseLib.php'; // --- This is your Firebase URL $url = 'https://xxxxxx.firebaseio.com/'; // --- Use your token from Firebase here $token = 'xxxxxxxxxxxxxxx'; // --- Here is your parameter from the http GET $arduino_data = $_GET['arduino_data']; // --- $arduino_data_post = $_POST['name']; // --- Set up your Firebase url structure here $firebasePath = '/'; /// --- Making calls $fb = new fireBase($url, $token); $response = $fb->push($firebasePath, $arduino_data); sleep(2); |
Теперь, если вы сделаете вручную запрос http://www.yourdomain.com/firbaseTest.php?arduino_data=21.00 значение 21,00 должно сохраниться в вашей базе данных firebase. И вы можете сразу его увидеть. Довольно просто! Теперь единственное, что осталось сделать Arduino — отправить фактические показания датчика. Таким образом, вместо указанного значения «21,00», которое мы помещаем вручную, будет записано в базе реальное значение температуры. Код Arduino:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#include <SPI.h> #include <Ethernet.h> #include <OneWire.h> int DS18S20_Pin = 2; OneWire ds(DS18S20_Pin); // on digital pin 2 // Enter a MAC address for your controller below. // Newer Ethernet shields have a MAC address printed on a sticker on the shield byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; char serverName[] = "ardufirebase.azurewebsites.net"; // name address of your domain // Set the static IP address to use if the DHCP fails to assign IPAddress ip(192,168,0,177); int serverPort = 80; EthernetClient client; int totalCount = 0; char pageAdd[64]; char tempString[] = "00.00"; // set this to the number of milliseconds delay // this is 5 seconds #define delayMillis 5000UL unsigned long thisMillis = 0; unsigned long lastMillis = 0; void setup() { Serial.begin(9600); // disable SD SPI pinMode(4,OUTPUT); digitalWrite(4,HIGH); // Start ethernet Serial.println(F("Starting ethernet...")); if (Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); // no point in carrying on, so do nothing forevermore: // try to congifure using IP address instead of DHCP: Ethernet.begin(mac, ip); } digitalWrite(10,HIGH); Serial.println(Ethernet.localIP()); delay(2000); Serial.println(F("Ready")); } void loop() { thisMillis = millis(); if(thisMillis - lastMillis > delayMillis) { lastMillis = thisMillis; float temperature = getTemp(); Serial.println(ftoa(tempString,temperature,2)); sprintf(pageAdd,"/firebaseTest.php?arduino_data=%s",ftoa(tempString,temperature,2)); if(!getPage(serverName,serverPort,pageAdd)) Serial.print(F("Fail ")); else Serial.print(F("Pass ")); totalCount++; Serial.println(totalCount,DEC); } } byte getPage(char *ipBuf,int thisPort, char *page) { int inChar; char outBuf[128]; Serial.print(F("connecting...")); if(client.connect(ipBuf,thisPort)) { Serial.println(F("connected")); sprintf(outBuf,"GET %s HTTP/1.1",page); client.println(outBuf); sprintf(outBuf,"Host: %s",serverName); client.println(outBuf); client.println(F("Connection: close\r\n")); } else { Serial.println(F("failed")); return 0; } // connectLoop controls the hardware fail timeout int connectLoop = 0; while(client.connected()) { while(client.available()) { inChar = client.read(); Serial.write(inChar); // set connectLoop to zero if a packet arrives connectLoop = 0; } connectLoop++; // if more than 10000 milliseconds since the last packet if(connectLoop > 10000) { // then close the connection from this end. Serial.println(); Serial.println(F("Timeout")); client.stop(); } // this is a delay for the connectLoop timing delay(1); } Serial.println(); Serial.println(F("disconnecting.")); // close client end client.stop(); return 1; } float getTemp(){ //returns the temperature from one DS18S20 in DEG Celsius byte data[12]; byte addr[8]; if ( !ds.search(addr)) { //no more sensors on chain, reset search ds.reset_search(); return -1000; } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.println("CRC is not valid!"); return -1000; } if ( addr[0] != 0x10 && addr[0] != 0x28) { Serial.print("Device is not recognized"); return -1000; } ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion, with parasite power on at the end byte present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } ds.reset_search(); byte MSB = data[1]; byte LSB = data[0]; float tempRead = ((MSB << 8) | LSB); //using two's compliment float TemperatureSum = tempRead / 16; return TemperatureSum; } char *ftoa(char *a, double f, int precision) { long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000}; char *ret = a; long heiltal = (long)f; itoa(heiltal, a, 10); while (*a != '\0') a++; *a++ = '.'; long desimal = abs((long)((f - heiltal) * p[precision])); itoa(desimal, a, 10); return ret; } |
Приведенный выше код не так сложен, как это может показаться. Используем части веб-клиента примера кода из arduino.cc. В нескольких словах, что мы делаем: мы делаем запрос, передаем текущее показание температуры. Делаем запросы каждые 5 секунд. Самая трудная часть — преобразовать значение с плавающей точкой в строку. Если этого не сделать — получим сообщение об ошибке.
Если все сделаете правильно, то ваш Arduino загрузит показания, и вы увидите заполнение базы данных. Вот видео работы:
Теперь, когда у вас есть заполненная база данных, вы можете использовать эти данные, как вам заблагорассудится. В качестве небольшого тестового примера создадим простой HTML-файл со скриптами и JQuery плагин диаграммы. Покажем график комнатной температуры.
index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<html> <head> <script type='text/javascript' src='https://cdn.firebase.com/js/client/1.0.6/firebase.js'></script> <script language="javascript" type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script> <script type="text/javascript" src="http://people.iola.dk/olau/flot/jquery.flot.js"></script> <script src="controller.js"></script> <link rel="stylesheet" type="text/css" href="bootstrap.min.css" /> </head> <body> <div style="width:800px; margin:0 auto;"> <div><h1 id='tempMsg' style='display:none;width:550px; margin:0 auto;margin-top: 50px;'>Current temp: <span id="currTemp"></span>°C</h1></div> <div id="chart1" style="width:600px;height:300px;"></div> </div> </body> <footer></footer> </html> |
javascript файл (controller.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
$(document).ready(function() { var tempsArray = []; var tArray = []; var sessionArray = []; var currTemp = ''; var dataRef = new Firebase('https://arduino-test.firebaseio.com'); dataRef.on('value', function(snapshot) { var t = snapshot.val(); var count = 0; for (var key in t) { if (t.hasOwnProperty(key)) { var dt = []; dt[0] = count; dt[1] = parseFloat(t[key]); tempsArray = []; tempsArray.push(dt); tArray = []; tArray.push(dt[1]); count++; } } sessionArray.push(tempsArray[0]) //console.log(tempsArray) $.plot($("#chart1"), [ sessionArray ]); currTemp = tempsArray[0][1].toString(); $('#tempMsg').show(); $("#currTemp").text(currTemp); }); }); |
Возможно, вы захотите загрузить и эти файлы в ваш хостинг каталог.
Спасибо за чтение!