Subversion Repositories heating

Rev

Rev 50 | 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 <sqlite3.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;

                enum REQUEST
                {
                        NONE,
                        GLOBALS,
                        ROOM,
                        DETAILS
                };

        public:
                HttpController(string mp, int *r, html::HTCONF *ht)
                {
                        mainPage = mp;
                        room = r;
                        HeatConf = ht;
                }

                inline static time_t strToTime(const char *str)                                         // Convert a time string into a time value
                {
                        int hour, min;

                        if (strchr(str, ':') != NULL)
                        {
                                sscanf(str, "%d:%d", &hour, &min);
                                return (time_t)hour * 3600 + (time_t)min * 60;
                        }

                        return 0;
                }

                inline static std::string timeToStr(time_t t)                                   // Convert a time value into a string
                {
                        int hour, min;
                        char hv0[48];

                        hour = (int)(t / 3600);
                        min = (int)((t - (time_t)hour * 3600) / 60);
                        sprintf(hv0, "%02d:%02d", hour, min);
                        string zeit(hv0);
                        return zeit;
                }

                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; }
                inline static int getSelRoom() { return *room; }
                inline static html::HTCONF *getHConf() { return HeatConf; }
                inline static REQUEST getLastRequest() { return rq; }

                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) ||
                                (strcmp(path, "/details") == 0 && strcmp("POST", method) == 0))
                        {
                                if (strcmp (path, "/global") == 0)
                                        HttpController::rq = GLOBALS;
                                else if (strcmp (path, "/room") == 0)
                                        HttpController::rq = ROOM;
                                else if (strcmp (path, "/details") == 0)
                                        HttpController::rq = DETAILS;
                                else
                                        HttpController::rq = NONE;

                                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, 1);
                                }
                                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, 1);
                                }
                                else
                                        con_info->answerstring.clear();
                        }
                        else if (strcmp(key, "mode") == 0)
                        {
                                if (data && size > 0)
                                {
                                        if (strcmp(data, "normal") == 0)
                                                *onoff = true;
                                        else if (strcmp(data, "off") == 0)
                                                *onoff = false;
                                }
                        }
                        else if (strcmp(key, "room") == 0)
                        {
                                if (data && size > 0)
                                {
                                        int r = atoi(data);

                                        if (r)
                                                *room = r;
                                }
                        }
                        else if (strcmp(key, "soll") == 0)
                        {
                                if (data && size > 0)
                                {
                                        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 && size > 0)
                                {
                                        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 && size > 0)
                                {
                                        html::HTCONF *akt = HeatConf;

                                        while (akt)
                                        {
                                                if (akt->rnum == *room)
                                                {
                                                        akt->minimal = strtod(data, NULL);
                                                        break;
                                                }

                                                akt = akt->next;
                                        }
                                }
                        }
                        else if (strcmp(key, "start1") == 0)
                        {
                                if (data && size > 0)
                                {
                                        html::HTCONF *akt = HeatConf;

                                        while (akt)
                                        {
                                                if (akt->rnum == *room)
                                                {
                                                        akt->start1 = strToTime(data);
                                                        break;
                                                }

                                                akt = akt->next;
                                        }
                                }
                        }
                        else if (strcmp(key, "end1") == 0)
                        {
                                if (data && size > 0)
                                {
                                        html::HTCONF *akt = HeatConf;

                                        while (akt)
                                        {
                                                if (akt->rnum == *room)
                                                {
                                                        akt->end1 = strToTime(data);
                                                        break;
                                                }

                                                akt = akt->next;
                                        }
                                }
                        }
                        else if (strcmp(key, "start2") == 0)
                        {
                                if (data && size > 0)
                                {
                                        html::HTCONF *akt = HeatConf;

                                        while (akt)
                                        {
                                                if (akt->rnum == *room)
                                                {
                                                        akt->start2 = strToTime(data);
                                                        break;
                                                }

                                                akt = akt->next;
                                        }
                                }
                        }
                        else if (strcmp(key, "end2") == 0)
                        {
                                if (data && size > 0)
                                {
                                        html::HTCONF *akt = HeatConf;

                                        while (akt)
                                        {
                                                if (akt->rnum == *room)
                                                {
                                                        akt->end2 = strToTime(data);
                                                        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, 1));
                        mp.replace(mp.find("#02#"), 4, h.doubleToString(*glb_minimal, 1));
                        
                        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");
                        }

                        int c = 100;

                        while (akt)
                        {
                                if (akt->rnum == *room)
                                {
                                        mp.replace(mp.find("#"+h.itostring(c)+"#"), 5, " selected");
                                        mp.replace(mp.find("#05#"), 4, h.doubleToString(akt->soll, 1));
                                        mp.replace(mp.find("#06#"), 4, h.doubleToString(akt->night, 1));
                                        mp.replace(mp.find("#07#"), 4, h.doubleToString(akt->minimal, 1));
                                        mp.replace(mp.find("#08#"), 4, timeToStr(akt->start1));
                                        mp.replace(mp.find("#09#"), 4, timeToStr(akt->end1));
                                        mp.replace(mp.find("#10#"), 4, timeToStr(akt->start2));
                                        mp.replace(mp.find("#11#"), 4, timeToStr(akt->end2));
                                }
                                else
                                        mp.erase(mp.find("#"+h.itostring(c)+"#"), 5);

                                akt = akt->next;
                                c++;
                        }

                        response << mp;
                }
        
        private:
                string mainPage;
                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
                static int *room;
                static REQUEST rq;
                static html::HTCONF *HeatConf;
};

double *HttpController::glb_night = nullptr;
double *HttpController::glb_minimal = nullptr;
bool *HttpController::onoff = nullptr;
int *HttpController::room = nullptr;
html::HTCONF *HttpController::HeatConf;
HttpController::REQUEST HttpController::rq = HttpController::REQUEST::NONE;

std::string html::intErrorPage;
bool html::datar = false;

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=\"viewport\" content=\"width=device-width\">\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 ("<form name=\"global\" action=\"/global\" method=\"post\">");
        mainPage.append ("<tr><td>Nachtabsenkung</td><td><input type=\"number\" name=\"gnight\"  min=\"16.0\" max=\"25.0\" step=\"0.5\" value=\"#01#\"></td></tr>\n");
        mainPage.append ("<tr><td>Abwesend</td><td><input type=\"number\" name=\"gabsent\" min=\"16.0\" max=\"25.0\" step=\"0.5\" value=\"#02#\"></td></tr>\n");
        mainPage.append ("</table></td></tr>\n");
        mainPage.append ("<tr><td><input type=\"radio\" name=\"mode\" value=\"normal\"#03#>Anwesend&nbsp;&nbsp;<input type=\"radio\" name=\"mode\" value=\"off\"#04#>Abwesend</td></tr>\n");

        mainPage.append ("<tr><td><input type=\"submit\" value=\"Speichern\"></form></td></tr>\n");
        mainPage.append ("</table>\n");
        
        mainPage.append ("<table border=1 cellpadding=0 cellspacing=0>\n");
        mainPage.append ("<tr><td>");
        
        mainPage.append ("<form name=\"choose\" action=\"/room\" method=\"post\">");
        mainPage.append ("<table border=0>\n");
        mainPage.append ("<tr><td>Raum</td><td><select name=\"room\" onChange=\"javascript:document.choose.submit()\">\n");

        int c = 100;

        while (akt)
        {
                if (!room)
                        room = akt->rnum;

                mainPage.append ("<option value=\"" + itostring(akt->rnum) + "\"#" + itostring(c) + "#>" + ToString(akt->rname) +"</option>\n");
                akt = akt->next;
                c++;
        }

        akt = HeatConf;
        mainPage.append ("</select>\n<input type=\"submit\" value=\"Auswahl\"></form>\n");
        mainPage.append ("<form name=\"details\" action=\"/details\" method=\"post\"></td></tr>\n");
        mainPage.append ("<tr><td>Solltemperatur</td><td><input type=\"number\" name=\"soll\" min=\"16.0\" max=\"30.0\" step=\"0.5\" value=\"#05#\"></td></tr>\n");
        mainPage.append ("<tr><td>Nachtabsenkung</td><td><input type=\"number\" name=\"night\" min=\"0.0\" max=\"25.0\" step=\"0.5\" value=\"#06#\"></td></tr>\n");
        mainPage.append ("<tr><td>Abwesenheit</td><td><input type=\"number\" name=\"absent\" min=\"0.0\" max=\"25.0\" step=\"0.5\" value=\"#07#\"></td></tr>\n");
        mainPage.append ("<tr><td colspan=2>&nbsp;</td></tr>\n");
        mainPage.append ("<tr><td>Startzeit Tagbetrieb</td><td><input type=\"time\" name=\"start1\" placeholder=\"23:59\" value=\"#08#\"></td></tr>\n");
        mainPage.append ("<tr><td>Startzeit Nachtbetrieb</td><td><input type=\"time\" name=\"end1\" placeholder=\"23:59\" value=\"#09#\"></td></tr>\n");
        mainPage.append ("<tr><td>Startzeit Werktag</td><td><input type=\"time\" name=\"start2\" placeholder=\"23:59\" value=\"#10#\"></td></tr>\n");
        mainPage.append ("<tr><td>Endzeit Werktag</td><td><input type=\"time\" name=\"end2\" placeholder=\"23:59\" value=\"#11#\"></td></tr>\n");
        mainPage.append ("<tr><td>&nbsp;</td><td><input type=\"submit\" value=\"Speichern\"></form></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, *prev;

                p = prev = HeatConf;

                while (p)
                {
                        prev = p;

                        if (p->rnum == ht->rnum)
                        {
                                p->soll = ht->soll;
                                p->night = ht->night;
                                p->minimal = ht->minimal;
                                p->start1 = ht->start1;
                                p->end1 = ht->end1;
                                p->start2 = p->start2;
                                p->end2 = p->end2;
                                delete akt;
                                debug("html::addConfig(): rname="+ToString(p->rname)+", rnum="+itostring(p->rnum)+", Orig:"+ToString(ht->rname)+":"+itostring(ht->rnum));
                                return p;
                        }

                        p = p->next;
                }

                prev->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)
{
sqlite3 *db;
sqlite3_stmt *res;
int rc;
char *zErrMsg = nullptr;
string query;
html::HTCONF *akt = HttpController::getHConf();
helper h;

        HttpController::connection_info_struct *con_info = (HttpController::connection_info_struct *)*con_cls;

        if (Configure.debug)
                syslog(LOG_DEBUG, "Request was completed.");

        if (con_info == nullptr)
                return;

        if (con_info->connectiontype == POST)
                MHD_destroy_post_processor (con_info->postprocessor);

        delete con_info;
        *con_cls = nullptr;

        // Here everything should be processed. So it's time to get the results.
        if (HttpController::getLastRequest() == HttpController::REQUEST::GLOBALS ||
                HttpController::getLastRequest() == HttpController::REQUEST::DETAILS)
        {
                if (Configure.debug)
                        syslog(LOG_DEBUG, "Values %s will be saved...", (HttpController::getLastRequest() == HttpController::REQUEST::GLOBALS)?"GLOBALS":"DETAILS");

                rc = sqlite3_open(Configure.home, &db);

                if (rc)
                {
                        syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
                        return;
                }

                if (HttpController::getLastRequest() == HttpController::REQUEST::DETAILS)
                {
                        while (akt)
                        {
                                if (akt->rnum == HttpController::getSelRoom())
                                {
                                        query = "update heating ";
                                        query.append("set name = \""+string(akt->rname)+"\"");
                                        query.append(",soll = "+h.doubleToString(akt->soll, 1));
                                        query.append(",night = "+h.doubleToString(akt->night, 1));
                                        query.append(",minimal = "+h.doubleToString(akt->minimal, 1));
                                        query.append(",start = "+h.itostring(akt->start1));
                                        query.append(",end = "+h.itostring(akt->end1));
                                        query.append(" where id = " + h.itostring(akt->rnum));
                                        rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);

                                        if (rc != SQLITE_OK)
                                        {
                                                syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
                                                sqlite3_free(zErrMsg);
                                                sqlite3_close(db);
                                                return;
                                        }
                                }

                                akt = akt->next;
                        }

                        datar = true;
                }

                if (HttpController::getLastRequest() == HttpController::REQUEST::GLOBALS)
                {
                        query = "update glbheat set night = ";
                        query.append(h.doubleToString(HttpController::getGlbNight(), 1));
                        query.append(", minimal = "+h.doubleToString(HttpController::getGlbAbsent(), 1));
                        query.append(", onoff = "+h.itostring(HttpController::getGlbPower()));

                        rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);

                        if (rc != SQLITE_OK)
                        {
                                syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
                                sqlite3_free(zErrMsg);
                                sqlite3_close(db);
                                return;
                        }

                        datar = true;
                }

                sqlite3_close(db);

                if (Configure.debug)
                        syslog(LOG_DEBUG, "Values successfully saved.");
        }
}

void html::addController(Controller* controller)
{
        controllers.push_back(controller);
}