/* DS1620/1626 Temperature-Deamon
 * Written by Michael "Fox" Meier in 2003, heavily updated in 2005.
 * Based on the Dallas/MaximIC C examples for reading their chip, and their
 * documentation on how to increase the resolution of the chip.
 * Both of them are available on their homepage.
 * Note that according to Dallas/MaximIC, the ds1620 is obsolete, and has
 * been replaced by a newer model, the ds1626, which does not require the
 * tricks used here to read the temperature at a resolution of <0.5 degrees.
 * There also seem to be (at least) three different versions of the DS1620,
 * that need different "tricks" to access high resolution temperature data.
 * The oldest chips only support the way that was mentioned in the old
 * application note 68, which required writing an undocumented command to
 * access an internal register. In newer revisions, they made that register
 * accessible like any other register, and documented it, but you could still
 * access it in the "old" way instead. And finally, the newest revisions of
 * the ds1620 chips no longer support the "old" way, you have to read the
 * normal register there.
 * That's why there are two types, "DS1620a" and "DS1620b" below. The first
 * one uses the old protocol, the second uses the new protocol. You should
 * try "b" first, and only resort to "a" if it doesn't work.
 * In theory this program also supports the DS1626 now, but it's untested.
 *
 * This program comes without any warranty.
 *
 * Compile with:
 *   gcc -O -o dstempd dstempd.c
 * Do NOT forget the -O, without it gcc does not know how to do outb or inb,
 * as these are inlined macros.
 */

/* ================================================================
 * Settings.
 * ================================================================
 */

/* We use the following cabling:
 * Data0 Power
 * Data5 CLK
 * Data6 /RST
 * Data7 DQ
 * Readback through "ACK"
 * That leads to the following macros. If you change the cabling,
 * you'll have to adapt the macros too. */
#define CLK_HI    p_data |= 0x20; outb(p_data, p_addr); usleep(slpdelay);
#define CLK_LOW   p_data &= 0xDF; outb(p_data, p_addr); usleep(slpdelay);
#define RSTB_HI   p_data |= 0x40; outb(p_data, p_addr); usleep(slpdelay);
#define RSTB_LOW  p_data &= 0xBF; outb(p_data, p_addr); usleep(slpdelay);
#define DQ_HI     p_data |= 0x80; outb(p_data, p_addr); usleep(slpdelay);
#define DQ_LOW    p_data &= 0x7F; outb(p_data, p_addr); usleep(slpdelay);
#define READ_BACK ((inb(p_addr + 1) & 0x40) >> 6)

/* Do you want IPv6 support? (change define to undef if you don't want to) */
#define USEIPV6

/* Next are some default settings. These are only used if no conflicting
 * command line parameters are used. */

/* Address of the parallel port - LPT1=0x378, LPT2=0x278*/
#define DEFPADDR 0x378
/* The port on which we listen */
#define DEFPORT 7337
/* sleepdelay - basically sets the maximum bus clock (in microseconds)
 * Should be set to 1000 from at least kernel 2.6.28 on, and 10 with old kernels. */
#define DEFSLPDELAY 1000
/* updatedelay - Since reading the sensor takes around 6 seconds, we only 
 * update the actual data after this many seconds, else we give back the
 * old result. */
#define DEFUPDDELAY 10
/* What chip do we have? Can be either DS1620a, DS1620b or DS1626 */
#define DEFCHIP DS1620b

/* ------------------- END OF SETTINGS ------------------
 * You should not need to touch anything below this line.
 * ------------------------------------------------------
 */

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/io.h>
#include <string.h>

#define DS1620a 1
#define DS1620b 2 /* Newer revisions of the chip */
#define DS1626  3

#define DSTEMPDVERSION "1.2"

#ifndef DEFCHIP
#error "NO DEFAULTCHIP SELECTED"
#endif
#if ((DEFCHIP != DS1620a) && (DEFCHIP != DS1620b) && (DEFCHIP != DS1626))
#error "INVALID DEFAULTCHIP SELECTED. Do not mess with the source, Luke, if you don't know what you're doing."
#endif

unsigned short int p_addr = DEFPADDR;
unsigned short int lport = DEFPORT;
unsigned short int slpdelay = DEFSLPDELAY;
unsigned short int upddelay = DEFUPDDELAY;
unsigned char chiptype = DEFCHIP;
unsigned char * logfilen = (unsigned char *)0;

unsigned char p_data = 0x00;

void sigpipehandler(int bla) { /* Dummyhandler for catching the event */
	return;
}

void dson(void) {
	p_data = 0x01;
	outb(p_data, p_addr);
}

/* Drives RSTB signal high (state=1) or low (state=0) */
void rb3w(int state) {
	if (state == 0) {
		RSTB_LOW
	} else {
		RSTB_HI
	}
}

/* Drives CLK signal high (state=1) or low (state=0) */
void c3w(int state) {
	if (state == 0) {
		CLK_LOW
	} else {
		CLK_HI
	}
}

/* Drives DQ signal high (state=1) or low (state=0)  */
void dq3w(int state) {
	if (state == 0) {
		DQ_LOW
	} else {
		DQ_HI
	}
}

/* Writes a 0 (w_bit=0) or a 1 (w_bit=1) to a three wire device. */
void write_bit(int w_bit) {
	if (w_bit == 0) {
		dq3w(0);
	} else {
		dq3w(1);
	}
	c3w(0);
	c3w(1);
	dq3w(1);
}

/* Reads a 0 (return 0) or a 1 (return 1) from a three wire device. */
int read_bit() {
	int i;
	c3w(0);
	i = READ_BACK;
	c3w(1);
	return(i);
}

/* write_part
Write a command and data to a 3-wire part. param is the integer value of the data to be
written
after the write protocol. n_bits is the number of bits to transfer in param.
*/
void write_part(int protocol, int param, int n_bits) {
	int index, data;
	rb3w(1); /* Raise the /RST line   */
	for (index = 0; index < 8; index++) { /* protocol is 8 bits */
		data = protocol >> index;
		data &= 0x01;
		write_bit(data);
	}
	for (index = 0; index < n_bits; index++) { /* Write out data */
		data = param >> index;
		data &= 0x01;
		write_bit(data);
	}
	rb3w(0); /* Drop /RST line */
}

/* read_part
 * Reads data from a 3-wire part. protocol is command to be written to the part,
 * and n_bits is number of bits to read after the protocol is sent. Returns int value
 * of reading.
 */
int read_part(int protocol, int n_bits) {
	int index, data;
	int r_data = 0;
	rb3w(1); /* Raise the /RST line */
	for (index = 0; index < 8; index++) { /* protocol is 8 bits */
		data = protocol >> index;
		data &= 0x01;
		write_bit(data);
	}
	for (index = 0; index < n_bits; index++) { /* Read in data */
		r_data |= read_bit() << index;
	}
	rb3w(0);
	return(r_data);
}

void putlog(const char *template, ...) {
	time_t ts;
	char sts[100];
	FILE *lf;
	va_list ap;
	struct tm brt;
	
	if (logfilen == (unsigned char *)0) {
		return;
	}
	if (strcmp(logfilen, "-") == 0) {
		lf = stdout;
	} else {
		lf = fopen(logfilen, "a");
		if (lf == NULL) {
			fprintf(stderr, "ERROR: Could not open logfile %s!\n", logfilen);
			return;
		}
	}
	ts = time(NULL);
	brt = *localtime(&ts);
	if (strftime(sts, 100, "%d.%m.%Y %H:%M:%S", &brt) == 0) {
		fprintf(stderr, "ERROR in %s:%d: strftime failed!\n", __FILE__, __LINE__);
		return;
	}
	fprintf(lf, "[%s]\t", sts);
	va_start(ap, template);
	vfprintf(lf, template, ap);
	va_end(ap);
	fprintf(lf, "\n");
	if (lf != stdout) {
		fclose(lf);
	}
}


void logaccess(struct sockaddr * soa, int soalen, unsigned char * txt) {
	struct sockaddr_in * sav4;
#ifdef USEIPV6
	struct sockaddr_in6 * sav6;

	if (soalen == sizeof(struct sockaddr_in6)) {
		sav6 = (struct sockaddr_in6 *)soa;
		if ((sav6->sin6_addr.s6_addr[ 0] == 0)
		 && (sav6->sin6_addr.s6_addr[ 1] == 0)
		 && (sav6->sin6_addr.s6_addr[ 2] == 0)
		 && (sav6->sin6_addr.s6_addr[ 3] == 0)
		 && (sav6->sin6_addr.s6_addr[ 4] == 0)
		 && (sav6->sin6_addr.s6_addr[ 5] == 0)
		 && (sav6->sin6_addr.s6_addr[ 6] == 0)
		 && (sav6->sin6_addr.s6_addr[ 7] == 0)
		 && (sav6->sin6_addr.s6_addr[ 8] == 0)
		 && (sav6->sin6_addr.s6_addr[ 9] == 0)
		 && (sav6->sin6_addr.s6_addr[10] == 0xFF)
		 && (sav6->sin6_addr.s6_addr[11] == 0xFF)) {
			/* This is really a IPv4 not a V6 access, so log it as
			 * a such. */
			putlog("%d.%d.%d.%d\t%s", sav6->sin6_addr.s6_addr[12],
			        sav6->sin6_addr.s6_addr[13],
			        sav6->sin6_addr.s6_addr[14],
			        sav6->sin6_addr.s6_addr[15], txt);
		} else {
			/* True IPv6 access */
			putlog("%x:%x:%x:%x:%x:%x:%x:%x\t%s",
			        (sav6->sin6_addr.s6_addr[ 0] << 8) | sav6->sin6_addr.s6_addr[ 1],
			        (sav6->sin6_addr.s6_addr[ 2] << 8) | sav6->sin6_addr.s6_addr[ 3],
			        (sav6->sin6_addr.s6_addr[ 4] << 8) | sav6->sin6_addr.s6_addr[ 5],
			        (sav6->sin6_addr.s6_addr[ 6] << 8) | sav6->sin6_addr.s6_addr[ 7],
			        (sav6->sin6_addr.s6_addr[ 8] << 8) | sav6->sin6_addr.s6_addr[ 9],
			        (sav6->sin6_addr.s6_addr[10] << 8) | sav6->sin6_addr.s6_addr[11],
			        (sav6->sin6_addr.s6_addr[12] << 8) | sav6->sin6_addr.s6_addr[13],
			        (sav6->sin6_addr.s6_addr[14] << 8) | sav6->sin6_addr.s6_addr[15],
			        txt);
		}
	} else
#endif
	if (soalen == sizeof(struct sockaddr_in)) {
		unsigned char brokeni32[4];
		
		sav4 = (struct sockaddr_in *)soa;
		brokeni32[0] = (sav4->sin_addr.s_addr & 0xFF000000UL) >> 24;
		brokeni32[1] = (sav4->sin_addr.s_addr & 0x00FF0000UL) >> 16;
		brokeni32[2] = (sav4->sin_addr.s_addr & 0x0000FF00UL) >>  8;
		brokeni32[3] = (sav4->sin_addr.s_addr & 0x000000FFUL) >>  0;
		putlog("%d.%d.%d.%d\t%s", brokeni32[0], brokeni32[1],
		        brokeni32[2], brokeni32[3], txt);
	} else {
		putlog("!UNKNOWN_ADDRESS_TYPE!\t%s", txt);
	}
}

void showhelp(char * argv0) {
	printf("temperature deamon for DS162X temperature probe chips\n");
	printf("Version %s, compiled on %s %s IPv6 Support\n",
	       DSTEMPDVERSION, __DATE__,
#ifdef USEIPV6
	       "with"
#else
	       "without"
#endif
	       );
	printf("Written by Michael 'Fox' Meier 2003/2005\n");
	printf("Syntax: %s [-c chiptype] [-p port] [-l addr] [-d updatedelay]\n", argv0);
	printf("           [-s sleep] [-f logfile] [-h]\n");
	printf("  -c chiptype    sets the type of chip used. Defaults to ds1620b.\n");
	printf("                 valid values for chiptype are:\n");
	printf("                 ds1620a  - very old revisions of the ds1620 chip\n");
	printf("                 ds1620b  - current revisions of the ds1620 chip.\n");
	printf("                 ds1626   - ds1626 chip\n");
	printf("  -p port        sets the port number on which the deamon listens\n");
	printf("                 for connections. default = %d\n", DEFPORT);
	printf("  -l addr        I/O address of the parallel port to use, in hexadecimal.\n");
	printf("                 Default = 0x%x\n", DEFPADDR);
	printf("  -d updatedelay specify updatedelay in seconds. Default = %d.\n", DEFUPDDELAY);
	printf("                 Since reading the chip takes up to 6 seconds, the\n");
	printf("                 chip is only reread after this many seconds, not for\n");
	printf("                 every connection.\n");
	printf("  -s sleep       this basically sets how fast the communication with\n");
	printf("                 the temperature probe is clocked, by setting the\n");
	printf("                 delay in micro-seconds after each change on a line.\n");
	printf("                 You should not need to change the default of %d\n", DEFSLPDELAY);
	printf("  -f logfilename Gives the name of a logfile, where connections\n");
	printf("                 will be logged.\n");
	printf("  -h             shows this help text\n");
}

int main(int argc, char **argv) {
	int listsock;
	int acsock;
	pid_t ourpid;
	char outbuf[512];
	time_t lastupd = (time_t) 0;
	int optval;
	int soalen;
	int confreg;
	int confmask;
#ifdef USEIPV6
	struct sockaddr_in6 soa;
#else
	struct sockaddr_in soa;
#endif

	printf("dstempd V%s\n", DSTEMPDVERSION);
	if (argc > 1) {
		int argpos = 1;
		while (argpos < argc) {
			if (strcmp(argv[argpos], "--help") == 0) {
				showhelp(argv[0]);
				exit(0);
			} else if (strcmp(argv[argpos], "-h") == 0) {
				showhelp(argv[0]);
				exit(0);
			} else if (strcmp(argv[argpos], "-c") == 0) {
				argpos++;
				if (argpos < argc) {
					if (strcmp(argv[argpos], "ds1620a") == 0) {
						chiptype = DS1620a;
					} else if (strcmp(argv[argpos], "ds1620b") == 0) {
						chiptype = DS1620b;
					} else if (strcmp(argv[argpos], "ds1626") == 0) {
						chiptype = DS1626;
					} else {
						printf("Error: Invalid chiptype %s\n", argv[argpos]);
						exit(1);
					}
				} else {
					printf("Error: Option -c requires a parameter\n");
					exit(1);
				}
			} else if (strcmp(argv[argpos], "-p") == 0) {
				argpos++;
				if (argpos < argc) {
					lport = strtoul(argv[argpos], NULL, 10);
				} else {
					printf("Error: Option -p requires a parameter\n");
					exit(1);
				}
			} else if (strcmp(argv[argpos], "-l") == 0) {
				argpos++;
				if (argpos < argc) {
					p_addr = strtoul(argv[argpos], NULL, 16);
				} else {
					printf("Error: Option -l requires a parameter\n");
					exit(1);
				}
			} else if (strcmp(argv[argpos], "-d") == 0) {
				argpos++;
				if (argpos < argc) {
					upddelay = strtoul(argv[argpos], NULL, 10);
				} else {
					printf("Error: Option -d requires a parameter\n");
					exit(1);
				}
			} else if (strcmp(argv[argpos], "-s") == 0) {
				argpos++;
				if (argpos < argc) {
					slpdelay = strtoul(argv[argpos], NULL, 10);
				} else {
					printf("Error: Option -s requires a parameter\n");
					exit(1);
				}
			} else if (strcmp(argv[argpos], "-f") == 0) {
				argpos++;
				if (argpos < argc) {
					logfilen = argv[argpos];
				} else {
					printf("Error: Option -f requires a parameter\n");
					exit(1);
				}
			} else {
				printf("Error: Unknown command line option: %s\n", argv[argpos]);
				printf("Run %s -h to see a list of valid options.\n", argv[0]);
				exit(1);
			}
			argpos++;
		}
	}
	printf("Using parallel port at I/O address 0x%x\n", p_addr);
	if (ioperm(p_addr, 2, 1) != 0) {
		perror("ioperm");
		printf("Could not get I/O permissions for I/O address range 0x%x - 0x%x\n",
		       p_addr, p_addr+2);
		printf("Please check that no driver blocks (uses) this range,\n");
		printf("and that you have root priviledges\n");
		return 1;
	}
	dson();
	printf("Initializing the ");
	if (chiptype == DS1626) {
		printf("DS1626");
	} else if (chiptype == DS1620b) {
		printf("DS1620 (new revisions)");
	} else {
		printf("DS1620 (old revisions)");
	}
	printf(" chip...\n");
	confreg = read_part(0xAC, 8);
	if (chiptype == DS1626) {
		/* Init the DS1626 to one-shot and cpu mode with maximum resolution */
		confmask = 0x0F;
	} else {
		/* Init the DS1620 to one-shot and cpu mode */
		confmask = 0x03;
	}
	if ((confreg & confmask) == confmask) {
		printf("Chip already configured for maximum resolution\n");
	} else {
		printf("Configuring chip for maximum resolution... ");
		write_part(0x0C, confmask, 8);
		sleep(1); /* Let it finish writing to EEPROM */
		confreg = read_part(0xAC, 8);
		if ((confreg & confmask) == confmask) {
			printf("[OK]\n");
		} else {
			printf("[!FAILED!]\n");
			printf("Warning: The chip doesn't seem to accept the setting.\n");
			printf("         Temperature measurement will most likely not work.\n");
			printf("         Check cable connections and chiptype.\n");
			printf("Continuing anyways...\n");
		}
	}
	printf("Answering queries on Port %d (IPv4 ", lport);
#ifdef USEIPV6
	printf("+ IPv6)\n");
	listsock = socket(PF_INET6, SOCK_STREAM, 0);
	soa.sin6_family = AF_INET6;
	soa.sin6_addr = in6addr_any;
	soa.sin6_port = htons(lport);
#else
	printf("only)\n");
	listsock = socket(PF_INET, SOCK_STREAM, 0);
	soa.sin_family = AF_INET;
	soa.sin_addr.s_addr = INADDR_ANY;
	soa.sin_port = htons(lport);
#endif
	optval = 1;
	setsockopt(listsock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
	if (bind(listsock, (struct sockaddr *)&soa, sizeof(soa)) < 0) {
		perror("Bind failed");
		return(-3);
	}
	if (listen(listsock, 20) < 0) { /* Large Queue as we block while reading the sens! */
		perror("Listen failed");
		return(-2);
	}
	/* The good old double-fork() trick from "Systemprogrammierung 1" */
	printf("launching into the background...\n");
	ourpid = fork();
	if (ourpid < 0) {
		perror("Ooops, fork() #1 failed");
		return(1);
	}
	if (ourpid == 0) {
		ourpid = fork();
		if (ourpid < 0) {
			perror("Ooooups. fork() #2 failed");
			return(1);
		}
		if (ourpid == 0) {
			struct sigaction sia;
			if (ioperm(p_addr, 2, 1)!=0) { /* We have to do this
				* again, because ioperm gets lost on fork(),
				* at least on linux 2.2 */
				perror("Could not (re-)get I/O Permissions");
				return 1;
			}
			sia.sa_handler = sigpipehandler;
			sigemptyset(&sia.sa_mask); /* If we don't do this, we're likely */
			sia.sa_flags = 0;          /* to die from 'broken pipe'! */
			sigaction(SIGPIPE, &sia, NULL);
			strcpy(outbuf, "ERROR!");
			soalen = sizeof(soa);
			while ((acsock = accept(listsock, (struct sockaddr *)&soa, &soalen)) > 0) {
				if ((lastupd + upddelay) < time(NULL)) {
					int repc = 0;
					int r;
					lastupd = time(NULL);
					if (chiptype == DS1626) {
						write_part(0x51, 0, 0);
					} else {
						write_part(0xEE, 0, 0);
					}
					do {
						r = read_part(0xAC, 8);
						repc++;
					} while (((r & 0x80) != 0x80) && (repc < 100));
					strcpy(outbuf, "ERROR!");
					if (repc < 100) {
						int tempread;
						float exacttemp;
						if (chiptype == DS1626) {
							tempread = read_part(0xAA, 12);
							/* printf("Raw value read: %X\n", tempread); */
							if (tempread > 0x7FF) {
								tempread -= 0x1000;
							}
							exacttemp = (float)tempread * 0.0625;
							sprintf(outbuf, "%6.2f", exacttemp);
						} else {
							int countremain, countperc;
							tempread = read_part(0xAA, 9);
							tempread >>= 1;
							countremain = read_part(0xA0, 9);
							if (chiptype == DS1620b) {
								countperc = read_part(0xA9, 9);
							} else {
								write_part(0x41, 0, 0);
								countperc = read_part(0xA0, 9);
							}
							/* printf("Raw values read: %X %X %X\n", tempread, countremain, countperc); */
							if (tempread > 0x7F) {
								tempread -= 0x100;
							}
							if (countperc > 0) {
								exacttemp = (float)tempread - 0.25
									  + ((float)countperc - (float)countremain)
									    / (float)countperc;
								if ((tempread != -1) || (countperc != 0x1FF) || (countremain != 0x1FF)) {
									sprintf(outbuf, "%6.2f", exacttemp);
								}
							}
						}
					}
				}
				write(acsock, outbuf, strlen(outbuf));
				close(acsock);
				logaccess((struct sockaddr *)&soa, soalen, outbuf);
				soalen = sizeof(soa);
			}
			perror("Exiting due to error on accept()");
			return(errno);
		}
	}
	return 0;
}

