UPD. В ходе эксплуатации добавил отдельное питание для шагового двигателя, кормление по расписанию и ребут раз в сутки.
UPD2. Добавлен еще один двигатель для перемешивания корма. Как показала практика корм вокруг сверла слеживается и со временем в аквариум за единицу времени начинает сыпаться существено меньше корма.
-----
Некоторое время назад нам Андрей презентовал аквариум с гуппи и одним сомиком.
Через некоторое время население аквариума выросло, появились растения, в общем образовался новый чудесный минимир.
Всё бы хорошо, да вот системе крайне полезна регулярность: в одно время включать/выключать свет, кормить рыбок.
Тут и появилась идея автоматизировать аквариум.
Вот что из этого получилось:
Фукциональность:
- веб-интерфейс
- включение/выключение фильтра
- включение/выключение помпы
- автовключение фильтра и помпы
- включение/выключение освещения
- автовключение/автовыключение освещения по расписанию
- включение/выключение кормушки
- автовключение/автовыключение кормушки по расписанию
- контроль температуры
- автовключение/автовыключение охлаждения
- синхронизация времени
- LCD дисплей с тачскрином (не реализовано)
В качестве мозга системы взял Arduino Uno + ethernet shield.
Через некоторое время пришлось сменить на Ardiuno Mega 2560, так как скетч не помешался в памяти.
Итоговые комплектующие:
- Arduino Mega 2560 R3
- Ethernet Shield
- Цифровой температурный датчик DS18B20 c интерфейсом Dallas 1-Wire, влагозащищенный
- 2x Шаговый двигатель на 5В (28BYJ48) + драйвер (ULN2003)
- Четырехканальный 5В реле-модуль для Arduino
- 2x Блок питания 5V
- Блок питания 12V
- Вентилятор 80мм от системного блока
- Пластиковая коробка, куски пластика
- Сверло 8мм, 12см
- Дюбель 6мм
Корпус для кормушки я сделал основываясь на: http://rukobludov.net/%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F-%D0%BA%D0%BE%D1%80%D0%BC%D1%83%D1%88%D0%BA%D0%B0-%D0%B4%D0%BB%D1%8F-%D1%80%D1%8B%D0%B1/
Куски кода компоновал из исходников из разных источников. Прошу прощения у авторов, что не указал их здесь.
Пины 1-4 реле подключены к пинам 5-8
DS18B20: красный провод - +5В, черный - GND, желтый - пин 9. Между черным и желтым проводом резистор на 4,7кОм.
Пины 1-4 драйвера шагового двигателя подключены к пинам 40-43
Пины 1-4 драйвера второго шагового двигателя (перемешивание) подключены к пинам 44-47
В сетевой фильтр включено: блок питания 5В с usb разъемом, блок питания 5В, блок питания 12В, просто вилка с проводом.
В реле 1,2,4 подключен один провод 220В, он подключен в центральную клемму.
От розеток 1,2 провода в реле 1,2 подключены в нормальнозамкнутом состоянии, то есть в такие клеммы, чтобы питание устройств 220В проходило при выключенном управлении (отсутствии питания от arduino на реле).
В первую розетку подключается фильтр, во вторую - помпу.
В розетку 3 включаем освещение.
От розетки 3 в реле 4 провод подключен в клемму в нормальноразомкнутом состоянии, по умолчанию освещение выключено.
Через реле 3 идёт питание кулера. В него идёт провод от БП 12В опять же в нормальноразомкнутом состоянии.
Фотографии:
После UPD:
ToDo
- Добавить возможность вводить время включения/выключения света
- Добавить возможность вводить время кормления
- Добавить сенсорный экран для вывода информации и управление
Скетч:
#include <Stepper.h>
#include <SD.h>
#include <OneWire.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
//#include <EEPROM.h>
#define stepsPerMotorRevolution 32
#define stepsPerOutputRevolution 32 * 64 //2048
#define motorSpeed 800
#define motor2Speed 200
#define motorPin1 40
#define motorPin2 41
#define motorPin3 42
#define motorPin4 43
#define motor2Pin1 44
#define motor2Pin2 45
#define motor2Pin3 46
#define motor2Pin4 47
#define dsPin 9 // DS18 Pin
#define ethPin 10
#define SSPin 53
#define SDPin 4
#define filterPin 5
#define airPin 6
#define coolerPin 7
#define lightPin 8
Stepper stepper(stepsPerMotorRevolution, motorPin1, motorPin2, motorPin3, motorPin4);
Stepper stepper2(stepsPerMotorRevolution, motor2Pin1, motor2Pin2, motor2Pin3, motor2Pin4);
OneWire ds(dsPin); //
float celsius, fahrenheit;
int randNumber;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x16 }; // the media access control (ethernet hardware) address for the shield
byte ip[] = { 192, 168, 18, 116 }; //the IP address for the shield
byte gateway[] = { 192, 168, 18, 1 };
byte subnet[] = { 255, 255, 255, 0 };
EthernetServer server(80);
//IPAddress timeServer1(88, 147, 254, 235);
//IPAddress timeServer2(91, 226, 136, 155);
//IPAddress timeServer3(88, 147, 254, 234);
IPAddress timeServer(192, 168, 18, 1);
const long timeZoneOffset = 10800L;
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
EthernetUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
boolean NTPSync = false;
const long NTPIntv = 3600000;
const long NTPRepIntv = 60000;
const long filterIntv = 600000; // Max delay in Filter
const long airIntv = 600000; // Max delay in Air
const long lightIntv = 60000; // interval to checking light
const long del = 100000;
long currentTime = 0;
long NTPSyncTime = 0;
long epoch = 0;
long prvLight = 0; // Last check time. Light
long prvNTPSync = 0; // Last NTP sync time
//long prvNTPsend = 0; // Last NTP sync time
long filterSwOffTime = 0; // When filter switch off
long airSwOffTime = 0; // When air switch off
const long softResetIntv = 86400000;
Sd2Card card;
SdVolume volume;
SdFile root;
File myFile;
String buttonsNames[9] = {};
char Filename[15] = "epoch.txt";
int upLightTime = 8; // switch on (hour)
int downLightTime = 18; // switch off (hour)
int isNight = 0; // in switch on at night
boolean lightTimer = true;
boolean feederTimer = true;
boolean feederMustStop = true;
boolean feederON = false;
boolean feeder2Timer = true;
boolean feeder2MustStop = true;
boolean feeder2ON = false;
int feederOnTime[2] = {9, 12}; // time to switch on feeder
int feederOnIntv = 5; // period in minutes
int feeder2OnTime[2] = {8, 11}; // time to switch on feeder
int feeder2OnIntv = 10; // period in minutes
int minTemp = 23; // Switch off Cooler
int maxTemp = 26; // Switch on Cooler
//The number of outputs going to be switched.
int outputQuantity = 4; //when added to outputLowest result should not exceed 10
//The lowest output pin we are starting from
int outputLowest = 5; //Should be between 2 to 9
// Variable declaration
int outp = 0;
boolean initialPrint = true;
boolean reading = false;
boolean readInput[10]; //Create a boolean array for the maximum ammount.
void setup()
{
randomSeed(analogRead(0));
//Set pins as Outputs
for (int var = outputLowest; var < outputLowest + outputQuantity; var++) {
pinMode(var, OUTPUT);
digitalWrite(var, 1);
}
pinMode(ethPin, OUTPUT);
pinMode(SSPin, OUTPUT);
digitalWrite(ethPin, HIGH);
//digitalWrite(SDPin, HIGH);
//digitalWrite(ethPin, LOW);
Serial.begin(9600);
//SD.begin(SDPin);
if (!SD.begin(SDPin)) {
Serial.println("SD initialization failed!");
//return;
}
if (!volume.init(card)) {
Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
//return;
}
Serial.println("\nFiles found on the card (name, date and size in bytes): ");
root.openRoot(volume);
root.ls(LS_R | LS_DATE | LS_SIZE);
//myFile = SD.open("epoch.txt", FILE_WRITE);
// print the type and size of the first FAT-type volume
//uint32_t volumesize;
//Serial.print("\nVolume type is FAT");
//Serial.println(volume.fatType(), DEC);
//Serial.println();
//volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
//volumesize *= volume.clusterCount(); // we'll have a lot of clusters
//volumesize *= 512; // SD card blocks are always 512 bytes
//Serial.print("Volume size (bytes): ");
//Serial.println(volumesize);
//Serial.print("Volume size (Kbytes): ");
//volumesize /= 1024;
//Serial.println(volumesize);
//Serial.print("Volume size (Mbytes): ");
//volumesize /= 1024;
//Serial.println(volumesize);
buttonsNames[5] = "Filter";
buttonsNames[6] = "Air";
buttonsNames[7] = "Cooler";
buttonsNames[8] = "Lamp";
currentTime = millis();
if (SD.exists(Filename)) {
myFile = SD.open(Filename);
if (myFile) {
while (myFile.available()) {
epoch = myFile.read();
Serial.print("Epoch: ");
Serial.println(epoch);
}
myFile.close();
}
} else {
myFile = SD.open(Filename,FILE_WRITE);
myFile.close();
if (SD.exists(Filename)) {
Serial.print(Filename);
Serial.println(" created");
} else {
Serial.print(Filename);
Serial.println(" did not create");
}
}
//epoch = EEPROM.read(301);
Ethernet.begin(mac, ip, gateway, subnet);
server.begin();
Udp.begin(localPort);
EthernetReset();
stepper.setSpeed(motorSpeed); // set the speed of the motor
stepper2.setSpeed(motor2Speed); // set the speed of the motor
Serial.println(F("Setup ready!"));
}
#define BUFSIZ 100 // Buffer for filename
void loop()
{
currentTime = millis();
if (currentTime % del == 0 ) {
Serial.print("currentTime: ");
Serial.print(currentTime);
Serial.println(" ");
}
if (currentTime > softResetIntv) softReset();
if (currentTime < prvNTPSync ) prvNTPSync = currentTime; // 00:00:00
if (currentTime < prvLight ) prvLight = currentTime; // 00:00:00
if ((currentTime - prvNTPSync >= NTPRepIntv) && (NTPSync == false || currentTime - NTPSyncTime >= NTPIntv)) {
//if (NTPSync == false && currentTime - NTPSyncTime >= NTPIntv) {
//EthernetReset();
//}
Serial.print("currentTime: ");
Serial.print(currentTime);
Serial.println(" ");
Serial.print("prvNTPSync: ");
Serial.print(prvNTPSync);
Serial.println(" ");
//Serial.print("currentTime - prvNTPSync: ");
//Serial.print((currentTime - prvNTPSync));
//Serial.println(" ");
//Serial.print("NTPSync: ");
//Serial.print(NTPSync);
//Serial.println(" ");
Serial.print("NTPSyncTime: ");
Serial.print(NTPSyncTime);
Serial.println(" ");
//Serial.print("currentTime - NTPSyncTime: ");
//Serial.print((currentTime - NTPSyncTime));
//Serial.println(" ");
//Serial.print("NTPIntv: ");
//Serial.print(NTPRepIntv);
//Serial.println(" ");
//randNumber = random(1,4);
//Serial.println(randNumber);
//if(randNumber == 1){sendNTPpacket(timeServer1);} // send an NTP packet to a time server
//if(randNumber == 2){sendNTPpacket(timeServer2);} // send an NTP packet to a time server
//if(randNumber == 3){sendNTPpacket(timeServer3);} // send an NTP packet to a time server
prvNTPSync = currentTime;
sendNTPpacket(timeServer);
}
readOutputStatuses(); //Refresh the reading of outputs
syncTime();
checkTemp();
checkForClient(); // listen for incoming clients, and process requests.
switchCooler();
switchFeeder(1);
if (lightTimer == true) {
if (currentTime - prvLight > lightIntv) {
prvLight = currentTime;
switchLight();
}
}
}
void checkTemp() {
byte i;
byte type_s;
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
//Serial.println("No more addresses.");
//Serial.println();
ds.reset_search();
//delay(250);
return;
}
if (OneWire::crc8(addr, 7) != addr[7]) {
//Serial.println("CRC is not valid!");
return;
}
Serial.println();
switch (addr[0]) {
case 0x10:
//Serial.println(" Chip = DS18S20");
type_s = 1;
break;
case 0x28:
//Serial.println(" Chip = DS18B20");
type_s = 0;
break;
case 0x22:
//Serial.println(" Chip = DS1822");
type_s = 0;
break;
default:
//Serial.println("Device is not a DS18x20 family device.");
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1);
//delay(1000);
ds.reset();
ds.select(addr);
ds.write(0xBE);
for ( i = 0; i < 9; i++) {
data[i] = ds.read();
}
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3;
if (data[7] == 0x10) {
raw = (raw & 0xFFF0) + 12 - data[6];
}
}
else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw & ~7;
else if (cfg == 0x20) raw = raw & ~3;
else if (cfg == 0x40) raw = raw & ~1;
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
}
void checkForClient() {
char clientline[BUFSIZ];
char *filename;
int index = 0;
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
boolean currentLineIsBlank = true;
boolean sentHeader = false;
while (client.connected()) {
if (client.available()) {
if (!sentHeader) {
// send a standard http response header
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connnection: close"));
client.println();
client.println(F("<!DOCTYPE HTML>"));
client.println(F("<head>"));
// add page title
client.println(F("<title>Aquarium's Control</title>"));
client.println(F("<meta name=\"description\" content=\"Aquarium's Control\"/>"));
client.println(F("<meta content=\"text/html; charset=UTF-8\" http-equiv=\"content-type\">"));
// add a meta refresh tag, so the browser pulls again every 10 seconds:
client.println(F("<meta http-equiv=\"refresh\" content=\"20; url=/\">"));
// add other browser configuration
client.println(F("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">"));
client.println(F("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"default\">"));
client.println(F("<meta name=\"viewport\" content=\"width=device-width, user-scalable=no\"/>"));
//inserting the styles data, usually found in CSS files.
client.println(F("<style type=\"text/css\">"));
client.println(F(""));
//This will set how the page will look graphically
client.println(F("html { height:100%; }"));
client.println(F("body {"));
client.println(F(" height: 100%;"));
client.println(F(" margin: 0;"));
client.println(F(" font-family: helvetica, sans-serif;"));
client.println(F(" -webkit-text-size-adjust: none;"));
client.println(F(" }"));
client.println(F(""));
client.println(F("body {"));
client.println(F(" -webkit-background-size: 100% 21px;"));
client.println(F(" background-color: #c5ccd3;"));
client.println(F(" background-image:"));
client.println(F(" -webkit-gradient(linear, left top, right top,"));
client.println(F(" color-stop(.75, transparent),"));
client.println(F(" color-stop(.75, rgba(255,255,255,.1)) );"));
client.println(F(" -webkit-background-size: 7px;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".button { width:150px; }"));
client.println(F(""));
client.println(F(".view {"));
client.println(F(" min-height: 100%;"));
client.println(F(" overflow: auto;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".header-wrapper {"));
client.println(F(" height: 44px;"));
client.println(F(" font-weight: bold;"));
client.println(F(" text-shadow: rgba(0,0,0,0.7) 0 -1px 0;"));
client.println(F(" border-top: solid 1px rgba(255,255,255,0.6);"));
client.println(F(" border-bottom: solid 1px rgba(0,0,0,0.6);"));
client.println(F(" color: #fff;"));
client.println(F(" background-color: #8195af;"));
client.println(F(" background-image:"));
client.println(F(" -webkit-gradient(linear, left top, left bottom,"));
client.println(F(" from(rgba(255,255,255,.4)),"));
client.println(F(" to(rgba(255,255,255,.05)) ),"));
client.println(F(" -webkit-gradient(linear, left top, left bottom,"));
client.println(F(" from(transparent),"));
client.println(F(" to(rgba(0,0,64,.1)) );"));
client.println(F(" background-repeat: no-repeat;"));
client.println(F(" background-position: top left, bottom left;"));
client.println(F(" -webkit-background-size: 100% 21px, 100% 22px;"));
client.println(F(" -webkit-box-sizing: border-box;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".header-wrapper h1 {"));
client.println(F(" text-align: center;"));
client.println(F(" font-size: 20px;"));
client.println(F(" line-height: 44px;"));
client.println(F(" margin: 0;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".group-wrapper {"));
client.println(F(" margin: 9px;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".group-wrapper h2 {"));
client.println(F(" text-align: center;"));
client.println(F(" color: #4c566c;"));
client.println(F(" font-size: 17px;"));
client.println(F(" line-height: 0.8;"));
client.println(F(" font-weight: bold;"));
client.println(F(" text-shadow: #fff 0 1px 0;"));
client.println(F(" margin: 20px 10px 12px;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".group-wrapper h3 {"));
client.println(F(" color: #4c566c;"));
client.println(F(" font-size: 12px;"));
client.println(F(" line-height: 1;"));
client.println(F(" font-weight: bold;"));
client.println(F(" text-shadow: #fff 0 1px 0;"));
client.println(F(" margin: 20px 10px 12px;"));
client.println(F(" }"));
client.println(F(""));
client.println(F(".group-wrapper table {"));
client.println(F(" background-color: #fff;"));
client.println(F(" -webkit-border-radius: 10px;"));
client.println(F(" -moz-border-radius: 10px;"));
client.println(F(" -khtml-border-radius: 10px;"));
client.println(F(" border-radius: 10px;"));
client.println(F(" font-size: 17px;"));
client.println(F(" line-height: 20px;"));
client.println(F(" margin: 9px 0 20px;"));
client.println(F(" border: solid 1px #a9abae;"));
client.println(F(" padding: 11px 3px 12px 3px;"));
client.println(F(" margin-left:auto;"));
client.println(F(" margin-right:auto;"));
client.println(F(" -moz-transform :scale(1);")); //Code for Mozilla Firefox
client.println(F(" -moz-transform-origin: 0 0;"));
client.println(F(" }"));
client.println(F(""));
//how the green (ON) LED will look
client.println(F(".green-circle {"));
client.println(F(" display: block;"));
client.println(F(" height: 23px;"));
client.println(F(" width: 23px;"));
client.println(F(" background-color: #0f0;"));
//client.println(F(" background-color: rgba(60, 132, 198, 0.8);"));
client.println(F(" -moz-border-radius: 11px;"));
client.println(F(" -webkit-border-radius: 11px;"));
client.println(F(" -khtml-border-radius: 11px;"));
client.println(F(" border-radius: 11px;"));
client.println(F(" margin-left: 1px;"));
client.println(F(" background-image: -webkit-gradient(linear, 0% 0%, 0% 90%, from(rgba(46, 184, 0, 0.8)), to(rgba(148, 255, 112, .9)));@"));
client.println(F(" border: 2px solid #ccc;"));
client.println(F(" -webkit-box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px;"));
client.println(F(" -moz-box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px; /* FF 3.5+ */"));
client.println(F(" box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px; /* FF 3.5+ */"));
client.println(F(" }"));
client.println(F(""));
//how the black (off)LED will look
client.println(F(".black-circle {"));
client.println(F(" display: block;"));
client.println(F(" height: 23px;"));
client.println(F(" width: 23px;"));
client.println(F(" background-color: #040;"));
client.println(F(" -moz-border-radius: 11px;"));
client.println(F(" -webkit-border-radius: 11px;"));
client.println(F(" -khtml-border-radius: 11px;"));
client.println(F(" border-radius: 11px;"));
client.println(F(" margin-left: 1px;"));
client.println(F(" -webkit-box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px;"));
client.println(F(" -moz-box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px; /* FF 3.5+ */"));
client.println(F(" box-shadow: rgba(11, 140, 27, 0.5) 0px 10px 16px; /* FF 3.5+ */"));
client.println(F(" }"));
client.println(F(""));
//this will add the glare to both of the LEDs
client.println(F(" .glare {"));
client.println(F(" position: relative;"));
client.println(F(" top: 1;"));
client.println(F(" left: 5px;"));
client.println(F(" -webkit-border-radius: 10px;"));
client.println(F(" -moz-border-radius: 10px;"));
client.println(F(" -khtml-border-radius: 10px;"));
client.println(F(" border-radius: 10px;"));
client.println(F(" height: 1px;"));
client.println(F(" width: 13px;"));
client.println(F(" padding: 5px 0;"));
client.println(F(" background-color: rgba(200, 200, 200, 0.25);"));
client.println(F(" background-image: -webkit-gradient(linear, 0% 0%, 0% 95%, from(rgba(255, 255, 255, 0.7)), to(rgba(255, 255, 255, 0)));"));
client.println(F(" }"));
client.println(F(""));
//and finally this is the end of the style data and header
client.println(F("</style>"));
client.println(F("</head>"));
//now printing the page itself
client.println(F("<body>"));
client.println(F("<div class=\"view\">"));
client.println(F(" <div class=\"header-wrapper\">"));
client.println(F(" <h1>Aquarium's Control</h1>"));
client.println(F(" </div>"));
client.println(F("<div class=\"group-wrapper\">"));
client.print(F("<h2>Time: "));
client.print(((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 86400L) / 3600);
client.print(F(":"));
client.print(((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 3600) / 60);
client.print(F(":"));
client.print((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 60);
client.println(F("</h2>"));
client.print(F("<h2>NTP last sync: "));
client.print(((epoch + NTPSyncTime / 1000) % 86400L) / 3600);
client.print(F(":"));
client.print(((epoch + NTPSyncTime / 1000) % 3600) / 60);
client.print(F(":"));
client.print((epoch + NTPSyncTime / 1000) % 60);
client.println(F("</h2>"));
client.print(F("<h2>Temperature: "));
client.print(celsius);
client.print(F("C</h2>"));
client.println(F(" </div>"));
client.println();
//This is for the arduino to construct the page on the fly.
sentHeader = true;
}
char c = client.read();
if (reading && c == ' ') {
reading = false;
}
if (c == '?') {
reading = true; //found the ?, begin reading the info
}
if (reading) {
if (c == 'A') {
lightTimer = true;
}
if (c == 'B') {
lightTimer = false;
//Serial.println("false");
}
if (c == 'C') {
feederTimer = true;
}
if (c == 'D') {
feederTimer = false;
}
if (c == 'E') {
feederMustStop = false;
feederON = true;
}
if (c == 'F') {
feederMustStop = true;
feederON = false;
}
if (c == 'G') {
outp = 0;
}
if (c == 'H') {
outp = 1;
}
if (c == 'J') {
feeder2Timer = true;
}
if (c == 'K') {
feeder2Timer = false;
}
if (c == 'L') {
feeder2MustStop = false;
feeder2ON = true;
}
if (c == 'M') {
feeder2MustStop = true;
feeder2ON = false;
}
Serial.print(c); //print the value of c to serial communication
//Serial.print(outp);
//Serial.print('\n');
switch (c) {
case '2':
//add code here to trigger on 2
triggerPin(2, client, outp);
break;
case '3':
//add code here to trigger on 3
triggerPin(3, client, outp);
break;
case '4':
//add code here to trigger on 4
triggerPin(4, client, outp);
break;
case '5':
//add code here to trigger on 5
triggerPin(5, client, outp);
//printHtml(client);
break;
case '6':
//add code here to trigger on 6
triggerPin(6, client, outp);
break;
case '7':
//add code here to trigger on 7
triggerPin(7, client, outp);
break;
case '8':
//add code here to trigger on 8
triggerPin(8, client, outp);
break;
case '9':
//add code here to trigger on 9
triggerPin(9, client, outp);
break;
}
}
if (c == '\n' && currentLineIsBlank) {
readOutputStatuses(); //Refresh the reading of outputs
printHtmlButtons(client);
Serial.println("");
break;
}
}
}
client.println(F("</div>\n</div>\n</body>\n</html>"));
delay(1); // give the web browser time to receive the data
client.stop(); // close the connection:
}
}
void triggerPin(int pin, EthernetClient client, int outp) {
//Switching on or off outputs
if (outp == 1) {
digitalWrite(pin, HIGH);
}
if (outp == 0) {
digitalWrite(pin, LOW);
}
if (pin == lightPin) {
lightTimer = false;
Serial.println("false");
}
if (pin == filterPin) {
filterSwOffTime = currentTime;
}
if (pin == airPin) {
airSwOffTime = currentTime;
}
//Serial.println(pin);
}
//print the html buttons to switch on/off channels
void printHtmlButtons(EthernetClient client) {
//Start to create the html table
client.println("");
client.println(F("<FORM>"));
client.println(F("<table border=\"0\" align=\"center\">"));
//Start printing button by button
for (int var = outputLowest; var < outputLowest + outputQuantity; var++) {
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - "));
client.print(buttonsNames[var]);
if (var == filterPin || var == airPin) {
client.print(F("\" onClick=\"parent.location='/?H"));
} else {
client.print(F("\" onClick=\"parent.location='/?G"));
}
client.print(var);
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - "));
client.print(buttonsNames[var]);
if (var == filterPin || var == airPin) {
client.print(F("\" onClick=\"parent.location='/?G"));
} else {
client.print(F("\" onClick=\"parent.location='/?H"));
}
client.print(var);
client.print(F("'\"></td>\n"));
if (var == filterPin || var == airPin) {
//Print first part of the Circles or the LEDs
if (readInput[var] == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
} else {
//Print first part of the Circles or the LEDs
if (readInput[var] == false) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
}
//Print end of row
client.print("</tr>\n");
}
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - Light Timer"));
client.print(F("\" onClick=\"parent.location='/?A"));
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - Light Timer"));
client.print(F("\" onClick=\"parent.location='/?B"));
client.print(F("'\"></td>\n"));
//Print first part of the Circles or the LEDs
if (lightTimer == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
//Print end of row
client.print("</tr>\n");
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - Feeder"));
client.print(F("\" onClick=\"parent.location='/?E"));
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - Feeder"));
client.print(F("\" onClick=\"parent.location='/?F"));
client.print(F("'\"></td>\n"));
//Print first part of the Circles or the LEDs
if (feederON == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
//Print end of row
client.print("</tr>\n");
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - Feeder Timer"));
client.print(F("\" onClick=\"parent.location='/?C"));
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - Feeder Timer"));
client.print(F("\" onClick=\"parent.location='/?D"));
client.print(F("'\"></td>\n"));
//Print first part of the Circles or the LEDs
if (feederTimer == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
//Print end of row
client.print("</tr>\n");
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - Feeder2"));
client.print(F("\" onClick=\"parent.location='/?L"));
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - Feeder2"));
client.print(F("\" onClick=\"parent.location='/?M"));
client.print(F("'\"></td>\n"));
//Print first part of the Circles or the LEDs
if (feeder2ON == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
//Print end of row
client.print("</tr>\n");
//Print begining of row
client.print("<tr>\n");
//Prints the ON Buttons
client.print(F(" <td><INPUT class='button' id='on' TYPE=\"button\" VALUE=\"ON - Feeder 2 Timer"));
client.print(F("\" onClick=\"parent.location='/?J"));
client.print(F("'\"></td>\n"));
//Prints the OFF Buttons
client.print(F(" <td><INPUT class='button' id='off' TYPE=\"button\" VALUE=\"OFF - Feeder 2 Timer"));
client.print(F("\" onClick=\"parent.location='/?K"));
client.print(F("'\"></td>\n"));
//Print first part of the Circles or the LEDs
if (feeder2Timer == true) {
client.print(F(" <td><div class='green-circle'><div class='glare'></div></div></td>\n"));
} else {
client.print(F(" <td><div class='black-circle'><div class='glare'></div></div></td>\n"));
}
//Print end of row
client.print("</tr>\n");
//Closing the table and form
client.println(F("</table>"));
client.println(F("</FORM>"));
}
//Reading the Output Statuses
void readOutputStatuses() {
for (int var = outputLowest; var < outputLowest + outputQuantity; var++) {
readInput[var] = digitalRead(var);
}
}
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
Serial.println(F("NTPpacket sending"));
//Ethernet.maintain();
// 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();
delay(1);
//Udp.stop();
Serial.println(F("NTPpacket sended"));
}
void syncTime() {
if ( Udp.parsePacket() ) {
Serial.println(F("UDP packet received"));
// 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;
// now convert NTP time into everyday time:
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
epoch = secsSince1900 - seventyYears + timeZoneOffset;
//Serial.println(epoch);
NTPSyncTime = currentTime;
NTPSync = true;
Serial.print(F("Time: "));
Serial.print(((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 86400L) / 3600);
Serial.print(F(":"));
Serial.print(((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 3600) / 60);
Serial.print(F(":"));
Serial.println((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 60);
EthernetReset();
}
}
void EthernetReset() {
//Udp.stop();
//Ethernet.begin(mac, ip, gateway, subnet);
//server.begin();
//Udp.begin(localPort);
// 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;
}
void switchFilter () { // Switch ON Filter
if (currentTime > filterIntv + filterSwOffTime) {
digitalWrite(filterPin, HIGH);
}
}
void switchAir () { // Switch ON Air
if (currentTime > airIntv + airSwOffTime) {
digitalWrite(airPin, HIGH);
}
}
void switchCooler () { // Switch ON/OFF Cooler
if (celsius > maxTemp) {
digitalWrite(coolerPin, LOW);
}
if (celsius < minTemp) {
digitalWrite(coolerPin, HIGH);
}
}
void switchFeeder (float num) { // Switch ON/OFF Feeder
int hr = (((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 86400L) / 3600);
int m = (((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 3600) / 60);
int stepsNum = stepsPerOutputRevolution * num;
if ((hr == feederOnTime[0] || hr == feederOnTime[1]) && m >= 0 && m < feederOnIntv) { // check interval
feederON = true;
} else {
if (feederMustStop == true) {
feederON = false;
}
}
if (feederON == true) {
//Serial.println(F("Feeder ON"));
stepper.step(-stepsNum);
}
if ((hr == feeder2OnTime[0] || hr == feeder2OnTime[1]) && m >= 0 && m < feeder2OnIntv) { // check interval
feeder2ON = true;
} else {
if (feeder2MustStop == true) {
feeder2ON = false;
}
}
if (feeder2ON == true) {
//Serial.println(F("Feeder ON"));
stepper2.step(-stepsNum);
}
}
void switchLight () { // Switch ON/OFF light
//Serial.println(F("switchLight"));
int hr = (((epoch + currentTime / 1000 - NTPSyncTime / 1000) % 86400L) / 3600);
//Serial.println(hr);
int isLght = 0;
if (isNight == 0) { // if day
if (hr >= upLightTime && hr < downLightTime) { // check interval
isLght = 1;
} else {
isLght = 0;
}
} else { // if night
if (hr - upLightTime >= 0) {
isLght = 1;
} else {
if (hr < downLightTime) {
isLght = 1;
} else {
isLght = 0;
}
}
}
if ((isLght == 1) && (readInput[lightPin] == 1)) {
digitalWrite(lightPin, LOW);
}
if (isLght == 0 && readInput[lightPin] == 0) {
digitalWrite(lightPin, HIGH);
}
}
void softReset() {
//Serial.print("currentTime: ");
//Serial.print(currentTime);
//Serial.println(" ");
//Serial.print("softResetIntv: ");
//Serial.print(softResetIntv);
//Serial.println(" ");
//Serial.println(F("softReset"));
//EEPROM.write(301, epoch + currentTime / 1000 - NTPSyncTime / 1000);
//SD.remove(Filename);
myFile = SD.open(Filename, FILE_WRITE);
if (myFile) {
myFile.println(epoch);
myFile.close();
} else {
Serial.println(F("Cannot open file"));
}
delay(100);
asm volatile (" jmp 0");
}