/**
 * Copyright (c) 1992-2009 Stefan Bethke. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * $Schlepperbande: src/temper/temper.c,v 1.3 2010/01/01 20:27:13 stb Exp $
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <err.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sysexits.h>
 
#define TEMPER_LM75_ADDRESS 0x9e
 
#define LM75_POINTER_TEMPERATURE	0
#define LM75_POINTER_CONTROL		1
#define LM75_POINTER_T_HYST			2
#define LM75_POINTER_T_OS			3
 
#define LM75_CONTROL_POLARITY			0x04
#define LM75_CONTROL_RESOLUTION_09BIT	0x00
#define LM75_CONTROL_RESOLUTION_10BIT	0x20
#define LM75_CONTROL_RESOLUTION_11BIT	0x40
#define LM75_CONTROL_RESOLUTION_12BIT	0x60
 
/*
 * Basic I2C bit-banging functions. iic_start(), iic_read_bit(), and iic_write_bit()
 * always end just before the falling edge of SCL is due.
 */
 
struct iic_port {
	int fd;
	int sleep;
};
 
#if defined(IIC_DEBUG)
char scl_debug[1024];
char sdo_debug[1024];
char sdi_debug[1024];
 
static void
debug_init() {
	scl_debug[0] = '\0';
	sdo_debug[0] = '\0';
	sdi_debug[0] = '\0';
}
 
static void
debug_add(int scl, int sdo, int sdi)
{
	strcat(scl_debug, scl ? "\xe2\x80\xbe" : "_");
	strcat(sdo_debug, sdo ? "\xe2\x80\xbe" : "_");
	strcat(sdi_debug, sdi ? "\xe2\x80\xbe" : "_");
}
 
static void
debug_marker()
{
	strcat(scl_debug, "\xc2\xb7");
	strcat(sdo_debug, "\xc2\xb7");
	strcat(sdi_debug, "\xc2\xb7");
}
 
static void
debug_print()
{
	printf("\nscl %s\nsdo %s\nsdi %s\n", scl_debug, sdo_debug, sdi_debug);
	debug_init();
}
#endif
 
static int
iic_scl_sda(struct iic_port *iic, int scl, int sda)
{
	int s;
	if (ioctl(iic->fd, TIOCMGET, &s) < 0)
		err(EX_OSERR, "ioctl(TIOCMGET)");
	if (scl)
		s |= TIOCM_DTR;
	else
		s &= ~TIOCM_DTR;
	if (sda)
		s |= TIOCM_RTS;
	else
		s &= ~TIOCM_RTS;
	if (ioctl(iic->fd, TIOCMSET, &s) < 0)
		err(EX_OSERR, "ioctl(TIOCMSET)");
	s = (s & TIOCM_CTS) ? 1 : 0;
#if defined(IIC_DEBUG)
	debug_add(scl, sda, s);
#endif
	return s;
}
 
static void
iic_start(struct iic_port *iic)
{
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	iic_scl_sda(iic, 1, 1);
	usleep(iic->sleep);
	iic_scl_sda(iic, 1, 0);
	usleep(iic->sleep);
}
 
static void
iic_stop(struct iic_port *iic)
{
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	iic_scl_sda(iic, 0, 0);
	usleep(iic->sleep);
	iic_scl_sda(iic, 1, 0);
	usleep(iic->sleep);
	iic_scl_sda(iic, 1, 1);
	usleep(iic->sleep);
}
 
static int
iic_read_bit(struct iic_port *iic)
{
	int v;
 
	iic_scl_sda(iic, 0, 1);
	usleep(iic->sleep);
	v = iic_scl_sda(iic, 1, 1);
	usleep(iic->sleep);
	return v;
}
 
static void
iic_write_bit(struct iic_port *iic, int v)
{
	iic_scl_sda(iic, 0, v);
	usleep(iic->sleep);
	iic_scl_sda(iic, 1, v);
	usleep(iic->sleep);
}
 
static uint8_t
iic_read_byte(struct iic_port *iic, int last)
{
	int i;
	uint8_t v;
 
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	for (i = 0, v = 0; i < 8; i++) {
		v = (v << 1) | iic_read_bit(iic);
	}
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	iic_write_bit(iic, last);
	return v;
}
 
static int
iic_write_byte(struct iic_port *iic, uint8_t v)
{
	int i;
 
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	for (i = 0; i < 8; i++) {
		iic_write_bit(iic, v & 0x80);
		v <<= 1;
	}
#if defined(IIC_DEBUG)
	debug_marker();
#endif
	return iic_read_bit(iic);
}
 
static int
iic_read(struct iic_port *iic, uint8_t a, int l, uint8_t *b)
{
	int r;
	iic_start(iic);
	if ((r = iic_write_byte(iic, a | 0x01) ? -1 : 0))
		goto end;
	for (; l > 0; l--, b++, r++) {
		*b = iic_read_byte(iic, l==0);
	}
end:
	iic_stop(iic);
#if defined(IIC_DEBUG)
	debug_print();
#endif
	return r;
}
 
static int
iic_write(struct iic_port *iic, uint8_t a, int l, const uint8_t *b)
{
	int r;
	iic_start(iic);
	if ((r = iic_write_byte(iic, a & 0xfe) ? -1 : 0))
		goto end;
	for (; l > 0; l--, b++, r++) {
		if (iic_write_byte(iic, *b))
			goto end;
	}
end:
	iic_stop(iic);
#if defined(IIC_DEBUG)
	debug_print();
#endif
	return r;
}
 
 
/*
 * Talk to the LM75.
 */
 
static int
lm75_set_control(struct iic_port *iic, uint8_t a, uint8_t c)
{
	uint8_t b[2];
 
	b[0] = 1;
	b[1] = c;
	return iic_write(iic, a, 2, b) != 2;
}
 
static int
lm75_set_pointer(struct iic_port *iic, uint8_t a, uint8_t p)
{
	return iic_write(iic, a, 1, &p) != 1;
}
 
static uint16_t
lm75_read_word(struct iic_port *iic, uint8_t a)
{
	uint8_t v[2];
	int r;
 
	r = iic_read(iic, a, 2, v);
	if (r != 2) {
		return 0xffff;
	}
	return v[0] << 8 | v[1];
}
 
static int
lm75_set(struct iic_port *iic, uint8_t a, uint8_t p, uint16_t v)
{
	uint8_t b[3];
 
	b[0] = p;
	b[1] = v >> 8;
	b[2] = v & 0xff;
	return iic_write(iic, a, 3, b) != 3;
}
 
void
temper_init(struct iic_port *iic, double high, double low)
{
	int16_t h, l;
	uint8_t c;
#if defined(IIC_DEBUG)
	debug_init();
#endif
	iic->sleep = 5000;
	iic_stop(iic);
	usleep(10000);
	iic_read(iic, 0x00, 0, (uint8_t *)"");
	lm75_set_pointer(iic, TEMPER_LM75_ADDRESS, 0);
	usleep(10000);
 
	c = LM75_CONTROL_RESOLUTION_12BIT;
	if (high > low) {
		h = high * 256;
		l = low * 256;
	} else {
		h = low * 256;
		l = high * 256;
		c |= LM75_CONTROL_POLARITY;
	}
	if (	lm75_set_control(iic, TEMPER_LM75_ADDRESS, c) ||
			lm75_set(iic, TEMPER_LM75_ADDRESS, LM75_POINTER_T_OS, h) ||
			lm75_set(iic, TEMPER_LM75_ADDRESS, LM75_POINTER_T_HYST, l) ||
			lm75_set_pointer(iic, TEMPER_LM75_ADDRESS, 0)) {
		fprintf(stderr, "Can't initialize TEMPer.\n");
		exit(1);
	}
}
 
double
temper_read_temp(struct iic_port *iic)
{
	int16_t v;
	double t;
 
	v = lm75_read_word(iic, TEMPER_LM75_ADDRESS);
	if (v & 0x8000) {
		v |= 0x000f; /* sign-extend lower bits */
	}
	t = v / 256.0;
	return t;
}
 
 
static void
usage() {
	fprintf(stderr, "usage: temper [-d device] [-h high] [-l low] [-n count]\n"
		"    -d device   USB serial port of the TEMPer\n"
		"    -h high     thermostat on temperature\n"
		"    -l low      thermostat off temperature\n"
		"    -n count    number of readings to take, <0 is infinite\n"
		"If the high temperature is below the low temperature, operation of the\n"
		"thermostat is reversed, and the output is turned on when the measured\n"
		"temperature is below the threshold.\n"
		"");
	exit(EX_USAGE);
}
 
 
int main(int argc, char **argv)
{
	int c;
	int count = -1;
	double low = 35;
	double high = 45;
	struct iic_port iic;
	char *dev = "/dev/cuaU0";
 
	while ((c = getopt(argc, argv, "d:h:l:n:?")) != -1) {
		switch (c) {
			case 'd':
				dev = optarg;
				break;
			case 'h':
				high = strtod(optarg, NULL);
				break;
			case 'l':
				low = strtod(optarg, NULL);
				break;
			case 'n':
				count = atoi(optarg);
				break;
			case '?':
			default:
				usage();
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 0)
		usage();
	iic.fd = open(dev, 0);
	if (iic.fd < 0)
		err(EX_OSERR, "can't open %s", dev);
 
	temper_init(&iic, high, low);
 
	if (count < 0) {
		while (1) {
			printf("%4.1lf\n", temper_read_temp(&iic));
			sleep(1);
		}
	} else {
		while (count > 0) {
			printf("%03.2lf\n", temper_read_temp(&iic));
			count--;
			sleep(1);
		}
	}
 
	return 0;
}