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