/* notifications_service.cpp - notifications service functions class Copyright (c) 2014 Luc Lebosse. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ //Inspired by following sources //* Line : // - https://github.com/TridentTD/TridentTD_LineNotify // - https://notify-bot.line.me/doc/en/ //* Pushover: // - https://github.com/ArduinoHannover/Pushover // - https://pushover.net/api //* Email: // - https://github.com/CosmicBoris/ESP8266SMTP // - https://www.electronicshub.org/send-an-email-using-esp8266/ #include "config.h" #ifdef ENABLE_NOTIFICATIONS #include "grbl.h" #include "commands.h" #include "notifications_service.h" #include #include #include #include "wificonfig.h" #define PUSHOVERTIMEOUT 5000 #define PUSHOVERSERVER "api.pushover.net" #define PUSHOVERPORT 443 #define LINETIMEOUT 5000 #define LINESERVER "notify-api.line.me" #define LINEPORT 443 #define EMAILTIMEOUT 5000 NotificationsService notificationsservice; bool Wait4Answer(WiFiClientSecure & client, const char * linetrigger, const char * expected_answer, uint32_t timeout) { if(client.connected()) { String answer; uint32_t starttimeout = millis(); while (client.connected() && ((millis() -starttimeout) < timeout)) { answer = client.readStringUntil('\n'); log_d("Answer: %s", answer.c_str()); if ((answer.indexOf(linetrigger) != -1) || (strlen(linetrigger) == 0)) { break; } COMMANDS::wait(10); } if (strlen(expected_answer) == 0) { log_d("Answer ignored as requested"); return true; } if(answer.indexOf(expected_answer) == -1) { log_d("Did not got answer!"); return false; } else { log_d("Got expected answer"); return true; } } log_d("Failed to send message"); return false; } NotificationsService::NotificationsService() { _started = false; _notificationType = 0; _token1 = ""; _token1 = ""; _settings = ""; } NotificationsService::~NotificationsService() { end(); } bool NotificationsService::started() { return _started; } const char * NotificationsService::getTypeString() { switch(_notificationType) { case ESP_PUSHOVER_NOTIFICATION: return "Pushover"; case ESP_EMAIL_NOTIFICATION: return "Email"; case ESP_LINE_NOTIFICATION: return "Line"; default: break; } return "None"; } bool NotificationsService::sendMSG(const char * title, const char * message) { if (!_started) return false; if (!((strlen(title) == 0) && (strlen(message) == 0))) { switch(_notificationType) { case ESP_PUSHOVER_NOTIFICATION: return sendPushoverMSG(title,message); break; case ESP_EMAIL_NOTIFICATION: return sendEmailMSG(title,message); break; case ESP_LINE_NOTIFICATION : return sendLineMSG(title,message); break; default: break; } } return false; } //Messages are currently limited to 1024 4-byte UTF-8 characters //but we do not do any check bool NotificationsService::sendPushoverMSG(const char * title, const char * message) { String data; String postcmd; bool res; WiFiClientSecure Notificationclient; if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); return false; } //build data for post data = "user="; data += _token1; data += "&token="; data += _token2;; data +="&title="; data += title; data += "&message="; data += message; data += "&device="; data += wifi_config.Hostname(); //build post query postcmd = "POST /1/messages.json HTTP/1.1\r\nHost: api.pushover.net\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Length: "; postcmd += data.length(); postcmd +="\r\n\r\n"; postcmd +=data; log_d("Query: %s", postcmd.c_str()); //send query Notificationclient.print(postcmd); res = Wait4Answer(Notificationclient, "{", "\"status\":1", PUSHOVERTIMEOUT); Notificationclient.stop(); return res; } bool NotificationsService::sendEmailMSG(const char * title, const char * message) { WiFiClientSecure Notificationclient; log_d("Connect to server"); if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); return false; } //Check answer of connection if(!Wait4Answer(Notificationclient, "220", "220", EMAILTIMEOUT)) { log_d("Connection failed!"); return false; } //Do HELO log_d("HELO"); Notificationclient.print("HELO friend\r\n"); if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { log_d("HELO failed!"); return false; } log_d("AUTH LOGIN"); //Request AUthentication Notificationclient.print("AUTH LOGIN\r\n"); if(!Wait4Answer(Notificationclient, "334", "334", EMAILTIMEOUT)) { log_d("AUTH LOGIN failed!"); return false; } log_d("Send LOGIN"); //sent Login Notificationclient.printf("%s\r\n",_token1.c_str()); if(!Wait4Answer(Notificationclient, "334", "334", EMAILTIMEOUT)) { log_d("Sent login failed!"); return false; } log_d("Send PASSWORD"); //Send password Notificationclient.printf("%s\r\n",_token2.c_str()); if(!Wait4Answer(Notificationclient, "235", "235", EMAILTIMEOUT)) { log_d("Sent password failed!"); return false; } log_d("MAIL FROM"); //Send From Notificationclient.printf("MAIL FROM: <%s>\r\n",_settings.c_str()); if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { log_d("MAIL FROM failed!"); return false; } log_d("RCPT TO"); //Send To Notificationclient.printf("RCPT TO: <%s>\r\n",_settings.c_str()); if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { log_d("RCPT TO failed!"); return false; } log_d("DATA"); //Send Data Notificationclient.print("DATA\r\n"); if(!Wait4Answer(Notificationclient, "354", "354", EMAILTIMEOUT)) { log_d("Preparing DATA failed!"); return false; } log_d("Send message"); //Send message Notificationclient.printf("From:ESP3D<%s>\r\n",_settings.c_str()); Notificationclient.printf("To: <%s>\r\n",_settings.c_str()); Notificationclient.printf("Subject: %s\r\n\r\n",title); Notificationclient.println(message); log_d("Send final dot"); //Send Final dot Notificationclient.print(".\r\n"); if(!Wait4Answer(Notificationclient, "250", "250", EMAILTIMEOUT)) { log_d("Sending final dot failed!"); return false; } log_d("QUIT"); //Quit Notificationclient.print("QUIT\r\n"); if(!Wait4Answer(Notificationclient, "221", "221", EMAILTIMEOUT)) { log_d("QUIT failed!"); return false; } Notificationclient.stop(); return true; } bool NotificationsService::sendLineMSG(const char * title, const char * message) { String data; String postcmd; bool res; WiFiClientSecure Notificationclient; (void)title; if (!Notificationclient.connect(_serveraddress.c_str(), _port)) { log_d("Error connecting server %s:%d", _serveraddress.c_str(), _port); return false; } //build data for post data = "message="; data += message; //build post query postcmd = "POST /api/notify HTTP/1.1\r\nHost: notify-api.line.me\r\nConnection: close\r\nCache-Control: no-cache\r\nUser-Agent: ESP3D\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nContent-Type: application/x-www-form-urlencoded\r\n"; postcmd +="Authorization: Bearer "; postcmd += _token1 + "\r\n"; postcmd += "Content-Length: "; postcmd += data.length(); postcmd +="\r\n\r\n"; postcmd +=data; log_d("Query: %s", postcmd.c_str()); //send query Notificationclient.print(postcmd); res = Wait4Answer(Notificationclient, "{", "\"status\":200", LINETIMEOUT); Notificationclient.stop(); return res; } //Email#serveraddress:port bool NotificationsService::getPortFromSettings() { Preferences prefs; String defV = DEFAULT_TOKEN; prefs.begin(NAMESPACE, true); String tmp = prefs.getString(NOTIFICATION_TS, defV); prefs.end(); int pos = tmp.lastIndexOf(':'); if (pos == -1) { return false; } _port= tmp.substring(pos+1).toInt(); log_d("port : %d", _port); if (_port > 0) { return true; } else { return false; } } //Email#serveraddress:port bool NotificationsService::getServerAddressFromSettings() { Preferences prefs; String defV = DEFAULT_TOKEN; prefs.begin(NAMESPACE, true); String tmp = prefs.getString(NOTIFICATION_TS, defV); prefs.end(); int pos1 = tmp.indexOf('#'); int pos2 = tmp.lastIndexOf(':'); if ((pos1 == -1) || (pos2 == -1)) { return false; } //TODO add a check for valid email ? _serveraddress = tmp.substring(pos1+1, pos2); log_d("server : %s", _serveraddress.c_str()); return true; } //Email#serveraddress:port bool NotificationsService::getEmailFromSettings() { Preferences prefs; String defV = DEFAULT_TOKEN; prefs.begin(NAMESPACE, true); String tmp = prefs.getString(NOTIFICATION_TS, defV); prefs.end(); int pos = tmp.indexOf('#'); if (pos == -1) { return false; } _settings = tmp.substring(0, pos); log_d("email : %s", _settings.c_str()); //TODO add a check for valid email ? return true; } bool NotificationsService::begin() { bool res = true; end(); Preferences prefs; String defV = DEFAULT_TOKEN; prefs.begin(NAMESPACE, true); _notificationType = prefs.getChar(NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_TYPE); switch(_notificationType) { case 0: //no notification = no error but no start return true; case ESP_PUSHOVER_NOTIFICATION: _token1 = prefs.getString(NOTIFICATION_T1, defV); _token2 = prefs.getString(NOTIFICATION_T2, defV); _port = PUSHOVERPORT; _serveraddress = PUSHOVERSERVER; break; case ESP_LINE_NOTIFICATION: _token1 = prefs.getString(NOTIFICATION_T1, defV); _port = LINEPORT; _serveraddress = LINESERVER; break; case ESP_EMAIL_NOTIFICATION: _token1 = base64::encode(prefs.getString(NOTIFICATION_T1, defV)); _token2 = base64::encode(prefs.getString(NOTIFICATION_T2, defV)); //log_d("%s",Settings_ESP3D::read_string(ESP_NOTIFICATION_TOKEN1)); //log_d("%s",Settings_ESP3D::read_string(ESP_NOTIFICATION_TOKEN2)); if(!getEmailFromSettings() || !getPortFromSettings()|| !getServerAddressFromSettings()) { prefs.end(); return false; } break; default: prefs.end(); return false; break; } prefs.end(); if(WiFi.getMode() != WIFI_STA){ res = false; } if (!res) { end(); } _started = res; return _started; } void NotificationsService::end() { if(!_started) { return; } _started = false; _notificationType = 0; _token1 = ""; _token1 = ""; _settings = ""; _serveraddress = ""; _port = 0; } void NotificationsService::handle() { if (_started) { } } #endif //ENABLE_NOTIFICATIONS