Skocz do zawartości

Zdalny (RF433MHz) monitoring warunków w zagrodzie poprzez stronę internetową


rziomber

Pomocna odpowiedź

Mamy budynek gospodarczy oddalony od domu o kilka(naście) metrów i chcemy upewnić się, że panują tam warunki odpowiednie dla zwierząt. Jak do tej pory nasze kury nie były zainteresowane dostępem do Internetu, nie mamy więc tam zasięgu sieci WiFi. Musimy więc jakoś "podkablować radiowo" dane i zaprezentować je w naszym pokoju.

Założeniami projektu, o który prosił mnie kolega było:

  • bezprzewodowe transmitowanie danych z czujnika do domu, przy czym sam czujnik "nie ma dostępu do Internetu"
  • wyświetlanie aktualnego odczytu na LCD oraz aktywacja alarmu w razie spadku temperatury poniżej zadanej wartości
  • zapisywanie wyników na stronie WWW dostępnej poprzez Internet, dzięki czemu warunki można sprawdzać będąc poza domem

kurnik.thumb.png.23f4c90cccbb03ea74de0cc1f486169a.pngSchemat.thumb.png.6edee5e1bf898a411c256ca8414bfc0b.png

Czujnik z nadajnikiem

nadajnik2.thumb.jpg.5e513fd3de5cabb83586a38fb38b3747.jpgczujnik.thumb.jpg.740e5ecbb9f5dc9aa67a3925209672f1.jpg

Nadajnik zasilany jest "kradnąc" prąd ze stale włączonego zasilacza instalacji alarmowej. Dlatego też konieczne było zastosowanie przetwornicy zmniejszającej napięcie do 5V.

Składniki:

Pamiętajmy, że BMP280 toleruje napięcie 3.3V na linii danych, a nasze Arduino wykorzystuje 5V. Najlepiej więc podłączyć szynę I2C poprzez dzielnik napięcia lub konwerter poziomów logicznych. Ja wstawiłem szeregowo rezystor 4.7kΩ na obu pinach I2C, co nie jest rozwiązaniem "profesjonalnym" i zalecanym, ale (chwilowo) działa 🙂

#include <RCSwitch.h> //https://github.com/sui77/rc-switch
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_BMP280.h>

Adafruit_BMP280 bmp;
RCSwitch mySwitch = RCSwitch();

void setup() {
  Serial.begin(9600);
  if (!bmp.begin(0x76)) //changed from default I2C adress 0x77
  {
    Serial.println("Nie odnaleziono czujnika BMP085 / BMP180");
    while (1) {
    }
  }
  mySwitch.enableTransmit(10);
}

void loop() {
  int temperature = (int)bmp.readTemperature();
  mySwitch.send(temperature, 24);
  delay(10000);
}

Odbiornik

odbiornik2.thumb.jpg.8cce0415cf4b57234e85d54abcc61557.jpgodbiornik.thumb.jpg.e0c87fed20ca5827f6800890119bfaec.jpg

ESP8266 odbiera dane z modułu RF433MHz i regularnie przesyła je na stronę WWW. Stanowi ją skrypt PHP, który zapisuje do bazy danych oraz prezentuje wyniki. Wystarczy, by ESP pobrało adres skryptu i metodą GET przekazało dane (http://mojadomena.pl/skryptodbiorczy.php?haslo=tajnehaslo&temperatura=21).

Składniki:

Najtańsze moduły RF433 oferują jedynie kilkadziesiąt cm zasięgu. Musimy wykonać własną antenę z kawałka drutu, by uzyskać użyteczną komunikację.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <RCSwitch.h> //https://github.com/sui77/rc-switch
#include <Wire.h>
#include <LiquidCrystal_I2C.h> //https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library

#define MinimumTemperature 16
#define TempAlarmPin D1

LiquidCrystal_I2C lcd(0x27, 16, 2);
HTTPClient httpc;

const char* ssid     = "";
const char* password = "";
const String accesspassword = "nojakieshaslo";
const String scripturl = "http://domena.saf/receivedata.php";
const char* WiFiHostname = "kurnik";
unsigned long lastTimeLCD = 0;
int temperature = 0;
RCSwitch mySwitch = RCSwitch();
void setup() {
  pinMode(TempAlarmPin, OUTPUT);
  digitalWrite(TempAlarmPin, LOW);
  Serial.begin(9600);
  Wire.begin(D3, D4);
  Wire.setClock(100000);
  lcd.begin();
  lcd.backlight();
  mySwitch.enableReceive(4);  // D2
  pinMode(13, OUTPUT);
  WiFi.hostname(WiFiHostname);
  WiFi.softAPdisconnect();
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
}
void loop() {
  if (mySwitch.available()) {
    temperature = mySwitch.getReceivedValue();
    Serial.print("Received ");
    Serial.println(temperature);
    mySwitch.resetAvailable();
    if (temperature <= MinimumTemperature) {
      digitalWrite(TempAlarmPin, HIGH);
    } else {
      digitalWrite(TempAlarmPin, LOW);
    }
  }

  if (lastTimeLCD == 0 || millis() - lastTimeLCD >= 60000)
  {
    lastTimeLCD = millis();
    lcd.clear();
    lcd.print(String(temperature) + "C");

    String url = scripturl + "?password=" + accesspassword + "&temperature=" + temperature;
    httpc.begin(url);
    int httpCode = httpc.GET();                                                                  //Send the request
    if (httpCode > 0) { //Check the returning code
      String payload = httpc.getString();   //Get the request response payload
      Serial.println(payload);
    }
  }
}

Skrypt PHP:

<?php
DEFINE ('DB_USER', '');
DEFINE ('DB_PASSWORD','');
DEFINE ('DB_HOST','localhost');
DEFINE ('DB_NAME','');
DEFINE ('DB_TABLE_NAME','zagroda');

DEFINE ('PasswordToSave','nojakieshaslo');

$variables = array(
	array("temperature", "float"),
	);
for($i = 0; $i < count($variables); $i++)
{
	if($_REQUEST[$variables[$i][0]] != "")
	{
		$savetodatabase = 1; break;
	}
}

try{
	$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';port=3306', DB_USER, DB_PASSWORD);
	//echo 'Connected to database';
}catch(PDOException $e){
	echo 'Cannot connect to database<br />';
}

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if($savetodatabase && $_REQUEST["password"] == PasswordToSave)
{	
	if(!tableExists($pdo, DB_TABLE_NAME)){
		
		$query= "CREATE TABLE ".DB_TABLE_NAME." (ID int NOT NULL AUTO_INCREMENT,
		";
		
		for($i = 0; $i < count($variables); $i++)
		{
			$query= $query.$variables[$i][0]." ".$variables[$i][1].",
			";
		}
		$query= $query."update_time BIGINT,
		PRIMARY KEY (ID))";
		$pdo->exec($query);
		//echo '<br><br>'.$query.'<br><br>';
	}
	
	$query="INSERT IGNORE INTO ".DB_TABLE_NAME." (ID,";
	if($_REQUEST["temperature"] != ""){
		$query = $query."temperature,update_time";
	}
	
	$query= $query.") VALUES (1,";
	if($_REQUEST["temperature"] != ""){
		$query = $query."'".mysql_real_escape_string($_REQUEST["temperature"])."',";
	} 
	
	$query= $query."'".time()."') ON DUPLICATE KEY UPDATE ";
	if($_REQUEST["temperature"] != ""){
		$query = $query."temperature=VALUES(temperature),update_time=VALUES(update_time)";
	} 
	
	//echo '<br><br>'.$query.'<br><br>';
	$pdo->exec($query);
}
else
{
	if($_REQUEST["values"] == "1"){
		$query="SELECT * FROM `".DB_TABLE_NAME."` ORDER BY `ID` DESC LIMIT 10";
		$stmt = $pdo -> query($query);	
		$row = $stmt->fetch(/* PDO::FETCH_ASSOC */);
		echo $row[1].';'.$row[2].';';
		$stmt->closeCursor();
	}
	else{
		echo '<html><head><meta charset="UTF-8"><title>Chicken coop</title></head>
		<body><table border="3">
		<tr><td><b>Temperature</b></td> <td><b>Time</b></td></tr>
		<tr><td><span id="temp"></span>°C</td> <td><span id="time"></span></td></tr>
		</table>
		<script>myTimer();var myVar = setInterval(myTimer, 3000);function myTimer() {
		var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { 
		if (this.readyState == 4 && this.status == 200) { 
		var str = this.responseText; 
		var values = str.split(";"); 
		document.getElementById("temp").innerHTML = values[0]; 
		document.getElementById("time").innerHTML = Unix_timestamp(values[1]); 
		} }; 
		xhttp.open("GET", "receivedata.php?values=1", true); xhttp.send();}
		
		function Unix_timestamp(t)
		{
		var dt = new Date(t*1000);
		var day = dt.getDate();
		var month = "0" + (dt.getMonth()+1);
		var year = dt.getFullYear();
		var hr = dt.getHours();
		var m = "0" + dt.getMinutes();
		var s = "0" + dt.getSeconds();
		return day+ "." + month.substr(-2) + "." + year + " " + hr+ ":" + m.substr(-2) + ":" + s.substr(-2);
		}
		</script>
		</body></html>';
	}
}

function tableExists($pdo, $table) {
	try {
		$result = $pdo->query("SELECT 1 FROM $table LIMIT 1");
	} catch (Exception $e) {
		return FALSE;
	}
	return $result !== FALSE;
}
?>

Jeżeli zechcemy dopisać do tego zapisywanie historii (przy obecnych założeniach projektu prezentowany jest jedynie najnowszy odczyt) i wykres zmian w czasie, polecam zapoznać się z biblioteką Google Charts. Przykład jej wykorzystania razem z AJAXem.

Możemy również wykonać kolejny wyświetlacz np do innego pokoju czy nawet innego budynku, który odczytywał będzie wartości ze wspomnianej strony WWW. Wykorzystałem do tego znane nam ESP oraz wyświetlacz siedmiosegmentowy sterowany chipem MAX7219.

Remote_temperature_sensors.thumb.jpg.943f8131eda4a7cb39b83b5994e718eb.jpg

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include "LedControl.h" //http://github.com/wayoda/LedControl
#include <MD5Builder.h>

#define WiFimodeButton 14 //D5
String scripturl = "";
String ssid = "", pass = "";
String host, url, http;
const int httpPort = 80;
const char* WiFiHostname = "Henhouse";
const String accesspassword = "nojakieshaslo";

unsigned long lastTimeLCD = 0, lastDataRead;
unsigned int intervalDataRead = 5;

WiFiClient client;
ESP8266WebServer server(80);
HTTPClient httpc;
// EasyESP or NodeMCU Pin D8 to DIN, D7 to Clk, D6 to LOAD, no.of devices is 1
LedControl lc = LedControl(D8, D7, D6, 1);

void setup() {
  pinMode(WiFimodeButton, INPUT_PULLUP);
  Serial.begin(9600);

  /*
    The MAX72XX is in power-saving mode on startup,
    we have to do a wakeup call
  */
  lc.shutdown(0, false);
  /* Set the brightness to a medium values */
  lc.setIntensity(0, 1);
  /* and clear the display */
  lc.clearDisplay(0);

  EEPROM.begin(512);
  EEPROM.get(0, intervalDataRead);
  WiFi.softAPdisconnect();
  WiFi.disconnect();
  int i;
  if (ssid == "")
  {
    for (i = 100; i < 170; i++)
    {
      if (EEPROM.read(i) == NULL) {
        break;
      }
      ssid += char(EEPROM.read(i));
    }
    for (i = i + 1; i < 250; i++)
    {
      if (EEPROM.read(i) == NULL) {
        break;
      }
      pass += char(EEPROM.read(i));
    }
  }

  if (scripturl == "")
  {
    for (i = i + 1; i < 450; i++)
    {
      if (EEPROM.read(i) == NULL) {
        break;
      }
      scripturl += char(EEPROM.read(i));
    }
  }

  if (digitalRead(WiFimodeButton) == LOW) {
    //access point part
    Serial.println("Creating Accesspoint");
    IPAddress apIP(192, 168, 1, 1);
    WiFi.mode(WIFI_AP);
    WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
    WiFi.softAP("aquarium", "aquarium123");
    Serial.print("IP address:\t");
    Serial.println(WiFi.softAPIP());
  }
  else
  {
    //station part

    Serial.print("connecting to...");
    Serial.println(ssid);
    WiFi.hostname(WiFiHostname);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid.c_str(), pass.c_str());
    /*
      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
      }
    */
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
  }
  server.on("/", handleRoot);
  server.on("/login", handleLogin);
  const char * headerkeys[] = {"Cookie"} ;
  size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
  //ask server to track these headers
  server.collectHeaders(headerkeys, headerkeyssize);
  server.begin();
}

void loop() {
  if (lastDataRead == 0 || (millis() - lastDataRead >= intervalDataRead * 1000))
  {
    //host = scripturl.substring(0, scripturl.indexOf('/'));
    httpc.begin("http://" + scripturl + "?values=1"); //Specify request destination
    int httpCode = httpc.GET();                                                                  //Send the request

    if (httpCode > 0) { //Check the returning code

      String payload = httpc.getString();   //Get the request response payload
      Serial.println(payload);                     //Print the response payload
      String temperature1 = explode(';', payload, 0);
      //String temperature2 = explode(';', payload, 2);
      Serial.print(temperature1);
      //Serial.print(" ");
      //Serial.println(temperature2);
      lc.clearDisplay(0);
      auto printLED = [](unsigned char beginLEDsegment, String printString)->void {
        int i;
        for (i = 0; i < printString.length(); i++) {
          lc.setChar(0, beginLEDsegment - i, (printString[i] == '.') ? printString[i + 1] : printString[i], (printString[i + 1] == '.') ? true : false);
          if (printString[i] == '.') {
            break;
          }
        }
        //lc.setChar(0, beginLEDsegment - i - 1, 'C', false);
        lc.setRow(0, beginLEDsegment - i - 1, B01001110); //"C"
      };
      printLED(7, String(temperature1));
      //printLED(3, String(temperature2));

      if (payload.length() > 4) {
        lastDataRead = millis();
      } else
      {
        lastDataRead += 1000;
      }
    }

    httpc.end();
  }
  server.handleClient();
}

void handleRoot() {
  if (!is_authentified()) {
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(301);
    return;
  }

  int i;
  if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "")
  {
    ssid = server.arg("SSID");
    pass = server.arg("wifipass");
    for (i = 0; i < server.arg("SSID").length(); i++)
    {
      EEPROM.write(i + 100, server.arg("SSID")[i]);
    }
    i += 100;
    EEPROM.write(i, NULL);

    for (i = 0; i < server.arg("wifipass").length(); i++)
    {
      EEPROM.write(i + 101 + server.arg("SSID").length(), server.arg("wifipass")[i]);
    }
    //i += 3 + server.arg("wifipass").length();
    EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL);
    EEPROM.commit();
  }

  if (server.hasArg("scripturl") && server.arg("scripturl") != "")
  {
    scripturl = server.arg("scripturl");
    for (i = 0; i < scripturl.length(); i++)
    {
      EEPROM.write(i + 102 + ssid.length() + pass.length(), scripturl[i]);
    }
    EEPROM.write(ssid.length() + pass.length() + scripturl.length() + 102, NULL);
    EEPROM.commit();
  }

  if (server.arg("intervalDataRead").toInt() > 0)
  {
    intervalDataRead = server.arg("intervalDataRead").toInt();
    EEPROM.put(0, intervalDataRead);
    EEPROM.commit();
  }

  if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && server.arg("wifipass") != "")
  {
    ESP.restart();
  }

  String content = "<html><head><title>Henhouse</title></head><body>";
  content += "<form action='/' method='POST'>Home WiFi Network Name (SSID): <input type='text' name='SSID' value='" + String(ssid) + "'><br>";
  content += "Password: <input type='password' name='wifipass'><br>";
  content += "URL to datalogging script (without http://): <input type='text' name='scripturl' id='scripturl' value='" + scripturl + "'><br>";
  content += "Read values every [seconds]: <input type='text' name='intervalDataRead' value='" + String((int)intervalDataRead) + "'><br>";
  content += "<script language=\"javascript\">function stringReplace() {var s = document.getElementById(\"scripturl\").value;var removeThis = /^(http?|https):\\/\\//;s = s.replace(removeThis, '');document.getElementById(\"scripturl\").value = s;}</script>";
  content += "<input type='submit' value='Submit' onClick=\"stringReplace();\"></form><br>";
  content += "</body></html>";
  server.send(200, "text/html", content);
}

bool is_authentified() {
  Serial.println("Enter is_authentified");
  if (server.hasHeader("Cookie")) {
    Serial.print("Found cookie: ");
    String cookie = server.header("Cookie");
    Serial.println(cookie);
    String cookielogin = "WEATHERSTATION=" + md5(accesspassword);
    if (cookie.indexOf(cookielogin) != -1) {
      Serial.println("Authentification Successful");
      return true;
    }
  }
  Serial.println("Authentification Failed");
  return false;
}

void handleLogin() {
  String msg;
  if (server.hasHeader("Cookie")) {
    Serial.print("Found cookie: ");
    String cookie = server.header("Cookie");
    Serial.println(cookie);
  }
  if (server.hasArg("DISCONNECT")) {
    Serial.println("Disconnection");
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.sendHeader("Set-Cookie", "WEATHERSTATION=0");
    server.send(301);
    return;
  }
  if (server.hasArg("PASSWORD")) {
    if (server.arg("PASSWORD") == accesspassword) {
      server.sendHeader("Location", "/");
      server.sendHeader("Cache-Control", "no-cache");
      String cookielogin = "WEATHERSTATION=" + md5(accesspassword);
      server.sendHeader("Set-Cookie", cookielogin);
      server.send(301);
      Serial.println("Log in Successful");
      return;
    }
    msg = "Wrong username/password! try again.";
    Serial.println("Log in Failed");
  }
  String content = "<html><body><form action='/login' method='POST'>";
  content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
  content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
  server.send(200, "text/html", content);
}

String md5(String str) {
  MD5Builder _md5;
  _md5.begin();
  _md5.add(String(str));
  _md5.calculate();
  return _md5.toString();
}

String explode(char character, String string, unsigned int position)
{
  unsigned int i, ii;
  unsigned int counter = 0;
  for (i = 0; i < string.length(); ++i)
  {
    if (string[i] == character) {
      counter++;
      i++;
    }
    if (counter >= position) {
      break;
    }
  }
  for (ii = i + 1; ii < string.length(); ii++)
  {
    if (string[ii] == character) {
      break;
    }
  }
  return string.substring(i, ii);
}

 

Edytowano przez rziomber
  • Lubię! 2
Link do komentarza
Share on other sites

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.