Subversion Repositories heizung

Rev

Rev 10 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * (C) Copyright 2010 to 2015, 2024 by Andreas Theofilu <andreas@theosys.at>
 * All rights reserved!
 */
//#define SENSOR_ELV
#define SENSOR_ETHERNET

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <syslog.h>
#include <errno.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <fcntl.h>
#include <netdb.h>
#include <dirent.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include "sensor.h"
#include "usb_comm.h"
#include "heizung.h"

#ifndef ulong
#define ulong       unsigned long
#endif

typedef struct
{
    int wday;
    ulong start;
    ulong end;
    float temp;
} HEIZUNG;

typedef struct HEIZINDEX
{
    HEIZUNG *heizung;
    struct HEIZINDEX *next;
} HEIZINDEX;

struct SOCKETS
{
    int sockfd;
    int newfd;
    int valid;
};

CONFIGURE configs;
HEIZINDEX *HeizFirst;
float ActTemperature;
float ActPressure;
struct SOCKETS soc;
int killed = 0;

static pthread_t pthr_pars, pthr_temp_pars, pthr_process, pthr_run_change;

// Prototypes
void daemon_start(int ignsigcld);
void *pthr_temperature(void *pV_data);
void *pthr_parser(void *pV_data);
void sig_child(int);
void sendList(int s1);
int listSchemas(int s1);
int parseCommand(int s1, char *cmd);
void *processCommands(void *pV_data);
void changeToUser(char *, char *);
void *pthr_report(void *pV_data);

HEIZINDEX *allocateMemory(void);
HEIZINDEX *insertMemory(HEIZINDEX *pos);
void freeMemory(void);
HEIZINDEX *freeChainMember(HEIZINDEX *member);
void freeDay(int wday);
void insertMember(int wday, long endt, float temp);
char *makeFileName(char *ret, char *fname, int len);
int readHeizPlan(void);
int readHeizPlanName(char *fname);
int writeHeizPlan(void);
int writeHeizPlanName(char *fname);
void readConf(void);

HEIZINDEX *allocMem(HEIZINDEX *first);
int sortTable(void);

char *readLine(int fd, char *buf, int bufLen);
char *trim(char *str);
char *remove_string(char *str, char *search, char *ret);

//static pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
//static pthread_mutex_t fastmutex_ser = PTHREAD_MUTEX_INITIALIZER;
//static pthread_mutex_t fastmutex_proc = PTHREAD_MUTEX_INITIALIZER;

/*
 * The main program, initializing everything and start the daemon.
 */
int main(int argc, char *argv[])
{
    HeizFirst = NULL;
    memset(&soc, 0, sizeof(soc));

    readConf();
    // Initialize USB
    serialDev.fd = -1;
    serialDev.switch_fd = -1;
    serialDev.handle = NULL;
    // Now daemonize this application
    daemon_start(0);
    changeToUser(&configs.User[0], &configs.Grp[0]);

    // Now start our Thread
    if (pthread_create(&pthr_pars, NULL, pthr_parser, (void *)0) != 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Create of thread \"pthr_parser\" failed!");
        return 1;
    }

    pthread_detach(pthr_pars);

    // Here we start another thread to read the temperature
    if (pthread_create(&pthr_temp_pars, NULL, pthr_temperature, (void *)0) != 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Create of thread \"pthr_temperature\" failed!");
        pthread_cancel(pthr_pars);
        return 1;
    }

    pthread_detach(pthr_temp_pars);

    // At least we neeed a thread to send the on/off status whenever it changes.
    if (pthread_create(&pthr_run_change, NULL, pthr_report, (void *)0) != 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Create of thread \"pthr_report\" failed!");
        pthread_cancel(pthr_pars);
        pthread_cancel(pthr_temp_pars);
        return 1;
    }

    pthread_detach(pthr_run_change);

    while (!killed)
        sleep(1);

    unlink(configs.Pidfile);
    pthread_exit(NULL);
    return 0;
}

/*
 * Detach application from console and make it a daemon.
 */
void daemon_start(int ignsigcld)
{
    int childpid, fd;
    char hv0[64];

    if (getpid() == 1)
        goto out;

#ifdef SIGTTOU
    signal(SIGTTOU, SIG_IGN);
#endif
#ifdef SIGTTIN
    signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
    signal(SIGTSTP, SIG_IGN);
#endif

    if ((childpid = fork()) < 0)
        fprintf(stderr, "Can't fork this child\n");
    else if (childpid > 0)
        exit(0);

    if (setpgrp() == -1)
        fprintf(stderr, "Can't change process group\n");

    signal(SIGHUP, SIG_IGN);

    if ((childpid = fork()) < 0)
        syslog(LOG_DAEMON | LOG_ERR, "Can't fork second child");
    else if (childpid > 0)
        exit(0);             /* first child */

    /* second child */
out:

    for (fd = 0; fd < NOFILE; fd++)
        close(fd);

    errno = 0;
    chdir("/");
    umask(0);

    if (ignsigcld)
        signal(SIGCLD, SIG_IGN);

    signal(SIGTERM, sig_child);
    // Create PID file
    if ((fd = open(configs.Pidfile, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
    {
        syslog(LOG_DAEMON | LOG_WARNING, "Can't create PID file %s: %s", configs.Pidfile, strerror(errno));
        return;
    }

    sprintf(&hv0[0], "%d", getpid());
    write(fd, hv0, strlen(hv0));
    close(fd);
}

void sig_child(int)
{
    syslog(LOG_DAEMON | LOG_INFO, "Program was killed by signal 15!");
    killed = 1;
}

void changeToUser(char *usr, char *grp)
{
    gid_t gr_gid;

    if (usr && strlen(usr))
    {
        /* get uid */
        struct passwd *userpwd;
        struct group *usergrp;

        if ((userpwd = getpwnam(usr)) == NULL)
        {
            syslog(LOG_DAEMON | LOG_ERR, "no such user: %s", usr);
            exit(EXIT_FAILURE);
        }

        if (!grp || strlen(grp) || (usergrp = getgrnam(grp)) == NULL)
        {
            if (grp && strlen(grp))
                syslog(LOG_DAEMON | LOG_WARNING, "no such group: %s", grp);

            gr_gid = userpwd->pw_gid;
        }
        else
            gr_gid = usergrp->gr_gid;

        if (setgid(gr_gid) == -1)
        {
            syslog(LOG_DAEMON | LOG_ERR, "cannot setgid of user %s: %s", usr, strerror(errno));
            exit(EXIT_FAILURE);
        }

#ifdef _BSD_SOURCE
        /* init suplementary groups
         * (must be done before we change our uid)
         */
        if (initgroups(usr, gr_gid) == -1)
            syslog(LOG_DAEMON | LOG_ERR, "cannot init suplementary groups of user %s: %s", usr, strerror(errno));
#endif

        /* set uid */
        if (setuid(userpwd->pw_uid) == -1)
        {
            syslog(LOG_DAEMON | LOG_ERR, "cannot change to uid of user %s: %s\n", usr, strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (userpwd->pw_dir)
            setenv("HOME", userpwd->pw_dir, 1);
    }
}

void *pthr_temperature(void *pV_data)
{
    time_t t, tstart;
    struct tm *zeit, sz;
    int sleep_sec;

    // Initialize the serial port.
    serial_set_method((strlen(configs.Device)) ? 1 : 0, configs.VID, configs.PID);

    if (strlen(configs.Device))
        serial_set_device(configs.Device);

    if (!serial_open())
        return NULL;

    if (serialDev.switch_fd == -1)
    {
        if (!serial_open())
            return NULL;
    }

    sleep(1);   // Give the other thread time to initialize the structure

    while (!killed)
    {
        HEIZINDEX *act;

        GetTemp();
        sleep_sec = 300;

        // Compare the actual temperature with the one that should be
        // and switch heating on or off
        if (HeizFirst)
        {
            int wday;
            unsigned long loctm;

            t = time(NULL);
            sleep_sec = 300 - (int)((t + 300L) % 300L);
            sleep_sec++;
            zeit = localtime(&t);

            wday = (zeit->tm_wday == 0) ? 7 : zeit->tm_wday;
            memset(&sz, 0, sizeof(struct tm));
            sz.tm_min = 0;
            sz.tm_hour = 0;
            sz.tm_mon = zeit->tm_mon;
            sz.tm_mday = zeit->tm_mday;
            sz.tm_year = zeit->tm_year;
            sz.tm_isdst = zeit->tm_isdst;
            tstart = mktime(&sz);
            loctm = (t - tstart) + 1; // Seconds since midnight
            act = HeizFirst;

            while (act)
            {
                if (act->heizung->wday == wday && loctm >= act->heizung->start && loctm <= act->heizung->end)
                {
                    if (ActTemperature == 9999.0)
                        SwitchOff();     // No temperature, no heating
                    else if (ActTemperature < 5.0)
                        SwitchOn();      // Make sure it will not freeze
                    else if (ActTemperature > 30.0)
                        SwitchOff();     // Don't over heat
                    else if (ActTemperature <= (act->heizung->temp - 0.5))
                        SwitchOn();
                    else if (ActTemperature > act->heizung->temp)
                        SwitchOff();
                }

                act = act->next;
            }
        }
        else
            syslog(LOG_DAEMON | LOG_INFO, "Structure not initialized!");

        sleep(sleep_sec);        // Wait 5 Minutes
    }
}

void *processCommands(void *pV_data)
{
    char ch, buf[128];
    int i, s1, s;
    struct SOCKETS *soc;
    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    soc = (struct SOCKETS *)pV_data;
    s1 = soc->newfd;
    s = soc->sockfd;
    memset(&buf[0], 0, sizeof(buf));
    i = 0;
    int bytes = 0;
    setsockopt(s1, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);

    while ((bytes = read(s1, &ch, 1)) > 0 || bytes < 0)
    {
        if (killed)
            break;

        if ((bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) || bytes == 0)
            continue;
        else
            break;

        if (i < (sizeof(buf) - 1))
        {
            buf[i] = ch;

            if (ch == ';' || ch == 0x0d)
            {
                int pstat;

                if (!strncmp(buf, "quit", 4))
                    break;

                if ((pstat = parseCommand(s1, buf)) == 0)
                {
                    char hv0[128];

                    sprintf(&hv0[0], "INVALID COMMAND: %s;", buf);
                    write(s1, hv0, strlen(hv0));
                }
                else if (pstat == 2)
                    write(s1, "NAK;", 4);
                else
                    write(s1, "OK;", 3);

                memset(&buf[0], 0, sizeof(buf));
                i = 0;

                continue;
            }
        }

        i++;
    }

    soc->valid = 0;
    close(s1);
}

void *pthr_parser(void *pV_data)
{
    char str[INET_ADDRSTRLEN];
    // socket structure
    struct sockaddr_in client1, server1;
    struct servent *serviceInfo;
    int s1, s;
    socklen_t length;
    int optval = 1;

    memset(&client1, 0, sizeof(client1));
    memset(&server1, 0, sizeof(server1));
    // socket server
    if (configs.port > 0)
        server1.sin_port = htons(configs.port);
    else if ((serviceInfo = getservbyname("heizung", "tcp")))
        server1.sin_port = serviceInfo->s_port;
    else
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error: No TCP port defined!");
        exit(EXIT_FAILURE);
    }

    server1.sin_family = AF_INET;
    server1.sin_addr.s_addr = INADDR_ANY;

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error in socket: %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)) == -1)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error setting socket options: %s", strerror(errno));
        close(s);
        exit(EXIT_FAILURE);
    }

    if (bind(s, (struct sockaddr *)&server1, sizeof(server1)) < 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error in bind: %s", strerror(errno));
        close(s);
        exit(EXIT_FAILURE);
    }

    if (listen(s, 5) < 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error in listen: %s", strerror(errno));
        close(s);
        exit(EXIT_FAILURE);
    }

    length = sizeof(client1);
    syslog(LOG_DAEMON | LOG_DEBUG, "Server ready: %d", s);

    while (!killed)
    {
        // Read time table at every loop, to be sure to have the latest
        // version (User may change it at every time)
        if (!readHeizPlan())
        {
            if (!HeizFirst)
            {
                syslog(LOG_DAEMON | LOG_ERR, "Error reading table %s with plan!", configs.HeizPath);
                exit(EXIT_FAILURE);
            }
            else
                syslog(LOG_DAEMON | LOG_ERR, "Error reading table with plan! Will work with old one.");
        }

        if ((s1 = accept(s, (struct sockaddr *)&client1, &length)) < 0)
        {
            syslog(LOG_DAEMON | LOG_ERR, "Error in accept: %d: %s", s1, strerror(errno));
            continue;
        }

        inet_ntop(AF_INET, &(client1.sin_addr), str, INET_ADDRSTRLEN);
        syslog(LOG_DAEMON | LOG_INFO, "Connected to client %s", str);
        soc.sockfd = s;
        soc.newfd = s1;
        soc.valid = 1;

        if (pthread_create(&pthr_process, NULL, processCommands, (void *)&soc) != 0)
        {
            syslog(LOG_DAEMON | LOG_ERR, "Create of thread \"pthr_parser\" failed!");
            close(s1);
        }

        pthread_detach(pthr_process);
    }

    close(s1);
    close(s);
    serial_close();
    return NULL;
}

void *pthr_report(void *pV_data)
{
    int oldState = HeatStatus;
    char hv0[256];

    while(!killed)
    {
        if (soc.valid && oldState != HeatStatus)
        {
            snprintf(&hv0[0], sizeof(hv0), "HEATSTAT:%d;", HeatStatus);
            write(soc.newfd, &hv0[0], strlen(hv0));
            oldState = HeatStatus;
        }

        sleep(1);
    }

    return NULL;
}

void sendList(int s1)
{
    char hv0[256];
    int i, wday = 1;
    HEIZINDEX *act;

    // We will write out the week days in the correct order.
    // So we need 2 loops. The first one is runnung for every week day
    // and the 2nd one will find every entry for the actual week day.
    while (wday <= 7)
    {
        act = HeizFirst;
        i = 1;       // The line counter --> reset it to 1 for every week day

        while (act)
        {
            if (act->heizung->wday == wday)
            {
                sprintf(&hv0[0], "LINE:%d:%d:%lu:%lu:%.1f;", i, act->heizung->wday,
                        act->heizung->start, act->heizung->end, act->heizung->temp);
                write(s1, &hv0[0], strlen(hv0));
                i++;
            }

            act = act->next;
        }

        wday++;
    }
}

int listSchemas(int s1)
{
    char hv0[256];
    DIR *dir;
    struct dirent *de;
    char *ldup, *lp;

    if ((ldup = strdup(configs.HeizPath)) == NULL)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Not enough memory for path: %s", strerror(errno));
        return 2;
    }

    lp = dirname(ldup);

    if ((dir = opendir(lp)) == NULL)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Could not open directory %s: %s", lp, strerror(errno));
        free(ldup);
        return 2;
    }

    strcpy(&hv0[0], "SCHEMA START;");
    write(s1, hv0, strlen(hv0));

    while ((de = readdir(dir)) != NULL)
    {
        if (de->d_type == DT_REG && strstr(de->d_name, ".heat") != NULL)
        {
            char dn[256], *p;

            memset(&dn[0], 0, sizeof(dn));
            strncpy(dn, de->d_name, sizeof(dn) - 1);

            if ((p = strrchr(dn, '.')) != NULL)
                * p = 0;
            else
                continue;

            sprintf(&hv0[0], "SCHEMA FILE:%s;", dn);
            write(s1, hv0, strlen(hv0));
        }
    }

    closedir(dir);
    free(ldup);
    return 1;
}

int parseCommand(int s1, char *cmd)
{
    char bef[32], par[1024], *p;
    char hv0[256];
    int i, j;
    HEIZINDEX *act, *last;

    memset(bef, 0, sizeof(bef));
    memset(par, 0, sizeof(par));

    if ((p = strchr(cmd, ':')) != NULL)     // do we have a parameter?
    {
        strncpy(bef, cmd, p - cmd);
        strncpy(par, p + 1, strlen(p) - 2); // Cut off the trailing semi colon
    }
    else
        strncpy(bef, cmd, strlen(cmd) - 1);

    if (!strcasecmp(bef, "LIST"))       // Write out current list
        sendList(s1);

    if (!strcasecmp(bef, "GET WDAY"))       // Write out a particular week day
    {
        int wday = atoi(par);

        if (wday < 1 || wday > 7)
        {
            strcpy(&hv0[0], "ERROR:Invalid week day");
            write(s1, &hv0[0], strlen(hv0));
            return 0;
        }

        act = HeizFirst;

        while (act && act->heizung->wday != wday)
            act = act->next;

        if (!act)
        {
            sprintf(&hv0[0], "ERROR:No plan for week day %d", wday);
            write(s1, &hv0[0], strlen(hv0));
            return 0;
        }

        i = 1;

        while (act && act->heizung->wday == wday)
        {
            sprintf(&hv0[0], "LINE:%d:%d:%lu:%lu:%.1f;", i, act->heizung->wday,
                    act->heizung->start, act->heizung->end, act->heizung->temp);
            write(s1, &hv0[0], strlen(hv0));
            i++;
            act = act->next;
        }
    }

    if (!strcasecmp(bef, "GET TEMP"))   // Return actual temperature
    {
        sprintf(&hv0[0], "TEMP:%.1f;", ActTemperature);
        write(s1, &hv0[0], strlen(hv0));
    }

    if (!strcasecmp(bef, "GET PRESSURE"))   // Return the actual air pressure
    {
        sprintf(&hv0[0], "PRESSURE:%.1f;", ActPressure);
        write(s1, &hv0[0], strlen(hv0));
    }

    if (!strcasecmp(bef, "HEATSTAT"))   // Return the status of the heating
    {
        sprintf(&hv0[0], "HEATSTAT:%d;", HeatStatus);
        write(s1, &hv0[0], strlen(hv0));
    }

    // SET DAY:<count>:<day>:<end1>:<temp>[:<end2>:<temp>[:...]];
    // <count>   number of entries following
    // <day>     The day of the week
    // <end1>    The end time
    // <temp>    The temperature wanted until end time is reached
    //
    if (!strcasecmp(bef, "SET DAY"))        // Set the plan for a particular day
    {
        int count, wday, i;
        long endt;
        float temp;

        count = atoi(par);
        remove_string(par, ":", &hv0[0]);
        wday = atoi(par);
        freeDay(wday);
        last = NULL;

        if (count > 0)
        {
            for (i = 0; i < count; i++)
            {
                remove_string(par, ":", &hv0[0]);
                endt = atol(par);
                remove_string(par, ":", &hv0[0]);
                temp = atof(par);

                if ((act = allocateMemory()) == NULL)
                {
                    syslog(LOG_DAEMON, "Error allocating memory for a temperature line.");
                    sprintf(&hv0[0], "ERROR:Not enough memory!");
                    write(s1, &hv0[0], strlen(hv0));
                    return 2;
                }

                act->heizung->wday = wday;
                act->heizung->end = endt;
                act->heizung->temp = temp;

                if (last)
                    act->heizung->start = last->heizung->end;
                else
                    act->heizung->start = 0L;

                last = act;
            }

            writeHeizPlan();
        }
    }

    // SET TEMP:<wday>:<end>:<temp>;
    if (!strcasecmp(bef, "SET TEMP"))   // Set the temperature for a particular day and line
    {
        int wday;
        unsigned long endt;
        float tmp;

        wday = atoi(par);
        remove_string(par, ":", &hv0[0]);
        endt = atol(par);
        remove_string(par, ":", &hv0[0]);
        tmp = atof(par);
        insertMember(wday, endt, tmp);
        writeHeizPlan();
    }

    // SAVE SCHEMA:<name>;
    if (!strcasecmp(bef, "SAVE SCHEMA"))    // Save the current heating plan into a file
    {
        sprintf(&hv0[0], "%s.heat", par);

        if (!writeHeizPlanName(hv0))
            return 2;
    }

    // LOAD SCHEMA:<name>;
    if (!strcasecmp(bef, "LOAD SCHEMA"))    // Load a previous saved schema from a file
    {
        sprintf(&hv0[0], "%s.heat", par);

        if (!readHeizPlanName(hv0))
            return 2;

        // write the new schema immediately to the default file
        writeHeizPlan();
        sendList(s1);
    }

    // DELETE SCHEMA:<name>;
    if (!strcasecmp(bef, "DELETE SCHEMA"))  // Delete an existing schema
    {
        makeFileName(&hv0[0], par, sizeof(hv0));

        if (strlen(hv0) < (sizeof(hv0) - 6))
            strcat(&hv0[0], ".heat");
        else
        {
            syslog(LOG_DAEMON, "File name too large for internal buffer");
            return 2;
        }

        if (access(hv0, R_OK | W_OK))
        {
            syslog(LOG_DAEMON, "No access to file %s: %s", hv0, strerror(errno));
            return 2;
        }

        if (unlink(hv0) == -1)
        {
            syslog(LOG_DAEMON, "Error deleting file %s: %s", hv0, strerror(errno));
            return 2;
        }

        return listSchemas(s1);
    }

    // LIST SCHEMA;
    if (!strcasecmp(bef, "LIST SCHEMA"))    // Return a list of all available schamas
        return listSchemas(s1);

    return 1;
}

/*
 * Remove a complete day
 */
void freeDay(int wday)
{
    HEIZINDEX *act, *next, *prev;

    act = HeizFirst;
    prev = NULL;

    while (act)
    {
        if (act->heizung->wday == wday)
            act = freeChainMember(act);

        act = act->next;
    }
}

/*
 * Insert a new entry
 */
void insertMember(int wday, long endt, float temp)
{
    HEIZINDEX *act;

    act = HeizFirst;

    while (act)
    {
        if (act->heizung->wday == wday && act->heizung->end == endt)
        {
            act->heizung->temp = temp;
            return;
        }

        act = act->next;
    }

    if ((act = allocateMemory()) != NULL)
    {
        act->heizung->wday = wday;
        act->heizung->end = endt;
        act->heizung->temp = temp;
    }
}

/*
 * Free the memory for the actual heizung plan.
 */
void freeMemory()
{
    HEIZINDEX *act, *next;

    act = HeizFirst;

    while (act)
    {
        if (act->heizung)
        {
            free(act->heizung);
            act->heizung = NULL;
        }

        next = act->next;
        free(act);
        act = next;
    }

    HeizFirst = NULL;
}

/*
 * Free only one entry in the chain
 */
HEIZINDEX *freeChainMember(HEIZINDEX *member)
{
    HEIZINDEX *act, *prev;

    act = HeizFirst;
    prev = NULL;

    while (act != member)
    {
        prev = act;
        act = act->next;
    }

    if (act == member)
    {
        if (prev)
            prev->next = act->next;
        else
        {
            prev = act->next;

            if (act == HeizFirst)
                HeizFirst = act->next;
        }

        if (act->heizung)
            free(act->heizung);

        free(act);
        return prev;
    }

    return NULL;
}

/*
 * Allocate the memory for the actual heizung plan.
 * This function appends an element to the end of the chain.
 */
HEIZINDEX *allocateMemory()
{
    HEIZINDEX *act, *last;

    if (!HeizFirst)
    {
        HeizFirst = malloc(sizeof(HEIZINDEX));

        if (HeizFirst)
        {
            HeizFirst->heizung = malloc(sizeof(HEIZUNG));
            HeizFirst->next = NULL;
        }
        else
            return NULL;

        return HeizFirst;
    }
    else
    {
        // Find last element
        last = HeizFirst;

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

        act = malloc(sizeof(HEIZINDEX));

        if (act)
        {
            act->heizung = malloc(sizeof(HEIZUNG));
            act->next = NULL;
            last->next = act;
        }
        else
            return NULL;

        return act;
    }

    return NULL;
}

/*
 * Allocate the memory for the actual heizung plan.
 * This function inserts an element into the chain.
 */
HEIZINDEX *insertMemory(HEIZINDEX *pos)
{
    HEIZINDEX *act;

    if (!HeizFirst)
    {
        HeizFirst = malloc(sizeof(HEIZINDEX));

        if (HeizFirst)
        {
            HeizFirst->heizung = malloc(sizeof(HEIZUNG));
            HeizFirst->next = NULL;
        }
        else
            return NULL;

        return HeizFirst;
    }
    else
    {
        act = malloc(sizeof(HEIZINDEX));

        if (act)
        {
            act->heizung = malloc(sizeof(HEIZUNG));
            act->next = pos->next;
            pos->next = act;
        }
        else
            return NULL;

        return act;
    }

    return NULL;
}

/*
 * The following functions read a config file and put the
 * contents into a structure.
 */
void readConf(void)
{
    int fd;
    char confFile[512], line[512];
    char *home, *p;
    char hv0[64], hv1[128];

    home = getenv("HOME");
    fd = -1;

    if (!access("/etc/heizung.conf", R_OK))
        strcpy(confFile, "/etc/heizung.conf");
    else if (!access("/etc/heizung/heizung.conf", R_OK))
        strcpy(confFile, "/etc/heizung/heizung.conf");
    else if (!access("/usr/etc/heizung.conf", R_OK))
        strcpy(confFile, "/usr/etc/heizung.conf");
    else if (home)
    {
        strcpy(confFile, home);
        strcat(confFile, "/.heizung.conf");

        if (access(confFile, R_OK))
        {
            syslog(LOG_DAEMON | LOG_WARNING, "Even config file %s was not found!", confFile);
            confFile[0] = 0;
        }
    }
    else
        confFile[0] = 0;

    memset(&configs, 0, sizeof(configs));
    strcpy(configs.User, "nobody");
    strcpy(configs.Grp, "nobody");
    strcpy(configs.HeizPath, "/var/www/.HeizPlan.conf");
    strcpy(configs.Werte, "/var/log/werte.dat");
    strcpy(configs.Device, "/dev/ttyS1");
    strcpy(configs.Pidfile, "/run/heizung.pid");
#ifdef SENSOR_ETHERNET
    strcpy(configs.IP, "0.0.0.0");
#endif
    configs.port = 11001;

    if (!confFile[0] || (fd = open(confFile, O_RDONLY)) == -1)
    {
        if (confFile[0])
            syslog(LOG_DAEMON | LOG_WARNING, "Error opening the config file %s! Using built in defaults. (%s)", confFile, strerror(errno));
        else
            syslog(LOG_DAEMON | LOG_WARNING, "Error opening the config file! Using built in defaults.");

        return;
    }

    while (readLine(fd, &line[0], sizeof(line)) != NULL)
    {
        int len;

        trim(&line[0]);

        if (line[0] == '#' || !strlen(line))
            continue;

        if ((p = strchr(line, '=')) == NULL)
            continue;

        *p = 0;
        p++;
        len = strlen(line);

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

        strncpy(hv0, line, len);
        hv0[len] = 0;
        trim(hv0);
        len = strlen(p);

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

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

        if (!strcasecmp(hv0, "user"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"user\": %s", hv1);
            strncpy(configs.User, hv1, sizeof(configs.User));
        }

        if (!strcasecmp(hv0, "group"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"group\": %s", hv1);
            strncpy(configs.Grp, hv1, sizeof(configs.Grp));
        }

        if (!strcasecmp(hv0, "port"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"port\": %s", hv1);
            configs.port = atoi(hv1);
        }

        if (!strcasecmp(hv0, "heizpath"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"heizpath\": %s", hv1);
            strncpy(configs.HeizPath, hv1, sizeof(configs.HeizPath));
        }

        if (!strcasecmp(hv0, "Werte"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"Werte\": %s", hv1);
            strncpy(configs.Werte, hv1, sizeof(configs.Werte));
        }

        if (!strcasecmp(hv0, "Device"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"Device\": %s", hv1);
            strncpy(configs.Device, hv1, sizeof(configs.Device));
        }

        if (!strcasecmp(hv0, "Pidfile"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found \"Pidfile\": %s", hv1);
            strncpy(configs.Pidfile, hv1, sizeof(configs.Pidfile));
        }

        if (!strcasecmp(hv0, "VID"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found VendorID: %04x", atoi(hv1));
            configs.VID = atoi(hv1);
        }

        if (!strcasecmp(hv0, "PID"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found ProductID: %04x", atoi(hv1));
            configs.PID = atoi(hv1);
        }

#ifdef SENSOR_ETHERNET
        if (!strcasecmp(hv0, "IP"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found IP: %s", hv1);
            strncpy(configs.IP, hv1, sizeof(configs.IP));
        }

        if (!strcasecmp(hv0, "TempPort"))
        {
            syslog(LOG_DAEMON | LOG_INFO, "Found temperature network port %d", atoi(hv1));
            configs.TempPort = atoi(hv1);
        }
#endif
    }

    close(fd);
}

char *makeFileName(char *ret, char *fname, int len)
{
    char *fdup, *pright, *cfg, *lcfg;

    if (ret == NULL || fname == NULL || !len || !strlen(fname))
        return NULL;

    if (!access(fname, R_OK | W_OK))
    {
        memset(ret, 0, len);
        strncpy(ret, fname, len - 1);
        return ret;
    }

    fdup = strdup(fname);

    if (fdup == NULL)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error allocating memory: %s", strerror(errno));
        return NULL;
    }

    cfg = strdup(configs.HeizPath);

    if (cfg == NULL)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error allocating memory: %s", strerror(errno));
        free(fdup);
        return NULL;
    }

    pright = basename(fdup);
    lcfg = dirname(cfg);
    memset(ret, 0, len);
    strncpy(ret, lcfg, len - 1);

    if (strlen(ret) < (len - (strlen(pright) + 2)))
    {
        strcat(ret, "/");
        strcat(ret, pright);
        free(fdup);
        free(cfg);
        return ret;
    }

    free(fdup);
    free(cfg);
    return NULL;
}

int readHeizPlan(void)
{
    return readHeizPlanName(configs.HeizPath);
}

int readHeizPlanName(char* fname)
{
    int fd, i, wday;
    ulong tim;
    char line[512];
    char *p, *xp;
    char hv0[64], hv1[128];
    char path[512];
    float temperature;
    int counter = 0;
    HEIZINDEX *act, *prev;

    fd = -1;

    if (makeFileName(&path[0], fname, sizeof(path)) == NULL)
    {
        syslog(LOG_DAEMON | LOG_ERR, "No or invalid path %s", fname);
        return 0;
    }

    if (access(path, R_OK))
    {
        syslog(LOG_DAEMON | LOG_ERR, "Access to file %s denied: %s", path, strerror(errno));
        return 0;
    }

    if ((fd = open(path, O_RDONLY)) == -1)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error opening file %s: %s", path, strerror(errno));
        return 0;
    }

    act = prev = NULL;
    freeMemory();

    while (readLine(fd, &line[0], sizeof(line)) != NULL)
    {
        int len;

        trim(&line[0]);

        if (line[0] == '#' || !strlen(line) || strchr(line, ',') == NULL)
            continue;

        // We need a place to store the information
        prev = act;

        if ((act = allocateMemory()) == NULL)
        {
            close(fd);
            syslog(LOG_DAEMON | LOG_ERR, "Error allocating memory for a temperature line! Stopped reading file %s.", configs.HeizPath);
            return 0;
        }

        counter++;
        memset(act->heizung, 0, sizeof(HEIZUNG));
        // Parse a line. The line has the format:
        // <wday>,<end>,<temperature>
        p = strtok(line, ",");
        i = 1;

        while (p)
        {
            switch (i)
            {
                case 1:    // Week day
                    wday = atoi(p);

                    if (wday < 1 || wday > 7)   // valid line?
                    {
                        if (prev)
                            wday = prev->heizung->wday;

                        if (wday < 1 || wday > 7)
                        {
                            p = strtok(NULL, ",");
                            i++;
                            continue;
                        }
                    }

                    act->heizung->wday = wday;
                    act->heizung->start = 0;
                    break;

                case 2:    // start/end time
                    if ((xp = strchr(p, ':')) != NULL)
                    {
                        int hour, min;

                        hour = atoi(p);
                        min = atoi(xp + 1);

                        if (hour >= 0 && hour <= 23 && min >= 0 && min <= 59)
                        {
                            act->heizung->end = hour * 3600 + min * 60 + 59;

                            if (prev && prev->heizung->wday == act->heizung->wday)
                                act->heizung->start = prev->heizung->end;
                        }
                    }

                    break;

                case 3:    // temperature
                    temperature = atof(p);

                    if (temperature < 5.0 || temperature > 30.0)
                    {
                        p = strtok(NULL, ",");
                        i++;
                        continue;
                    }

                    act->heizung->temp = temperature;
                    break;
            }

            p = strtok(NULL, ",");
            i++;
        }
    }

    syslog(LOG_DAEMON | LOG_INFO, "Found %d entries in %s", counter, configs.HeizPath);
    close(fd);
    return 1;
}

/*
 * write the (may be) altered plan.
 * This function allways writes whole plan.
 */
int writeHeizPlan()
{
    return writeHeizPlanName(configs.HeizPath);
}

int writeHeizPlanName(char *fname)
{
    int fd, wday;
    ulong lastEnd;
    char hv0[128], path[512];
    HEIZINDEX *act;

    fd = -1;

    if (makeFileName(&path[0], fname, sizeof(path)) == NULL)
    {
        syslog(LOG_DAEMON, "No or invalid path %s", fname);
        return 0;
    }

    if (!sortTable())
        return 0;

    if ((fd = open(path, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Error opening/creating file %s: %s", path, strerror(errno));
        return 0;
    }

    // Sort table for week days
    for (wday = 1; wday <= 7; wday++)
    {
        lastEnd = 0L;
        act = HeizFirst;

        while (act)
        {
            if (act->heizung->wday == wday)
            {
                int hour, min;

                hour = act->heizung->end / 3600;
                min = (act->heizung->end - (hour * 3600)) / 60;

                if (hour > 23)
                {
                    hour = 23;
                    min = 59;
                }

                sprintf(&hv0[0], "%d,%02d:%02d,%.1f\n",
                        act->heizung->wday, hour, min,
                        act->heizung->temp);
                write(fd, &hv0[0], strlen(hv0));
            }

            act = act->next;
        }
    }

    close(fd);
    return 1;
}

/*
 * Allocate the memory for the actual heizung plan.
 * This function appends an element to the end of the chain.
 */
HEIZINDEX *allocMem(HEIZINDEX *first)
{
    HEIZINDEX *act, *last;

    if (!first)
    {
        first = malloc(sizeof(HEIZINDEX));

        if (first)
        {
            first->heizung = malloc(sizeof(HEIZUNG));
            first->next = NULL;
        }
        else
            return NULL;

        return first;
    }
    else
    {
        // Find last element
        last = first;

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

        act = malloc(sizeof(HEIZINDEX));

        if (act)
        {
            act->heizung = malloc(sizeof(HEIZUNG));
            act->next = NULL;
            last->next = act;
        }
        else
            return NULL;

        return act;
    }

    return NULL;
}

// This function sorts the table for week days and every into increasing
// time stamps
int sortTable()
{
    int count, actElement, wday, flag;
    ulong end;
    HEIZINDEX *first, *act, *p;

    if (!HeizFirst)
        return 0;

    p = HeizFirst;
    // Count the number of elements
    count = 0;

    while (p)
    {
        if (p->heizung->wday)
            count++;

        p = p->next;
    }

    wday = 1;
    act = first = NULL;
    actElement = 0;

    // First loop sorts for week days
    while (actElement < count && wday <= 7)
    {
        int cnt, pos;

        p = HeizFirst;
        flag = 0;
        // find number of entries for actual week day
        cnt = 0;
        act = HeizFirst;

        while (act)
        {
            if (act->heizung->wday == wday)
                cnt++;

            act = act->next;
        }

        pos = 0;
        end = 0L;

        // Second loop sorts the actual week day for end times
        while (p)
        {
            if (p->heizung->wday == wday && p->heizung->start == end && p->heizung->end > end)
            {
                actElement++;
                flag = 1;
                act = allocMem(first);

                if (!first)
                    first = act;

                // Check for valid memory block
                if (act)
                {
                    memmove(act->heizung, p->heizung, sizeof(HEIZUNG));
                    end = p->heizung->end;
                    pos++;
                }
                else
                {
                    syslog(LOG_DAEMON | LOG_ERR, "Error allocationg memory for sorting table: %s", strerror(errno));
                    // Free allocated memory
                    act = first;

                    while (act)
                    {
                        p = act->next;
                        free(act->heizung);
                        free(act);
                        act = p;
                    }

                    return 0;
                }
            }

            p = p->next;

            // If there are still members left, reset pointer and restart loop
            if (!p && pos < (cnt - 1) && flag)
            {
                p = HeizFirst;
                flag = 0;
            }
        }

        wday++;
    }

    freeMemory();
    HeizFirst = first;
    return 1;
}

char *readLine(int fd, char *buf, int bufLen)
{
    int i, end;
    char ch, *p;

    if (fd <= 0)
    {
        syslog(LOG_DAEMON | LOG_ERR, "Function readLine was called with an invalid file descriptor of %d!", fd);
        return NULL;
    }

    i = end = 0;
    p = buf;

    while (read(fd, &ch, 1) > 0)
    {
        end = 1;

        if (ch == 0x0a)
        {
            *p = 0;
            return buf;
        }

        if (ch == 0x0d)      // ignore this!
            continue;

        if (i < (bufLen - 1))
        {
            *p = ch;
            p++;
            i++;
        }
    }

    *p = 0;

    if (end)
        return buf;
    else
        return NULL;
}

char *trim(char *str)
{
    char *p1, *p2, *p;

    if (!str)
        return NULL;

    if (!strlen(str))
        return str;

    p = str;
    p1 = p2 = NULL;

    while (*p)
    {
        if (!p1 && *p != ' ')
        {
            p1 = p;
            break;
        }

        p++;
    }

    p2 = str + (strlen(str) - 1);

    while (p2 > str && *p2 == ' ')
        p2--;

    if (p2)
        *(p2 + 1) = 0;

    if (p1)
    {
        char *buf = strdup(p1);
        strcpy(str, buf);
        free(buf);
    }

    return str;
}

char *remove_string(char *str, char *search, char *ret)
{
    char *p;

    if (!strlen(str) || !strlen(search))
        return NULL;

    if ((p = strstr(str, search)) != NULL)
    {
        int len = strlen(search);

        strncpy(ret, str, p - str + len);
        ret[p - str + len] = 0;
        memmove(str, p + len, strlen(p + len));
        str[strlen(p + len)] = 0;
        return ret;
    }

    return NULL;
}