Subversion Repositories heating

Rev

Rev 4 | Rev 8 | 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 <fstream>
#include <string>
#include <iomanip>
#include <cstring>
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <fstream>
#include <cerrno>
#include <unistd.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sqlite3.h>
#include "heating.h"
#include "config.h"
#include "helper.h"

using namespace std;

heating::heating()
{
        enableDebug(Configure.debug);
        glb_minimal = 16.0;
        glb_night = 18.0;
        rm = 0;
        rstop = false;
        rrun = false;
        HeatConf = nullptr;

        for (int i = 0; i < 100; i++)
                s1[i] = -1;

        initialized = false;

        if (readConf())
        {
                if (buildDB())
                        initialized = true;
        }
}

heating::~heating()
{
        destroyHConf();
}

void heating::run()
{
int i;
double tmp;
HCONF *akt;

        rstop = false;
        rrun = true;
        i = 0;

        while (!rstop)
        {
                akt = HeatConf;
                
                while (akt)
                {
                        tmp = getTemp(akt->tempsensor);

                        if (tmp < 99.0 && tmp > -99.0)
                                akt->ist = tmp;

                        akt = akt->next;
                }

                i++;

                if (i > 60)
                {
                        i = 0;
                        akt = HeatConf;

                        while (akt)
                        {
                                answer("IST:" + itostring(akt->rnum) + ":" + doubleToString(akt->ist, 2) + ";");
                                akt = akt->next;
                        }
                }

                sleep(1);
        }

        rrun = false;
}

double heating::incSoll(int room)
{
HCONF *akt;
char *zErrMsg = nullptr;
int rc;
sqlite3 *db;
sqlite3_stmt *res;
string query;

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

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

        if ((akt = findRoom(room)) != nullptr)
        {
                if (akt->soll >= 30.0)
                {
                        sqlite3_close(db);
                        return akt->soll;
                }

                akt->soll += 0.5;
                query = "update heating set soll = " + doubleToString(akt->soll, 2) + " where id = " + itostring(room);

                if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
                {
                        syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
                        sqlite3_free(zErrMsg);
                        sqlite3_close(db);
                        akt->soll -= 0.5;
                        return akt->soll;
                }

                sqlite3_close(db);
                stringstream stream;
                stream << "SOLL:" << setprecision(2) << akt->soll << ";";
                answer(stream.str());
                return akt->soll;
        }

        return 0.0;
}

double heating::decSoll(int room)
{
HCONF *akt;
char *zErrMsg = nullptr;
int rc;
sqlite3 *db;
sqlite3_stmt *res;
string query;

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

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

        if ((akt = findRoom(room)) != nullptr)
        {
                if (akt->soll <= 10.0)
                {
                        sqlite3_close(db);
                        return akt->soll;
                }

                akt->soll -= 0.5;
                query = "update heating set soll = " + doubleToString(akt->soll, 2) + " where id = " + itostring(room);

                if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
                {
                        syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
                        sqlite3_free(zErrMsg);
                        sqlite3_close(db);
                        akt->soll += 0.5;
                        return akt->soll;
                }

                sqlite3_close(db);
                stringstream stream;
                stream << "SOLL:" << setprecision(2) << akt->soll << ";";
                answer(stream.str());
                return akt->soll;
        }

        return 0.0;
}

void heating::setNight(int room, double val)
{
HCONF *akt;
char *zErrMsg = nullptr;
int rc;
sqlite3 *db;
sqlite3_stmt *res;
string query;

        if (val < 10.0 || val > 25.0)
        {
                syslog(LOG_INFO, "Invalid value for the night!");
                return;
        }

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

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

        if (room > 0 && (akt = findRoom(room)) != nullptr)
        {
                akt->night = val;
                query = "update heating set night = " + doubleToString(val, 2) + " where id = " + itostring(room);

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

                query = "NIGHT:" + itostring(room) + ":" + doubleToString(val, 2) + ";";
                answer(query);
        }
        else if (room == 0)
        {
                glb_night = val;
                query = "update glbheat set night = " + doubleToString(val, 2);

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

                query = "CONFIG:GLOBAL NIGHT:" + doubleToString(val, 2) + ";";
                answer(query);
        }

        sqlite3_close(db);
}

void heating::setMinimal(int room, double val)
{
HCONF *akt;
char *zErrMsg = nullptr;
int rc;
sqlite3 *db;
sqlite3_stmt *res;
string query;

        if (val < 10.0 || val > 20.0)
        {
                syslog(LOG_INFO, "Invalid value for the night!");
                return;
        }

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

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

        if (room > 0 && (akt = findRoom(room)) != nullptr)
        {
                akt->minimal = val;
                query = "update heating set minimal = " + doubleToString(val, 2) + " where id = " + itostring(room);

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

                query = "MINIMAL:" + itostring(room) + ":" + doubleToString(val, 2) + ";";
                answer(query);
        }
        else if (room == 0)
        {
                glb_minimal = val;
                query = "update glbheat set minimal = " + doubleToString(val, 2);

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

                query = "CONFIG:GLOBAL MINIMAL:" + doubleToString(val, 2) + ";";
                answer(query);
        }

        sqlite3_close(db);
}

bool heating::switchOnOff(bool what)
{
HCONF *akt;
char *zErrMsg = nullptr;
int rc;
sqlite3 *db;
sqlite3_stmt *res;
string query;

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

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

        onoff = what;
        query = "update glbheat set onoff = " + (what)?"1":"0";

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

        sqlite3_close(db);
        query = "POWER:" + (onoff) ? "ON;" : "OFF;";
        answer(query);
        return onoff;
}

double heating::getNight (int room)
{
HCONF *akt = findRoom(room);

        if (akt && akt->night > 0.0)
                return akt->night;

        return glb_night;
}

double heating::getMinimal (int room)
{
HCONF *akt = findRoom(room);

        if (akt && akt->minimal > 0.0)
                return akt->minimal;

        return glb_minimal;
}

void heating::destroyHConf()
{
HCONF *p, *next;

        p = HeatConf;

        while (p)
        {
                next = p->next;
                delete p;
                p = next;
        }

        HeatConf = nullptr;
}

void heating::getConfig (int s1)
{
HCONF *akt = HeatConf;
stringstream stream;

        stream << "CONFIG:GLOBAL NIGHT:" << setprecision(2) << glb_night << ";";
        write(s1, stream.str().c_str(), stream.str().length());
        stream << "CONFIG:GLOBAL MINIMAL:" << setprecision(2) << glb_minimal << ";";
        write(s1, stream.str().c_str(), stream.str().length());
        stream << "CONFIG:ONOFF:" << (onoff) ? "1;" : "0;";
        write(s1, stream.str().c_str(), stream.str().length());

        while (akt)
        {
                stream << "CONFIG:ROOM:" << akt->rnum << ":" << string(akt->rname) << ":" <<
                        setprecision(2) << akt->soll << ":" << akt->night << ":" <<
                        akt->minimal << ":" << timeToStr(akt->start) << ":" << timeToStr(akt->end) << ";";
                write(s1, stream.str().c_str(), stream.str().length());

                akt = akt->next;
        }
}

heating::HCONF* heating::findRoom (int room)
{
HCONF *akt = HeatConf;

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

                akt = akt->next;
        }

        return nullptr;
}

bool heating::readConf()
{
ifstream cfile;
char line[1024], hv0[256], hv1[128];
char *p;
CNFSTAT cs;
HCONF *akt = HeatConf;

        // If there's no config file, inform the user about it.
        cfile.open(Configure.heatconf, ios::in);
        
        if ((cfile.rdstate() & std::ifstream::failbit) != 0)
        {
                syslog(LOG_WARNING,"Error opening the config file: %s", strerror(errno));
                return false;
        }
        
        syslog(LOG_INFO, "Found config file %s.", Configure.heatconf);
        cs = NONE;
        // Here we've found a config file. We'll read it and set the
        // contents to the structure
        while (cfile.getline(&line[0], sizeof(line)))
        {
                int len;

                trim (&line[0]);
                
                if (line[0] == '#' || !char_traits<char>::length(line))
                        continue;

                if ((p = findc(line, char_traits<char>::length(line), ']')) != NULL)
                {
                        char *x;

                        *(p+1) = 0;

                        if (!compcase(line, "[global]"))
                        {
                                cs = GLOBAL;
                                continue;
                        }

                        if ((x = findc(line, char_traits<char>::length(line), '[')) != NULL)
                        {
                                *p = 0;
                                akt = appendHConf();
                                strncpy(akt->rname, x+1, sizeof(akt->rname) - 1);
                                akt->soll = 20.0;
                                akt->minimal = glb_minimal;
                                akt->night = glb_night;
                                cs = ROOM;
                                continue;
                        }
                        else
                                cs = NONE;
                }

                if ((p = findc(line, char_traits<char>::length(line), '=')) == NULL || cs == NONE)
                        continue;

                *p = 0;
                p++;
                len = char_traits<char>::length(line);

                if (len > (int)sizeof(hv0))
                        len = (int)sizeof(hv0) - 1;

                strncpy(hv0, line, len);
                hv0[len] = 0;
                trim (hv0);
                len = char_traits<char>::length(p);

                if (len > (int)sizeof(hv1))
                        len = (int)sizeof(hv0) - 1;

                strncpy(hv1, p, len);
                hv1[len] = 0;
                trim (hv1);

                if (!compcase(hv0, "minimal"))
                {
                        if (cs == GLOBAL)
                                glb_minimal = atof (hv1);
                        else
                                akt->minimal = atof (hv1);
                }

                if (!compcase(hv0, "night"))
                {
                        if (cs == GLOBAL)
                                glb_night = atof (hv1);
                        else
                                akt->night = atof (hv1);
                }

                if (!compcase(hv0, "OnOff") && cs == GLOBAL)
                        onoff = ToBool(hv1);

                if (cs == ROOM)
                {
                        if (!compcase(hv0, "soll"))
                                akt->soll = atoi(hv1);

                        if (!compcase(hv0, "Sensor"))
                                akt->tempsensor = atoi(hv1);

                        if (!compcase(hv0, "GPIO"))
                                akt->gpio = atoi(hv1);

                        if (!compcase(hv0, "StartTime"))
                                akt->start = strToTime(hv1);

                        if (!compcase(hv0, "EndTime"))
                                akt->end = strToTime(hv1);
                }
        }
        
        cfile.close ();
        return true;
}


bool heating::buildDB()
{
sqlite3 *db;
sqlite3_stmt *res;
int rc;
char *zErrMsg = nullptr;
string query;

        if (access(Configure.home, R_OK | W_OK))                // Try to create a new database if it doesn't exist
        {
                HCONF *akt;

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

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

                query = "create table \"main\".\"heating\" (";
                query.append("\"id\" INTEGER PRIMARY KEY NOT NULL,");
                query.append("\"name\" TEXT NOT NULL,");
                query.append("\"soll\" REAL,");
                query.append("\"night\" REAL,");
                query.append("\"minimal\" REAL,");
                query.append("\"start\" INTEGER,");
                query.append("\"end\" INTEGER");
                query.append(")");
                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 false;
                }
                else
                        syslog(LOG_INFO, "Database \"heating\" was successfully created.");

                // Insert the values from the config file into the table
                akt = HeatConf;
                
                while (akt)
                {
                        query = "insert into heating (id, name, soll, night, minimal, start, end) values (";
                        query.append(itostring(akt->rnum));
                        query.append(","+string(akt->rname));
                        query.append(","+doubleToString(akt->soll, 2));
                        query.append(","+doubleToString(akt->night, 2));
                        query.append(","+doubleToString(akt->minimal, 2));
                        query.append(","+itostring(akt->start));
                        query.append(","+itostring(akt->end));
                        query.append(")");
                        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 false;
                        }

                        akt = akt->next;

                        query = "create table \"main\".\"glbheat\" (";
                        query.append("\"night\" REAL,");
                        query.append("\"minimal\" REAL,");
                        query.append("\"onoff\" INTEGER");
                        query.append(")");
                        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 false;
                        }
                        else
                                syslog(LOG_INFO, "Database \"glbheat\" was successfully created.");

                        query = "insert into heating (night, minimal, onoff) values (";
                        query.append(doubleToString(glb_night, 2));
                        query.append(","+doubleToString(glb_minimal, 2));
                        query.append(","+itostring(onoff));
                        query.append(")");
                        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 false;
                        }
                }

                sqlite3_close(db);
        }
        else
        {
                HCONF *akt;
                
                rc = sqlite3_open(Configure.home, &db);
                
                if (rc)
                {
                        syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
                        return false;
                }

                akt = HeatConf;
                
                while (akt)
                {
                        query = "select id, name, soll, night, minimal, start, end from heating where id = "+itostring(akt->rnum);

                        if (sqlite3_prepare(db, query.c_str(), -1, &res, NULL) != SQLITE_OK)
                        {
                                syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), sqlite3_errmsg(db));
                                sqlite3_close(db);
                                return false;
                        }

                        if ((rc = sqlite3_step(res)) == SQLITE_ROW)
                        {
                                strncpy(akt->rname, (const char *)sqlite3_column_text(res, 1), sizeof(akt->rname) - 1);
                                akt->soll = sqlite3_column_double(res, 2);
                                akt->night = sqlite3_column_double(res, 3);
                                akt->minimal = sqlite3_column_double(res, 4);
                                akt->start = (time_t)sqlite3_column_int(res, 5);
                                akt->end = (time_t)sqlite3_column_int(res, 6);
                        }

                        akt = akt->next;
                }

                query = "select night, minimal, onoff from glbheat";
                
                if (sqlite3_prepare(db, query.c_str(), -1, &res, NULL) != SQLITE_OK)
                {
                        syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), sqlite3_errmsg(db));
                        sqlite3_close(db);
                        return false;
                }
                
                if ((rc = sqlite3_step(res)) == SQLITE_ROW)
                {
                        strncpy(akt->rname, (const char *)sqlite3_column_text(res, 1), sizeof(akt->rname) - 1);
                        glb_night = sqlite3_column_double(res, 0);
                        glb_minimal = sqlite3_column_double(res, 1);
                        onoff = (time_t)sqlite3_column_int(res, 2);
                }

                sqlite3_close(db);
        }

        return true;
}

void heating::setHandle (int fd)
{
        // First look into table to make sure the handle isn't already in the table
        for (int i = 0; i < 100; i++)
        {
                if (s1[i] == fd)
                        return;
        }

        // Because we're here, the handle is not in the table. So insert it now.
        for (int i = 0; i < 100; i++)
        {
                if (s1[i] < 0)
                {
                        s1[i] = fd;
                        break;
                }
        }
}

void heating::removeHandle (int fd)
{
        for (int i = 0; i < 100; i++)
        {
                if (s1[i] == fd)
                {
                        s1[i] = -1;
                        break;
                }
        }
}

void heating::answer (string msg)
{
        for (int i = 0; i < 100; i++)
        {
                if (s1[i] > 0)
                        write(s1[i], msg.c_str(), strlen(msg.c_str()));
        }
}

/*
 * This will work only with C++11 or newer!
 */
time_t heating::strToTime (char* str)
{
tm tm;

        std::istringstream ss(str);
        ss >> std::get_time(&tm, "%H:%M");
        return mktime(&tm);
}

string heating::timeToStr(time_t t)
{
tm *ltm = localtime(&t);

        string zeit = itostring(ltm->tm_hour) + ":" + itostring(ltm->tm_min);
        return zeit;
}

heating::HCONF* heating::appendHConf()
{
HCONF *akt, *p;

        akt = new HCONF;
        memset(akt, 0, sizeof(HCONF));

        if (HeatConf == nullptr)
                HeatConf = akt;
        else
        {
                p = HeatConf;

                while (p->next)
                        p = p->next;

                p->next = akt;
        }

        rm++;
        akt->rnum = rm;
        return akt;
}