Rev 24 | Rev 28 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* Copyright (C) 2015 by Andreas Theofilu. All rights reserved!
*
* All rights reserved. No warranty, explicit or implicit, provided.
*
* NOTICE: All information contained herein is, and remains
* the property of Andreas Theofilu and his suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to Andreas Theofilu and its suppliers and
* may be covered by European and Foreign Patents, patents in process,
* and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Andreas Theofilu.
*
* Author: Andreas Theofilu <andreas@theosys.at>
*/
#include <iostream>
#include <string>
#include <cstring>
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <syslog.h>
#include <microhttpd.h>
#include "config.h"
#include "helper.h"
#include "controller.h"
#include "html.h"
using namespace std;
#define POSTBUFFERSIZE 512
#define MAXNAMESIZE 20
#define MAXANSWERSIZE 512
#define GET 0
#define POST 1
class HttpController : public DynamicController
{
public:
typedef struct
{
int connectiontype;
string answerstring;
struct MHD_PostProcessor *postprocessor;
}connection_info_struct;
static double glb_minimal; // The global temperature for normal (day) mode
static double glb_night; // The global temperature for night mode
static bool onoff; // True if heating is in normal or night mode
public:
HttpController(string mp, int r, html::HTCONF *ht)
{
mainPage = mp;
room = r;
HeatConf = ht;
}
inline static void setGlobals(double n, double a, bool m)
{
glb_minimal = a;
glb_night = n;
onoff = m;
}
inline static double getGlbNight() { return glb_night; }
inline static double getGlbAbsent() { return glb_minimal; }
inline static bool getGlbPower() { return onoff; }
virtual bool validPath(const char* path, const char* method)
{
if (Configure.debug)
syslog(LOG_DEBUG, "path=%s, method=%s", path, method);
if((strcmp(path, "/") == 0 && strcmp("GET", method) == 0) ||
(strcmp(path, "/global") == 0 && strcmp("POST", method) == 0) ||
(strcmp(path, "/room") == 0 && strcmp("POST", method) == 0))
return true;
return false;
}
static int iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
const char *filename, const char *content_type,
const char *transfer_encoding, const char *data, uint64_t off,
size_t size)
{
helper h;
connection_info_struct *con_info = (connection_info_struct *)coninfo_cls;
if (Configure.debug)
syslog(LOG_DEBUG, "Iterate: key=%s, data=%s", key, data);
if (strcmp (key, "gnight") == 0)
{
if (size > 0 && size <= MAXNAMESIZE)
{
glb_night = strtod(data, NULL);
con_info->answerstring = h.doubleToString(glb_night, 2);
}
else
con_info->answerstring.clear();
}
else if (strcmp(key, "gabsent") == 0)
{
if (size > 0 && size <= MAXNAMESIZE)
{
glb_minimal = strtod(data, NULL);
con_info->answerstring = h.doubleToString(glb_minimal, 2);
}
else
con_info->answerstring.clear();
}
else if (strcmp(key, "mode") == 0)
{
if (data)
{
if (strcmp(data, "normal") == 0)
onoff = true;
else
onoff = false;
}
}
else if (strcmp(key, "room") == 0)
{
if (data)
{
int r = atoi(data);
if (r)
room = r;
}
}
else if (strcmp(key, "soll") == 0)
{
if (data)
{
html::HTCONF *akt = HeatConf;
while (akt)
{
if (akt->rnum == room)
{
akt->soll = strtod(data, NULL);
break;
}
akt = akt->next;
}
}
}
else if (strcmp(key, "night") == 0)
{
if (data)
{
html::HTCONF *akt = HeatConf;
while (akt)
{
if (akt->rnum == room)
{
akt->night = strtod(data, NULL);
break;
}
akt = akt->next;
}
}
}
else if (strcmp(key, "absent") == 0)
{
if (data)
{
html::HTCONF *akt = HeatConf;
while (akt)
{
if (akt->rnum == room)
{
akt->minimal = strtod(data, NULL);
break;
}
akt = akt->next;
}
}
}
return MHD_YES;
}
virtual void createResponse(struct MHD_Connection *connection,
const char *url, const char *method, const char *upload_data,
size_t *upload_data_size, std::stringstream& response, void **ptr)
{
string mp = mainPage;
helper h;
html::HTCONF *akt = HeatConf;
// Replace the place holders with data
mp.replace(mp.find("#01#"), 4, h.doubleToString(glb_night, 2));
mp.replace(mp.find("#02#"), 4, h.doubleToString(glb_minimal, 2));
if (onoff)
{
mp.replace(mp.find("#03#"), 4, " checked");
mp.erase(mp.find("#04#"), 4);
}
else
{
mp.erase(mp.find("#03#"), 4);
mp.replace(mp.find("#04#"), 4, " checked");
}
while (akt)
{
if (akt->rnum == room)
{
mp.replace(mp.find("#05#"), 4, h.doubleToString(akt->soll, 2));
mp.replace(mp.find("#06#"), 4, h.doubleToString(akt->night, 2));
mp.replace(mp.find("#07#"), 4, h.doubleToString(akt->minimal, 2));
break;
}
akt = akt->next;
}
response << mp;
}
private:
string mainPage;
static int room;
static html::HTCONF *HeatConf;
};
double HttpController::glb_night = 0.0;
double HttpController::glb_minimal = 0.0;
bool HttpController::onoff = false;
int HttpController::room;
html::HTCONF *HttpController::HeatConf;
std::string html::intErrorPage;
html::html()
{
enableDebug(Configure.debug);
HeatConf = nullptr;
glb_night = 0.0;
glb_absent = 0.0;
stop = false;
daemon = nullptr;
room = 0;
}
html::~html()
{
HTCONF *akt = HeatConf;
if (daemon)
MHD_stop_daemon (daemon);
while (akt)
{
HTCONF *p;
p = akt->next;
delete akt;
akt = p;
}
}
void html::run()
{
initPages();
HttpController HtmlPage(mainPage, room, HeatConf);
HtmlPage.setGlobals(glb_night, glb_absent, mode);
addController(&HtmlPage);
daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, Configure.html_port, NULL, NULL,
&request_handler, this,
MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL,
MHD_OPTION_END);
while (!stop)
sleep(60);
MHD_stop_daemon (daemon);
}
void html::initPages()
{
HTCONF *akt = HeatConf;
// This is the main page
mainPage = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n";
mainPage.append ("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
mainPage.append ("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"de\" lang=\"de\">\n\n");
mainPage.append ("<head>\n");
mainPage.append ("<title>Heizungsteuerung</title>\n");
mainPage.append ("<META NAME=\"author\" CONTENT=\"Andreas Theofilu\">\n");
mainPage.append ("<META NAME=\"publisher\" CONTENT=\"Andreas Theofilu (TheoSys)\">\n");
mainPage.append ("<META NAME=\"copyright\" CONTENT=\"(C) 2015 by Andreas Theofilu\">\n");
mainPage.append ("<meta name=\"Description\" content=\"Heating control\">\n");
mainPage.append ("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
mainPage.append ("<meta NAME=\"content-language\" content=\"de\">\n");
mainPage.append ("</head>\n");
mainPage.append ("<body>\n");
mainPage.append ("<h1>Heizungsteuerung</h1>\n");
mainPage.append ("<table border=1 cellpadding=0 cellspacing=0>\n");
mainPage.append ("<tr><td>");
mainPage.append ("<table border=0>\n");
mainPage.append ("<tr><td>");
mainPage.append ("<form action=\"/global\" method=\"post\">");
mainPage.append ("<tr><td>Nachtabsenkung</td><td><input type=\"text\" name=\"gnight\" value=\"#01#\"></td></tr>\n");
mainPage.append ("<tr><td>Abwesend</td><td><input type=\"text\" name=\"gabsent\" value=\"#02#\"></td></tr>\n");
mainPage.append ("</table></tr></td>\n");
mainPage.append ("<tr><td><input type=\"radio\" name=\"mode\" value=\"normal\"#03#>Anwesend <input type=\"radio\" name=\"mode\" value=\"off\"#04#>Abwesend</td></tr>\n");
mainPage.append ("<tr><td><input type=\"submit\" value=\"Speichern\"></td></tr>\n</form>\n");
mainPage.append ("</table>\n");
mainPage.append ("<table border=1 cellpadding=0 cellspacing=0>\n");
mainPage.append ("<tr><td>");
mainPage.append ("<form = action=\"/room\" method=\"post\">");
mainPage.append ("<table border=0>\n");
mainPage.append ("<tr><td>Raum</td><td><select size=\"3\" name=\"room\" onChange=\"javascript:document.room.submit()\">\n");
while (akt)
{
if (!room)
room = akt->rnum;
mainPage.append ("<option value=\"" + itostring(akt->rnum) + "\"");
if (room == akt->rnum)
mainPage.append(" selected");
mainPage.append (">" + ToString(akt->rname) +"</option>");
akt = akt->next;
}
akt = HeatConf;
mainPage.append ("</select></td></tr>\n");
mainPage.append ("<tr><td>Solltemperatur</td><td><input type=\"text\" name=\"soll\" value=\"#05#\"></td></tr>\n");
mainPage.append ("<tr><td>Nachtabsenkung</td><td><input type=\"text\" name=\"night\" value=\"#06#\"></td></tr>\n");
mainPage.append ("<tr><td>Abwesenheit</td><td><input type=\"text\" name=\"absent\" value=\"#07#\"></td></tr>\n");
mainPage.append ("<tr><td> </td><td><input type=\"submit\" value=\"Speichern\"></td></tr>\n");
mainPage.append ("</table></td></tr></table>\n");
mainPage.append ("</body>\n</html>\n");
// Internal error page
intErrorPage = "<html><body>An internal server error has occured!</body></html>";
}
html::HTCONF *html::addConfig(HTCONF *ht)
{
HTCONF *akt;
akt = new HTCONF;
memmove (akt, ht, sizeof(HTCONF));
akt->next = nullptr;
if (HeatConf == nullptr)
HeatConf = akt;
else
{
HTCONF *p;
p = HeatConf;
while (p->next)
p = p->next;
p->next = akt;
}
debug("html::addConfig(): rname="+ToString(akt->rname)+", rnum="+itostring(akt->rnum));
return akt;
}
/*
* The following functions are callbacks for the HTML server
*/
int html::request_handler(void* cls, struct MHD_Connection *connection,
const char *url, const char *method, const char* version,
const char *upload_data, size_t *upload_data_size, void **ptr)
{
html *server = (html *)cls;
Controller *controller = 0;
for (int i = 0; i < (int)server->controllers.size(); i++)
{
Controller *c = server->controllers.at(i);
if(c->validPath(url, method))
{
controller = c;
break;
}
}
if (!controller)
{
syslog(LOG_WARNING, "Path <%s> not found.", url);
struct MHD_Response* response = MHD_create_response_from_buffer(strlen(intErrorPage.c_str()), (void *)intErrorPage.c_str(), MHD_RESPMEM_PERSISTENT);
return MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
}
else
{
if (*ptr == NULL)
{
HttpController::connection_info_struct *con_info;
if (Configure.debug)
syslog(LOG_DEBUG, "Initializing postprocessor (url=%s, method=%s, upload_data=%s, upload_data_size=%d)", url, method, upload_data, upload_data_size);
if (Configure.debug)
syslog(LOG_DEBUG, "Initializing postprocessor (url=%s, method=%s, upload_data=%s, upload_data_size=%d)", url, method, upload_data, upload_data_size);
con_info = new HttpController::connection_info_struct;
con_info->answerstring.clear();
if (strcmp (method, "POST") == 0)
{
con_info->postprocessor = MHD_create_post_processor (connection, POSTBUFFERSIZE, HttpController::iterate_post, (void *)con_info);
if (con_info->postprocessor == NULL)
{
delete con_info;
syslog(LOG_WARNING, "Error creating a HTML post processor");
return MHD_NO;
}
con_info->connectiontype = POST;
}
else
con_info->connectiontype = GET;
*ptr = (void *)con_info;
return MHD_YES;
}
if (strcmp (method, "POST") == 0)
{
HttpController::connection_info_struct *con_info = (HttpController::connection_info_struct *)*ptr;
if (Configure.debug)
syslog(LOG_DEBUG, "Executing postprocessor (url=%s, method=%s, upload_data=%s, upload_data_size=%d)", url, method, upload_data, upload_data_size);
if (*upload_data_size != 0)
{
MHD_post_process (con_info->postprocessor, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
}
}
return controller->handleRequest(connection, url, method, upload_data, upload_data_size, ptr);
}
void html::request_completed (void *cls, struct MHD_Connection *connection,
void **con_cls, enum MHD_RequestTerminationCode toe)
{
HttpController::connection_info_struct *con_info = (HttpController::connection_info_struct *)*con_cls;
if (con_info == nullptr)
return;
if (con_info->connectiontype == POST)
{
MHD_destroy_post_processor (con_info->postprocessor);
}
delete con_info;
*con_cls = nullptr;
}
void html::addController(Controller* controller)
{
controllers.push_back(controller);
}
/*
int html::send_page (struct MHD_Connection *connection, const char *page)
{
int ret;
struct MHD_Response *response;
response = MHD_create_response_from_buffer (strlen (page), (void *)page, MHD_RESPMEM_PERSISTENT);
if (!response)
return MHD_NO;
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
MHD_destroy_response (response);
return ret;
}
int html::iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
const char *filename, const char *content_type,
const char *transfer_encoding, const char *data, uint64_t off,
size_t size)
{
struct connection_info_struct *con_info = coninfo_cls;
if (strcmp (key, "name") == 0)
{
if (size > 0 && size <= MAXNAMESIZE)
{
char *answerstring;
answerstring = malloc (MAXANSWERSIZE);
if (!answerstring)
{
syslog (LOG_DAEMON, "Error allocating memory for an answer string!");
return MHD_NO;
}
snprintf (answerstring, MAXANSWERSIZE, mainPage.c_str(), data);
con_info->answerstring = answerstring;
}
else
con_info->answerstring = NULL;
return MHD_NO;
}
return MHD_YES;
}
void request_completed (void *cls, struct MHD_Connection *connection,
void **con_cls, enum MHD_RequestTerminationCode toe)
{
struct connection_info_struct *con_info = *con_cls;
if (NULL == con_info)
return;
if (con_info->connectiontype == POST)
{
MHD_destroy_post_processor (con_info->postprocessor);
if (con_info->answerstring)
free (con_info->answerstring);
}
free (con_info);
*con_cls = NULL;
}
int html::answer_to_connection (void *cls, struct MHD_Connection *connection,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls)
{
if (*con_cls == NULL)
{
struct connection_info_struct *con_info;
con_info = malloc (sizeof (struct connection_info_struct));
if (con_info == NULL)
{
syslog (LOG_DAEMON, "Not enought memory for a HTML answer!");
return MHD_NO;
}
con_info->answerstring = NULL;
if (strcmp (method, "POST") == 0)
{
con_info->postprocessor = MHD_create_post_processor (connection, POSTBUFFERSIZE, iterate_post, (void *) con_info);
if (NULL == con_info->postprocessor)
{
free (con_info);
return MHD_NO;
}
con_info->connectiontype = POST;
}
else
con_info->connectiontype = GET;
*con_cls = (void *)con_info;
return MHD_YES;
}
if (strcmp (method, "GET") == 0)
return send_page (connection, mainPage.c_str());
if (strcmp (method, "POST") == 0)
{
struct connection_info_struct *con_info = *con_cls;
if (*upload_data_size != 0)
{
MHD_post_process (con_info->postprocessor, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
else if (con_info->answerstring != NULL)
return send_page (connection, con_info->answerstring);
}
return send_page (connection, errorpage);
}
*/