Skocz do zawartości
rziomber

Sterownik akwarium zarządzany przez stronę WWW

Pomocna odpowiedź

Napisano (edytowany)

Kolega Robert (SP9-10126-KR) poprosił mnie o wykonanie sterownika do akwarium.

IMG_20171126_125636.thumb.jpg.9b98676c67c149cb203e5156350a86f2.jpgIMG_20171209_193118.thumb.jpg.70eaafd5a43f608d135077db00643ac7.jpg

Założeniem była "symulacja dnia" (płynne rozjaśnianie i ściemnianie oświetlenia LED o świcie i zmierzchu) oraz monitorowanie temperatury wody z alarmem (w razie awarii fabrycznej grzałki z termostatem do akwariów).

Aquarium_controller.thumb.jpg.df524f52cbc89f47b604e53036f1bd8f.jpg

Pracę urządzenia można doglądać za pomocą strony WWW. Parametry odświeżają się na żywo dzięki zastosowaniu technologii AJAX.

akwarium.thumb.jpg.955a07c16ce898973c471d768a34687f.jpg

Jeżeli temperatura nie znajdzie się w ustawionym przedziale zostanie włączony alarm.

IMG_20171222_181001.thumb.jpg.e1f0e55a2142edbc1c95b346edefcde6.jpg

Czas synchronizowany jest automatycznie korzystając z serwera NTP. Ciekowostką jest możliwość konfiguracji nazywy SSID sieci WiFi i hasła do niej przez samego użytkownika poprzez stronę sterująca. Standardowo urządzenie łączy się z siecią WiFi. W przypadku, gdy zapamiętana konfiguracja jest niezgodna z parametrami sieci domowej, wystarczy nacisnąć i przytrzymać przycisk przed podłączeniem do prądu. ESP8266 uruchomi się w trybie Access Pointa, z którym będziemy mogli się połączyć i na stronie http://192.168.1.1 dokonamy poprawek.

Aquarium_controller_webpage.thumb.png.98b98cb3706d49335561a28c077c5f46.png

Po podłączeniu do sieci domowej WiFi możemy też nacisnąć wspomniany przycisk w trakcie pracy urządzenia, co pozwoli na wyświetlenie uzyskanego z DHCP IP. Ułatwi to wejście na stronę konfiguracyjną.

Przy uruchomieniu następuje odczyt ustawień sieci

#include <EEPROM.h>
String ssid = "", pass = "";
[...]
setup(){
   if (ssid == "")
  {
    int i;
    for (i = 100; i < 170; i++)
    {
      if (EEPROM.read(i) == NULL) {
        break;
      }
      ssid += char(EEPROM.read(i));
    }
    for (int ii = i + 1; ii < 250; ii++)
    {
      if (EEPROM.read(ii) == NULL) {
        break;
      }
      pass += char(EEPROM.read(ii));
    }
  } 
  
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid.c_str(), pass.c_str());
  [...]
  
  server.on("/", handleRoot);
  server.on("/sensors", handleSensors);
  server.begin();
}

Za odbieranie danych z formularza WWW i zapisywanie ich w pamięci odpowiada

 void handleRoot() {
   [...]
  if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && 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]);
    }
    EEPROM.write(server.arg("SSID").length() + server.arg("wifipass").length() + 101, NULL);
    EEPROM.commit();
    ESP.restart();
  }
}

Cały kod wygląda następująco:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <WiFiUdp.h>
#include <Time.h>  // http://www.pjrc.com/teensy/td_libs_Time.html
#include <ESP8266mDNS.h>
#include <OneWire.h> //https://github.com/adafruit/ESP8266-Arduino/tree/esp8266/libraries/OneWire
#include <DallasTemperature.h> //https://github.com/milesburton/Arduino-Temperature-Control-Library
#include <Wire.h>
#include <LiquidCrystal_I2C.h> //https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library

#define PWMpin 15  //(D8)
#define WiFimodeButton 12 //D6
#define PumpPin 13  //(D7)
#define TempAlarmPin 14  //(D5)
#define ONE_WIRE_BUS 4 //D2

/*char ssid[] = "";  //  your network SSID (name)
  char pass[] = "";       // your network password */
String ssid = "", pass = "";
const char* WiFiHostname = "aquarium";
unsigned char screenCount = 0, tmBegin, tmEnd, dusk, pumpBegin, pumpEnd, tempMin, tempMax;
bool lastTimeLCD, tempAlarm, lastButtonStatus = HIGH;
unsigned int localPort = 2390;      // local port to listen for UDP packets
int PWM = 0;
float temp = 0;
/* Don't hardwire the IP address or we won't get the benefits of the pool.
    Lookup the IP address for the host name instead */
//IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server
IPAddress timeServerIP; // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
ESP8266WebServer server(80);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
LiquidCrystal_I2C lcd(0x27, 16, 2);
tmElements_t tm;
time_t getNtpTime();

unsigned long last_time_sync = 0, last_NTP_check = 0;
void setup()
{
  pinMode(PWMpin, OUTPUT);
  pinMode(WiFimodeButton, INPUT_PULLUP);
  pinMode(TempAlarmPin, OUTPUT);
  Serial.begin(9600);
  EEPROM.begin(512);
  Wire.begin(0, 2); //D2, D4
  Wire.setClock(100000);
  sensors.begin();
  lcd.begin();
  lcd.backlight();
  tmBegin = EEPROM.read(0);
  tmEnd = EEPROM.read(1);
  dusk = EEPROM.read(2);
  pumpBegin = EEPROM.read(3);
  pumpEnd = EEPROM.read(4);
  tempAlarm = EEPROM.read(5);
  tempMin = EEPROM.read(6);
  tempMax = EEPROM.read(7);
  // We start by connecting to a WiFi network
  //  WiFi.mode(WIFI_AP_STA);
  WiFi.softAPdisconnect();
  WiFi.disconnect();

  if (ssid == "")
  {
    int i;
    for (i = 100; i < 170; i++)
    {
      if (EEPROM.read(i) == NULL) {
        break;
      }
      ssid += char(EEPROM.read(i));
    }
    for (int ii = i + 1; ii < 250; ii++)
    {
      if (EEPROM.read(ii) == NULL) {
        break;
      }
      pass += char(EEPROM.read(ii));
    }
  }

  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());
  }
  /* MDNS.begin("aquarium");
    MDNS.addService("http", "tcp", 80);*/
  Serial.println("Starting UDP");
  udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(udp.localPort());
  server.on("/", handleRoot);
  server.on("/sensors", handleSensors);
  server.begin();
}

void loop()
{
  Serial.println(ssid);
  Serial.println(pass);
  Serial.println(WiFi.status());
  if (millis() - last_time_sync > 10 * 24 * 60 * 60 * 1000 || last_time_sync == 0) {
    unsigned long unixt = unix_time();
    if (unixt > 1512876158) {
      setTime(unixt);
      last_time_sync = millis();
    }
  }
  Serial.print("Time library ");
  Serial.print(current_time(0));
  breakTime(now(), tm);
  if (now() < 1512876158) {
    PWM = 0;
  }

  else if (tmBegin < tmEnd && tm.Hour >= tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour >= tmBegin || tm.Hour < tmEnd))
  {
    if (tm.Hour * 60 + tm.Minute < tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk) {
      PWM = 1023 - map(tmBegin * 60 * 60 + dusk * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60  + tm.Second), 0, dusk * 60, 0, 1023);
    }
    if (tm.Hour * 60 + tm.Minute > tmEnd * 60 - dusk) {
      if (tmBegin < tmEnd)
      {
        PWM = 1023 - map(tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second - (tmEnd * 60 * 60 - dusk * 60), 0, dusk * 60, 0, 1023);
      } else
      {
        PWM = map(tmEnd * 60 * 60 - (tm.Hour * 60 * 60 + tm.Minute * 60 + tm.Second), 0, dusk * 60, 0, 1023);
      }
    }
    if (tm.Hour * 60 + tm.Minute >= tmBegin * 60 + dusk && tm.Hour * 60 + tm.Minute <= tmEnd * 60 - dusk || tmBegin > tmEnd && (tm.Hour * 60 + tm.Minute > tmBegin * 60 + dusk || tm.Hour * 60 + tm.Minute < tmEnd * 60 - dusk))
    {
      PWM = 1023;
    }
  }

  /*else if (tmBegin < tmEnd && tm.Hour > tmBegin && tm.Hour < tmEnd || tmBegin > tmEnd && (tm.Hour > tmBegin || tm.Hour < tmEnd))
    {
    PWM = 1023;
    }
    else if (tm.Hour == tmBegin)
    {
    PWM = map(tm.Minute, 0, 59, 0, 1023);
    }
    else if (tm.Hour == tmEnd)
    {
    PWM = map(59 - tm.Minute, 0, 59, 0, 1023);
    }*/
  else
  {
    PWM = 0;
  }
  analogWrite(PWMpin, PWM);
  Serial.print(" ");
  Serial.println(PWM);
  server.handleClient();

  if (now() % 2 != lastTimeLCD)
  {
    lastTimeLCD = now() % 2;

    sensors.requestTemperatures();

    if (digitalRead(WiFimodeButton) == LOW && lastButtonStatus == HIGH && WiFi.status() == WL_CONNECTED) {
      screenCount++;
    }
    lastButtonStatus = digitalRead(WiFimodeButton);

    temp = sensors.getTempCByIndex(0);
    if (tempAlarm && (temp < (float)tempMin || temp > (float)tempMax)) {
      digitalWrite(TempAlarmPin, HIGH);
    } else {
      digitalWrite(TempAlarmPin, LOW);
    }

    if (tm.Hour >= pumpBegin && tm.Hour < pumpEnd) {
      digitalWrite(PumpPin, HIGH);
    } else {
      digitalWrite(PumpPin, LOW);
    }

    lcd.clear();
    switch (screenCount % 2)
    {
      case 0:
        lcd.print(current_time(1));
        lcd.setCursor(0, 1);
        lcd.print(temp, 1);
        lcd.print("C Day");
        lcd.print(tmBegin);
        lcd.print("-");
        lcd.print(tmEnd);
        break;

      case 1:
        lcd.print(WiFi.localIP().toString());
        break;
    }
  }
}

void handleRoot() {
  int i;
  if (server.hasArg("begin") && server.arg("begin").toInt() >= 0 && server.arg("begin").toInt() <= 24 && server.hasArg("end") && server.arg("end").toInt() >= 0 && server.arg("end").toInt() <= 24 && server.hasArg("dusk") && server.arg("dusk").toInt() >= 0 && server.arg("dusk").toInt() <= 255)
  {
    tmBegin = server.arg("begin").toInt();
    tmEnd = server.arg("end").toInt();
    dusk = server.arg("dusk").toInt();
    EEPROM.write(0, tmBegin);
    EEPROM.write(1, tmEnd);
    EEPROM.write(2, dusk);
    if (server.hasArg("pumpBegin") && server.arg("pumpBegin").toInt() >= 0 && server.arg("pumpBegin").toInt() <= 24 && server.hasArg("pumpEnd") && server.arg("pumpEnd").toInt() >= 0 && server.arg("pumpEnd").toInt() <= 24)
    {
      pumpBegin = server.arg("pumpBegin").toInt();
      pumpEnd = server.arg("pumpEnd").toInt();
      EEPROM.write(3, pumpBegin);
      EEPROM.write(4, pumpEnd);
    }

    //  if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.hasArg("tempMax") && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt())
    if (server.arg("tempMin").toInt() >= 0 && server.arg("tempMin").toInt() <= 100 && server.arg("tempMax").toInt() >= 0 && server.arg("tempMax").toInt() <= 100 && server.arg("tempMax").toInt() > server.arg("tempMin").toInt())
    {
      tempAlarm = server.arg("tempAlarm").toInt();
      tempMin = server.arg("tempMin").toInt();
      tempMax = server.arg("tempMax").toInt();
      EEPROM.write(5, tempAlarm);
      EEPROM.write(6, tempMin);
      EEPROM.write(7, tempMax);
    }
    EEPROM.commit();
  }

  if (server.hasArg("SSID") && server.arg("SSID") != "" && server.hasArg("wifipass") && 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();
    ESP.restart();
  }
  String content = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Aquarium controller</title></head><body>";
  /*
    for (i = 2; i < 100; i++)
    {
    if (EEPROM.read(i) == 5) {
      break;
    }
    content += char(EEPROM.read(i));
    }
    content += ";";
    for (int ii = i + 1; ii < 150; ii++)
    {
    if (EEPROM.read(ii) == 5) {
      break;
    }
    content += char(EEPROM.read(ii));
    }
  */
  content += "<DIV style=\"display:table; font-size: large; border-style: solid;\">Current time: <span id=\"time\"></span><BR>Temperature: <span id=\"temp\"></span>°C<BR>PWM: <span id=\"PWM\"></span>%<br>";
  // content += "<div id=\"refresh\"> </div><script>var myVar = setInterval(myTimer, 1000); function myTimer() {var xhttp = new XMLHttpRequest();  xhttp.onreadystatechange = function() {  if (this.readyState == 4 && this.status == 200) {  document.getElementById(\"refresh\").innerHTML =     this.responseText;}  };  xhttp.open(\"GET\", \"sensors\", true);  xhttp.send();}</script>";
  content += "\"Artificial day\" from " + String((int)tmBegin) + ":00 to " + String((int)tmEnd) + ":00. Dusk duration: " + String((int)dusk) + " minutes.</DIV>";
  content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);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(\"time\").innerHTML = values[0]; document.getElementById(\"temp\").innerHTML = values[1]; document.getElementById(\"PWM\").innerHTML = values[2]; } }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}  var i = 0;</script>";
  content += "<form action='/' method='POST'>Begin: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='begin' value='" + String((int)tmBegin) + "'> UTC<br>";
  content += "End: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='end' value='" + String((int)tmEnd) + "'> UTC<br>";
  content += "Dusk: <input type='number' pattern='[0-9]' maxlength='2' size='2' name='dusk' value='" + String((int)dusk) + "'> minutes<br>";
  content += "Air pump operates between <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpBegin' value='" + String((int)pumpBegin) + "'> and <input type='number' pattern='[0-9]' maxlength='2' size='2' name='pumpEnd' value='" + String((int)pumpEnd) + "'><br>";
  content += "<input type=\"checkbox\" name=\"tempAlarm\" value=\"1\"";
  if (tempAlarm) {
    content += " checked";
  }
  content += ">TempAlarm <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMin' value='" + String((int)tempMin) + "'> - <input type='number' pattern='[0-9]' maxlength='2' size='2' name='tempMax' value='" + String((int)tempMax) + "'><br>";
  content += "<a href=\"\" onclick=\"i = i+1; if(i % 2 == 0) {wifi.style.display = 'none';} else  {wifi.style.display = 'block';} return false;\">Change WiFi settings</a>";
  content += "<div  id=\"wifi\" style=\"display:none;\">Home WiFi Network Name (SSID): <input type='text' name='SSID' value='" + String(ssid) + "'><br>";
  content += "Password: <input type='password' name='wifipass'></div><br>";
  content += "<input type='submit' value='Submit'></form> <br>";
  content += "</body></html>";
  server.send(200, "text/html", content);
}

void handleSensors() {
  String content = current_time(0) + ";" + String(temp) + ";" + String(100.0 * PWM / 1023.0);
  server.send(200, "text/html", content);
}

String current_time(bool shortstring) {
  breakTime(now(), tm);
  String timeStr;
  if (!shortstring) {
    if (tm.Year > 0) {
      tm.Year -= 30;
    } else {
      tm.Year = 0;
    }
  }
  timeStr = String(tm.Day) + "." + String(tm.Month);
  if (!shortstring) {
    timeStr += "." + String(tm.Year);
  }
  timeStr += " " + String(tm.Hour) + ":";
  if (tm.Minute < 10) {
    timeStr += "0";
  }
  timeStr += String(tm.Minute) + ":";
  if (tm.Second < 10) {
    timeStr += "0";
  }
  timeStr += String(tm.Second) + "UTC";

  return timeStr;
}
// send an NTP request to the time server at the given address
//https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/NTPClient/NTPClient.ino
unsigned long sendNTPpacket(IPAddress& address)
{
  //Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

time_t unix_time() {
  if (WiFi.status() == WL_CONNECTED && ((unsigned long)(millis() - last_NTP_check) > 15000 || last_time_sync == 0)) {
    last_NTP_check = millis();
    //get a random server from the pool
    WiFi.hostByName(ntpServerName, timeServerIP);

    sendNTPpacket(timeServerIP); // send an NTP packet to a time server
    // wait to see if a reply is available
    delay(1000);

    int cb = udp.parsePacket();
    if (!cb) {
      //Serial.println("no packet yet");
    }
    else {
      //Serial.print("packet received, length=");
      //Serial.println(cb);
      // We've received a packet, read the data from it
      udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

      //the timestamp starts at byte 40 of the received packet and is four bytes,
      // or two words, long. First, esxtract the two words:

      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
      // combine the four bytes (two words) into a long integer
      // this is NTP time (seconds since Jan 1 1900):
      unsigned long secsSince1900 = highWord << 16 | lowWord;
      //Serial.print("Seconds since Jan 1 1900 = " );
      //Serial.println(secsSince1900);

      // now convert NTP time into everyday time:
      Serial.println("Unix time sync ");
      // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
      const unsigned long seventyYears = 2208988800UL;
      // subtract seventy years:
      unsigned long epoch = secsSince1900 - seventyYears;
      // print Unix time:
      if (epoch > 1512876158) {
        return epoch + 2;
      } else
      {
        return 0;
      }
    }
  }
}

Składniki:

IMG_20171214_202914.thumb.jpg.a8bd2f2749c68d55c8b6cb63c8b33559.jpg

Edytowano przez rziomber
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Właśnie zaakceptowałem Twój opis, możesz go teraz zgłosić do akcji rabatowej umieszczając link w temacie zbiorczym. Dziękuję za przedstawienie ciekawego projektu, zachęcam do prezentowania kolejnych DIY oraz aktywności na naszym forum 🙂

Udostępnij ten post


Link to post
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »

×