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