/* arduino mega 2560
ISCP пины
MOSI - pin 16
MISO - pin 17
CLK (SCK) - pin 15
*****
enc28j60 подключается к ISCP пинам,
пин CS определен как ETH_CS, по умолчанию 10 (меняем на 2).
*****
MicroSD Card Adapter v1.0 11/01/2013 подключается к ISCP пинам,
Питание 5v.
Пин CS определен как SD_CS 3
*****
модуль часов реального времени DS3231M
подключается к пинам SDA, SCL (43, 44)
Питание 3,3v.
*****
модуль твердотельного реле на базе G3MB-202P
DC- и CH подключаеются к GND, DC+ подключается к 4 ноге
для замыкания реле подаем сигнал HIGH на 4 ногу
*/
#include <EtherCard.h>
#include <SD.h>
#include <DS3231.h>
DS3231 rtc(SDA, SCL);
#define ETH_CS 2
#define SD_CS 3
const String LOG_FILE = "log.txt"; // имя лог файла
const String ZV_FILE = "zvonki.txt"; // имя файла с расписанием звонков
const String ST_FILE = "nastr.txt"; // имя файла с настройками (время синхронизации NTP и отметки начала дня/суток)
const byte mymac[] PROGMEM = { 0x74, 0x69, 0x69, 0x2D, 0x30, 0x31 }; //mac адрес сетевой карты
const char NTP_REMOTEHOST[] PROGMEM = "ntp21.vniiftri.ru"; // NTP сервер, лучше использовать тип сервера stratum 2
const unsigned int NTP_REMOTEPORT PROGMEM = 123; // NTP удаленный порт
const unsigned int NTP_LOCALPORT PROGMEM = 8888; // локальный порт
const unsigned int NTP_PACKET_SIZE PROGMEM = 48; // временная отметка NTP находится в первых 48 байтах сообщения
const unsigned int TIMEZONE PROGMEM = 5; // временная зона UTC+5
byte Ethernet::buffer[500];
static BufferFiller bfill;
String bells[20]; // массив для хранения расписания звонков (20 звонков)
String settings[3]; // массив для хранения настроек (время синхронизации NTP и пр.)
// нулевой элемент массива используется для указания времени начала дня/суток
const unsigned int RingTime = 6000; // длительность звонка в милисекундах (можно вынести в файл настроек)
const int rele_vcc = 4; // вывод для управления реле
//unsigned long timing = 0; // не заработало
void setup () {
pinMode(rele_vcc, OUTPUT);
Serial.begin(9600); // стартуем
Serial.println(F("Старт")); // запускам часы, SD и ethernet
rtc.begin(); // и пишем логи
if (SD.begin(SD_CS) == false) {
Serial.println(F("Карта повреждена или отсутствует"));
return;
}
SDwriteLog("************************");
SDwriteLog("start");
if (ether.begin(sizeof Ethernet::buffer, mymac, ETH_CS) == 0) {
SDwriteLog("Failed to access Ethernet controller");
}
if (ether.dhcpSetup() == false ) { // IP получаем по DHCP
SDwriteLog("Failed to configure Ethernet using DHCP"); // делаем привязку IP адреса по mac на роутере
}
else {
ether.printIp("My IP: ", ether.myip);
SDwriteLog("IP poluchen");
}
SDreadRasp(); // загружаем раписание звонков
SDreadSets(); // и прочие настройки
Serial.println(F("Расписание загружено"));
// digitalWrite(zvonok_vcc, HIGH);
// delay(300);
// digitalWrite(zvonok_vcc, LOW);
Serial.println(rtc.getTimeStr());
}
/****************************************************/
void loop () {
ether.packetLoop(ether.packetReceive()); // смотрим входящие пакеты
word pos = ether.packetLoop(ether.packetReceive());
if (pos) {
bfill = ether.tcpOffset(); // указатель на начало полезной информации TCP. (возможно неправильно перевел)
char* data = (char *) Ethernet::buffer + pos;
if (strncmp("GET /?", data, 6) == 0) { // ищем во входящем заголовке HTTP строку "GET /?" (после "/" можно поставить любые символы для безопасности) длинною 6 символов и сравниваем
parseGet(data, bfill); // если нашли, начинаем разбирать (парсить) строку
}
else {
bfill.emit_p(PSTR( // если ничего нужного нам нет (т.е. на все другие HTTP запросы), шлем в ответ 404 ошибку.
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<h1>404 Not Found</h1>")); // если зайдем через браузер, то тоже получим 404 ошибку и текст
ether.httpServerReply(bfill.position()); // отправить ответ клиенту
}
}
if (millis() % 500 == 0) { // каждые 0,5 сек перебираем массивы
for (int i = 0; i < sizeof(bells) / sizeof(String); i++) { // перебираем массив с расписанием
if (bells[i] == rtc.getTimeStr()) { // если есть совпадение
Serial.println(F("Звонок!!")); // то даем звонок
SDwriteLog("Zvonok");
zvonok(1);
}
}
for (int i = 0; i < sizeof(settings) / sizeof(String); i++) { // перебираем массив с настройками
if (i == 0 && settings[i] == rtc.getTimeStr()) { // если время в нулевом элементе массива совпадает с текущим временем, пишем в лог новый день
SDwriteLog("--------- NEW DAY ---------");
delay(1000);
}
else if (i !=0 && settings[i] == rtc.getTimeStr()) { // если же не нулевой элемент массива, запускаем синхронизацию NTP
acttime();
SDwriteLog("Vremya skorrectirovanno po raspisaniyu");
}
}
}
}
/************************************/
static void parseGet (const char* data, BufferFiller & buf) { // функция разбора (парсинга) строки
char * str; // в которой ищем комбинации ключ+значение, по типу /?nwrasp=now
if (data[5] == '?') { // для команд используется первая часть выражения (то что идет до символа "=", но можно унифицировать обработку комманд
// первая часть будет "command"= а потом пишите что хотите, можно использовать цифры (int), для более простой обработки
// switch case и не нужно будет городить кучу if
// также можно использовать интерпретацию switch case для String, но это если память позволяет
char buf2[182]; // размер буфера для полезной получаемой информации
if (EtherCard::findKeyVal(data + 6, buf2, sizeof(buf2) - 1, "nwrasp")) { // команда загрузки нового расписания
bfill.emit_p(PSTR( // если нашли команду, шлем (подготавливаем ответ) 200 OK, чтобы браузер не ждал, и не слал повторные запросы
"HTTP/1.1 200 OK\r\n"
"\r\n"));
ether.httpServerReply(bfill.position()); // отправляем ответ клиенту
int i = 0; // новое расписание звонков приходит в виде строки ?nwrasp=08:00:00=08:45:00.....
str = strtok (buf2, "="); // разбиваем строку на части, разделителем будет "="
while (str != NULL)
{
bells[i] = str;
str = strtok (NULL, "=");
i++;
}
SDwriteLog("Novoe raspisanie polucheno");
SDwriteRasp();
delay(2000); // делаем задержку на 2 сек, просто, потому что можем (убрать)
}
if (EtherCard::findKeyVal(data + 6, buf2, sizeof(buf2) - 1, "3bells")) { // команда подачи 3 звонков
Serial.println(buf2);
bfill.emit_p(PSTR(
"HTTP/1.1 200 OK\r\n"
"\r\n"));
ether.httpServerReply(bfill.position()); // отправляем ответ клиенту
zvonok(3);
}
if (EtherCard::findKeyVal(data + 6, buf2, sizeof(buf2) - 1, "acttim")) { // получаем актуальное время с arduino
Serial.println(buf2); // данная комманда не сильно нужна, но с помощью нее, можно узнать текущее время на arduino
// и принудительно синхронизировать время по NTP
bfill.emit_p(PSTR(
"HTTP/1.1 200 OK\r\n"
"\r\n"));
ether.httpServerReply(bfill.position()); // отправляем ответ клиенту
//ether.browseUrl // будем отправлять обратным запросом время
// время будет передаваться в виде JSON массива и обрабатываться JS скриптом на сайте
acttime();
}
}
}
// описание данной функции есть на сайте http://w...content-available-to-author-only...k.com/wiki/Arduino:%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B/UdpNtpClient
void udpReceiveNtpPacket(uint16_t dest_port, uint8_t src_ip[IP_LEN], uint16_t src_port, const char *packetBuffer, uint16_t len) {
int h, m, s;
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord;
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears;
epoch = epoch + TIMEZONE * 60 * 60; // делаем корректировку времени с поправкой на часовой пояс
h = ((epoch % 86400L) / 3600); // получаем время в привычном виде
m = ((epoch % 3600) / 60);
s = (epoch % 60);
rtc.setTime(h, m, s); // корректируем время
SDwriteLog("Vremya skorrectirovanno");
Serial.println(epoch);
}
// описание данной функции есть на сайте http://w...content-available-to-author-only...k.com/wiki/Arduino:%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B/UdpNtpClient
void sendNTPpacket() {
uint8_t ntpIp[IP_LEN]; // небольшие корректировки в функцию
if (ether.dnsLookup(NTP_REMOTEHOST) == false) { // если получили DNS failed
Serial.println("DNS failed"); // вручную указываем IP NTP сервера
SDwriteLog("DNS failed");
ntpIp[0] = 89;
ntpIp[1] = 109;
ntpIp[2] = 251;
ntpIp[3] = 25;
}
else {
ether.copyIp(ntpIp, ether.hisip);
}
//ether.printIp("NTP: ", ntpIp);
byte packetBuffer[ NTP_PACKET_SIZE];
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 2; // Тип сервера stratum, пишем 2. (можно 3)
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
ether.sendUdp(packetBuffer, NTP_PACKET_SIZE, NTP_LOCALPORT, ntpIp, NTP_REMOTEPORT );
}
void SDwriteLog(char* data) { // функция записи логов
File myFile;
myFile = SD.open(LOG_FILE, FILE_WRITE); // открываем файл для записи
if (myFile != false) {
myFile.print(rtc.getTimeStr()); // пишем время
myFile.print(" - ");
myFile.println(data); // и переданные в функцию данные (строку)
}
else {
Serial.println("Chto-to poshlo ne tak. LOG_FILE");
}
myFile.close();
}
void SDreadRasp () { // функция чтения расписания из файла
String str;
int i = 0;
File dataFile = SD.open(ZV_FILE, FILE_READ); // открываем файл для чтения
if (dataFile != false) {
while (dataFile.available()) // пока не достигли конца файла
{
str = dataFile.readStringUntil('\n'); // читаем строчку пока не достигли символа переноса строки
str.replace("\n", ""); // из полученной строчки удаляем ненужные символы
str.replace("\r", ""); // (символы возврата каретки и переноса на новую строку)
bells[i] = str; // пишем в массив
delay (10);
i++;
}
SDwriteLog("Raspisanie zagrujeno");
}
else {
SDwriteLog("Chto-to poshlo ne tak. ZV_FILE");
}
dataFile.close();
}
void SDwriteRasp () { // функция записи в файл нового расписания
if (SD.exists(ZV_FILE) == true) { // если файл существует
SD.remove(ZV_FILE); // удаляем его
SDwriteLog("File ZV_FILE udalen");
}
File dataFile = SD.open(ZV_FILE, FILE_WRITE); // открываем файл для записи (если файла не существует, то он будет создан)
if (dataFile != false) {
for (int i = 0; i < sizeof(bells) / sizeof(String); i++) { // в цикле перебираем все элементы массива и пишем их файл
if (i == ((sizeof(bells) / sizeof(String) - 1))) { // если доходим до последнего элемента массива
dataFile.print(bells[i]); // то пишем его в файл уже без символа переноса на новую строчку
}
else {
dataFile.println(bells[i]);
}
}
Serial.println(F("Расписание записано в файл"));
dataFile.close();
SDwriteLog("Novoe raspisanie zagrujeno v fail");
}
else {
SDwriteLog("Chto-to poshlo ne tak pri zapisi novogo raspisaniya. ZV_FILE");
}
}
void SDreadSets () { // функция аналогична чтению расписания
String str;
int i = 0;
File dataFile = SD.open(ST_FILE, FILE_READ);
if (dataFile != false) {
while (dataFile.available())
{
str = dataFile.readStringUntil('\n');
str.replace("\n", "");
str.replace("\r", "");
settings[i] = str;
delay (10);
i++;
}
SDwriteLog("Nastrpoyki zagrujeni iz faila");
}
else {
SDwriteLog("Chto-to poshlo ne tak. ZV_FILE");
}
dataFile.close();
}
void zvonok(unsigned int inch) { // функция подачи звонков
Serial.println(inch); // принимает значение int - количество звонков
if (inch == 1) { // если 1, то обычный звонок
digitalWrite(rele_vcc, HIGH);
delay(RingTime);
digitalWrite(rele_vcc, LOW);
}
else { // если же нет
for (int i = 0; i < inch; i++) { // то в цикле звоним нужное кличество раз (у меня 3)
digitalWrite(rele_vcc, HIGH);
delay(1000);
digitalWrite(rele_vcc, LOW);
delay(800);
}
}
}
void acttime () {
sendNTPpacket(); // шлем NTP запрос
ether.udpServerListenOnPort(&udpReceiveNtpPacket, NTP_LOCALPORT); // получаем ответ и время в unix формате
}