/*****************************
* Heat Control program *
* Last updated: 18 Oct 2014 *
* Author: Mateusz Viste *
*****************************
Change log:
[18 Oct 2014]
- Fixed segfaults occuring when data files do not exist
- Slot changes are applied within a few seconds (was: 5 minutes later)
[24 Nov 2013]
- Rewritten in ANSI C.
[11 Nov 2009]
- Added support for a 4th zone.
[18 Oct 2009]
- First public release.
*/
/* This struct will hold all our 'shared' variables */
struct heatdata {
enum zonestate WeekProgram[4][7][96];
enum zonestate ForcedZone[4];
enum zonestate CurZoneState[4];
time_t ForcedTime[4];
time_t LastConfReload;
time_t LastForceCheck;
int portaddr;
int activeslot;
};
/* Declare all routines and functions */
void LoadWeekProgram(struct heatdata *data) {
char tmpstring[128];
int x, y, z;
FILE *fd;
/* load weekly data */
snprintf(tmpstring, 128, "/etc/heatcontrol/%d.cfg", data->activeslot);
fd = fopen(tmpstring, "r");
if (fd == NULL) return;
for (x = 0; x < 4; x++) { /* Zones 1-4 */
for (y = 0; y < 7; y++) { /* Weekdays 1-7 */
for (z = 0; z < 96; z++) { /* Time spaces 1-96 (24h) */
fscanf(fd, "%u", &(data->WeekProgram[x][y][z]));
}
}
}
fclose(fd);
}
int getActiveSlot(void) {
FILE *fd;
int slot = 0;
/* fetch the active slot number */
fd = fopen("/etc/heatcontrol/active.cfg", "r");
if (fd != NULL) {
fscanf(fd, "%d", &slot);
fclose(fd);
}
return(slot);
}
/* Returns the week of the day (0-6, where 0 = Monday) */
int CurrentWeekDay(time_t t) {
int result;
result = localtime(&t)->tm_wday;
if (result == 0) result = 7;
result -= 1;
return(result);
}
/* returns the current 15-minute slice of the day (0..95) */
int CurrentTimeSlice(time_t curtime) {
int result;
struct tm *brokentime;
brokentime = localtime(&curtime);
result = brokentime->tm_hour * 60;
result += brokentime->tm_min;
result /= 15; /* compute the Nth 15-minutes slice of the day */
return(result);
}
void CheckForConfUpdate(struct heatdata *data, time_t curtime) {
int x;
FILE *fd;
/* Check for forced modes every 15 seconds */
if ((curtime - data->LastForceCheck) > 15) {
data->LastForceCheck = curtime;
fd = fopen("/etc/heatcontrol/forcing", "r");
if (fd == NULL) return;
for (x = 0; x < 4; x++) {
fscanf(fd, "%u", &(data->ForcedZone[x]));
}
for (x = 0; x < 4; x++) {
fscanf(fd, "%ld", &(data->ForcedTime[x]));
}
fclose(fd);
}
/* reload the configuration every 5 minutes, or as soon a new slot is
* activated. */
x = getActiveSlot();
if ((x != data->activeslot) || ((curtime - data->LastConfReload) > 300)) {
data->activeslot = x;
data->LastConfReload = curtime;
LoadWeekProgram(data);
}
}
void UpdateCurState(struct heatdata *data) {
FILE *fd;
int x;
fd = fopen("/etc/heatcontrol/curstate.dat", "w");
if (fd == NULL) return;
for (x = 0; x < 4; x++) {
fprintf(fd, "%d\n", data->CurZoneState[x]);
}
fclose(fd);
}
void UpdateParPort(struct heatdata *data) {
/* Update the parallel port status */
int ByteDriver = 0;
int x;
for (x = 0; x < 4; x++) {
/* Prepare the bit fields for all zones */
switch (data->CurZoneState[x]) {
case MODE_OFF: /* Off */
ByteDriver |= (2 << (x * 2));
break;
case MODE_NOFREEZE: /* Hors Gel */
ByteDriver |= (1 << (x * 2));
break;
case MODE_ECO: /* Eco */
ByteDriver |= (3 << (x * 2));
break;
case MODE_COMFORT: /* Comfort */
/* No bit to set. Leave at zero. */
break;
case MODE_INIT: /* NOOP */
break;
}
}
/* Send the byte to the parallel port now */
outb(ByteDriver, data->portaddr);
}
void ParPortTestProcedure(int portaddr) {
outb(0, portaddr); /* sends the byte 0 to LPT port (clear all pins) */
sleep(1);
outb(255, portaddr); /* All pins to ON */
sleep(1);
outb(0, portaddr); /* sends the byte 0 to LPT port (clear all pins) */
sleep(5); /* wait 5 seconds before starting the test */
outb(1, portaddr);
sleep(2);
outb(2, portaddr);
sleep(2);
outb(4, portaddr);
sleep(2);
outb(8, portaddr);
sleep(2);
outb(16, portaddr);
sleep(2);
outb(32, portaddr);
sleep(2);
outb(64, portaddr);
sleep(2);
outb(128, portaddr);
sleep(2);
outb(0, portaddr); /* sends the byte 0 to LPT port (clear all pins) */
}
/* Here starts the real program */
int main(int argc, char **argv) {
struct heatdata *data;
enum zonestate OldZoneState[4];
time_t curtime;
int curwday, curtslice, x;
if (argc != 2) {
printf("Invalid number of arguments!\n");
printf("Example: heatcontrol 0x3BC\n");
return(4);
}
/* allocate memory for variables */
data = calloc(1, sizeof(struct heatdata));
if (data == NULL) {
printf("Failed to allocate memory. Out of memory?\n");
return(3);
}
/* Load current zone states to init values */
for (x = 0; x < 4; x++) data->CurZoneState[x] = MODE_INIT;
/* Read the port address, and convert it into decimal. */
data->portaddr = strtol(argv[1], NULL, 16);
if (data->portaddr < 1) {
printf("Invalid port argument. Example: heatcontrol 0x3BC\n");
printf("Program aborted.\n");
return(1);
}
/* Load the heating configuration */
data->activeslot = getActiveSlot();
LoadWeekProgram(data);
printf("HeatControl started on hardware port 0x%03X\n", data->portaddr);
if (ioperm(data->portaddr, 1, 1) != 0) { /* turn on access to port xxxx */
printf("ERROR: ioperm() failed on 0x%03X (%s)\n", data->portaddr, strerror(errno));
return(2);
}
ParPortTestProcedure(data->portaddr);
for (;;) {
sleep(5); /* Wait five seconds before re-looping */
curtime = time(NULL);
curwday = CurrentWeekDay(curtime);
curtslice = CurrentTimeSlice(curtime);
CheckForConfUpdate(data, curtime);
/* printf("CurrentWeekDay(): %d / CurrentTimeSlice: %d\n", curwday, curtslice); */
for (x = 0; x < 4; x++) {
/* Save current zones states to a temporary buffer and refresh current value */
OldZoneState[x] = data->CurZoneState[x];
data->CurZoneState[x] = data->WeekProgram[x][curwday][curtslice];
/* check if the zone is forced currently */
if (data->ForcedTime[x] - curtime > 0) data->CurZoneState[x] = data->ForcedZone[x];
/* printf("Zone %d = %d\n", x, data->CurZoneState[x]); */
}
/* Now check if anything changed - if so, send an update to the LPT port */
if ((data->CurZoneState[0] != OldZoneState[0]) || (data->CurZoneState[1] != OldZoneState[1]) || (data->CurZoneState[2] != OldZoneState[2]) || (data->CurZoneState[3] != OldZoneState[3])) {
UpdateCurState(data);
UpdateParPort(data);
}
}
}