Subversion Repositories heating

Rev

Rev 54 | 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 <vector>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <csignal>
#include <syslog.h>
#include <cerrno>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <pwd.h>
#include <grp.h>
#include <bcm2835.h>
#include <sys/resource.h>
#include "config.h"
#include "heating.h"

struct SOCKETS
{
        int sockfd;
        int newfd;
        char ip[256];
};

void daemon_start (int ignsigcld);
void sig_handler(int sig);
void changeToUser(const char *usr, const char *grp);
void sig_child ();

void *pthr_parser(void *pV_data);
void *pthr_Heat(void * pV_data);
void *processCommands(void *pV_data);
int parseCommand(int s1, char *buf);

static pthread_t pthr_pars, pthr_heat, pthr_process;
static pthread_mutex_t fastmutex_proc = PTHREAD_MUTEX_INITIALIZER;

heating *heat;
struct SOCKETS soc;                             // The network sockets to be passed to a thread

int main(int argc, char **argv) 
{
struct rlimit core_limits;

        heat = nullptr;
        bcmInitialized = false;

        // First read config file and check if it was ok
        config *cfg = new config();
        
        if (!cfg->is_initialized())
        {
                std::cerr << "Error reading config file! Make sure there is one and that it is readable by root!" << std::endl;
                delete (cfg);
                return 1;
        }
        
        CONFIGURE configs = cfg->getConfig();
        // We must initialize the BCM2835 library here, as long as we are root!
        if (!bcm2835_init())
                syslog(LOG_WARNING, "Error initializing the BCM2835 library! The GPIO ports will not function.");
        else
                bcmInitialized = true;

        // Daemonize and run in background
        daemon_start(0);
        changeToUser(configs.user, configs.group);
        // core dumps may be disallowed by parent of this process; change that
        if (Configure.debug)
        {
                core_limits.rlim_cur = core_limits.rlim_max = RLIM_INFINITY;
                setrlimit(RLIMIT_CORE, &core_limits);
        }
        // We no longer need the configuration data here, so we remove them.
        delete (cfg);
        /* Prepare the thread attributes */
        if (pthread_attr_init(&pattr) != 0)
        {
                syslog(LOG_DAEMON,"Error getting thread attributes.");
                return 0;
        }

        if (pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED) != 0)
        {
                syslog(LOG_DAEMON,"Error setting thread attributes.");
                return 0;
        }
        
        // Now start our Thread
        if (pthread_create(&pthr_pars, NULL, pthr_parser, (void *)0) != 0)
        {
                syslog (LOG_DAEMON,"Create of thread \"pthr_parser\" failed!");
                return 2;
        }

        pthread_join(pthr_pars, NULL);                  // Wait for just started thread to end

/*      while (1)
                sleep(3600);
*/
        return 0;
}

/*
 * Detach application from console and make it a daemon.
 */
void daemon_start (int ignsigcld)
{
        int childpid, fd;
        char hv0[128];
        
        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, "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);

        // Define a signal handler for terminate signals
        if (signal(SIGTERM, sig_handler) == SIG_ERR)
                syslog(LOG_WARNING,"Can't catch signal SIGTERM!");

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

        sprintf(hv0, "%d", getpid());
        write(fd, hv0, strlen(hv0));
        close(fd);
}

void sig_child ()
{
#if defined(BSD) && !defined(sinix) && !defined(Linux)
        int pid;
        union wait status;

        while ((pid = wait3 (&status, WNOHANG, (struct rusage *)0)) > 0)
                ;
#endif
}

/*
 * This is the signal handler who disconnects from network and terminates
 * this daemon
 */
void sig_handler(int sig)
{
        if (sig == SIGTERM || sig == SIGKILL)
        {
                syslog(LOG_INFO, "Terminating program! Killed by signal %d", sig);

                if (heat != nullptr)
                {
                        heat->stopRun();
                        sleep(2);
                }

                if (bcmInitialized)
                        bcm2835_close();

                exit(0);
        }
}

void changeToUser(const char *usr, const 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,"no such user: %s", usr);
                        exit(EXIT_FAILURE);
                }

                if (!grp || !strlen(grp) || (usergrp = getgrnam(grp)) == NULL)
                {
                        if (grp && strlen(grp))
                                syslog(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,"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,"Cannot init suplementary groups of user %s: %s", usr, strerror(errno));
#endif

                /* set uid */
                if (setuid(userpwd->pw_uid) == -1)
                {
                        syslog(LOG_DAEMON,"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_Heat(void * pV_data)
{
        pthread_mutex_lock (&fastmutex_proc);
        
        if (heat != nullptr && !heat->statusRun())
                heat->run();
        
        pthread_mutex_unlock(&fastmutex_proc);
}

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

        // socket server
        if (Configure.port > 0)
                server1.sin_port = htons(Configure.port);
        else if ((serviceInfo = getservbyname ("heating", "tcp")))
                server1.sin_port = serviceInfo->s_port;
        else
        {
                syslog(LOG_DAEMON,"Error: No TCP port defined!");
                pthread_exit(NULL);
        }

        server1.sin_family = AF_INET;
        server1.sin_addr.s_addr = INADDR_ANY;
        s = socket(AF_INET,SOCK_STREAM,0);

        if (s < 0)
        {
                syslog (LOG_DAEMON, "Error in socket: %s",strerror(errno));
                pthread_exit(NULL);
        }

        if (bind (s, (struct sockaddr *)&server1, sizeof (server1)) < 0)
        {
                syslog (LOG_DAEMON, "Error in bind: %s", strerror(errno));
                pthread_exit(NULL);
        }

        if (listen (s, 5) < 0)
        {
                syslog (LOG_DAEMON, "Error in listen: %s", strerror(errno));
                pthread_exit(NULL);
        }

        length = sizeof(client1);

        if (Configure.debug)
                syslog (LOG_DEBUG, "Server ready: %d",s);
        // Initialize and start the probe
        if (heat == nullptr)
                heat = new heating();

        if (pthread_create(&pthr_heat, &pattr, pthr_Heat, (void *)0) != 0)
        {
                syslog (LOG_DAEMON,"Create of thread \"pthr_heat\" failed!");
                close (s);
                pthread_exit(NULL);
        }
        
        if (Configure.debug)
                syslog(LOG_DEBUG, "Heating was initialized and is polling ...");
        
        while (heat->statusRun())
        {
                if ((s1 = accept (s, (struct sockaddr *)&client1, &length)) < 0)
                {
                        syslog (LOG_DAEMON, "Error in accept: %d: %s", s1, strerror(errno));
                        continue;
                }
                
                inet_ntop(AF_INET, &(client1.sin_addr), str, INET_ADDRSTRLEN);
                
                if (Configure.debug)
                        syslog (LOG_DEBUG, "Connected to client %s", str);
                
                soc.ip[0] = 0;
                soc.sockfd = s;
                soc.newfd = s1;
                
                // This thread will parse the commands comming to this daemon.
                if (pthread_create(&pthr_process, &pattr, processCommands, (void *)&soc) != 0)
                {
                        syslog (LOG_DAEMON,"Create of thread \"processCommands\" failed!");
                        close(s1);
                        soc.newfd = 0;
                }
        }

        close (s);
        soc.sockfd = 0;
        delete heat;
        heat = nullptr;
        pthread_exit(NULL);
}

void *processCommands(void *pV_data)
{
char ch, buf[128];
int i, s1, s;
bool initialized = false;
struct SOCKETS *socket;

        socket = (struct SOCKETS *)pV_data;
        s1 = socket->newfd;
        s = socket->sockfd;
        heat->setHandle(s1);

        if (Configure.debug)
                syslog(LOG_DEBUG, "Starting to process commands ...");

        i = 0;
        strcpy(buf, "HEATING:READY;");
        write(s1, buf, strlen(buf));
        memset(&buf[0], 0, sizeof(buf));
        
        while (heat->statusRun() && read(s1,&ch,1) > 0)
        {
                if (i < (int)(sizeof(buf) - 1))
                {
                        buf[i] = ch;
                        
                        if (ch < 0x0a || ch == 0x0b || ch == 0x0c)
                                continue;

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

                                buf[i] = 0;

                                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++;
        }

        heat->removeHandle(s1);
        close(s1);
        socket->newfd = 0;
}

int parseCommand(int s1, char *buf)
{
std::string line(buf);
char hv0[64];
std::string vname, vcontent;
helper help;

        if (line.find(":") != std::string::npos)
        {
                vname = line.substr(0, line.find(":"));
                vcontent = line.substr(line.find(":") + 1);

                if (Configure.debug)
                        syslog(LOG_DEBUG, "Command: %s, Content: %s", vname.c_str(), vcontent.c_str());
        }
        else
        {
                vname = line;
                vcontent = "";

                if (Configure.debug)
                        syslog(LOG_DEBUG, "Command only: %s", vname.c_str());
        }

        // INCREMENT:<room>;
        if (vname.compare("INCREMENT") == 0 && vcontent.length() > 0)
        {
                int room = atoi(vcontent.c_str());
                heat->incSoll(room);
                return 1;
        }

        // DECREMENT:<room>;
        if (vname.compare("DECREMENT") == 0 && vcontent.length() > 0)
        {
                int room = atoi(vcontent.c_str());
                heat->decSoll(room);
                return 1;
        }

        // GET CONFIG;
        if (vname.compare("GET CONFIG") == 0)
        {
                heat->getConfig(s1);
                return 1;
        }

        // POWER:<ON|OFF>;
        if (vname.compare("POWER") == 0)
        {
                if (vcontent.compare("ON") == 0)
                        heat->switchOnOff(true);
                else
                        heat->switchOnOff(false);

                return 1;
        }

        // SET SOLL:<room>:<soll>; --> Solltemperatur direkt setzen
        if (vname.compare("SET SOLL") == 0)
        {
                std::vector<std::string> s = help.split(vcontent, ':');

                if (s.size() != 2)
                        return 0;

                int room = atoi(s.at(0).c_str());
                double soll = atof(s.at(1).c_str());
                
                if (!room || soll < 10.0 || soll > 30.0)
                        return 0;
                
                heat->setSoll(room, soll);
        }

        // SET NIGHT:<room>:<soll>;
        if (vname.compare("SET NIGHT") == 0)
        {
                std::vector<std::string> s = help.split(vcontent, ':');

                if (s.size() != 2)
                        return 0;

                int room = atoi(s.at(0).c_str());
                double soll = atof(s.at(1).c_str());

                if (room < 0 || soll < 10.0 || soll > 30.0)
                        return 0;

                heat->setNight(room, soll);
        }

        // SET MINIMAL:<room>:<soll>;
        if (vname.compare("SET MINIMAL") == 0)
        {
                std::vector<std::string> s = help.split(vcontent, ':');
                
                if (s.size() != 2)
                        return 0;
                
                int room = atoi(s.at(0).c_str());
                double soll = atof(s.at(1).c_str());
                
                if (room < 0 || soll < 10.0 || soll > 30.0)
                        return 0;
                
                heat->setMinimal(room, soll);
        }

        // NIGHT:<soll>;
        if (vname.compare("NIGHT") == 0)
        {
                double soll = atof(vcontent.c_str());
                heat->setNight(0, soll);
        }

        // MINIMAL:<soll>;
        if (vname.compare("MINIMAL") == 0)
        {
                double soll = atof(vcontent.c_str());
                heat->setMinimal(0, soll);
        }

        return 0;
}