/*   USBTenki - Interfacing sensors to USB
 *   Copyright (C) 2007-2014  Raphaël Assénat <raph@raphnet.net>
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>

#include <util/delay.h>

#include "usbdrv.h"
#include "usbconfig.h"
#include "interface.h"

#include "i2c.h"
#include "eeprom.h"
#include "serno.h"
#include "adc.h"
#include "usbtenki_cmds.h"

static char g_auto_mode = 1;


void usbtenki_delay_ms(int ms)
{
	int i;

	for (i=0; i<(ms/10); i++) {
        wdt_reset();
		_delay_ms(10);
	}
}

/* Delay with USBpoll. Do not use in functions
 * called by usbFunctionSetup() or you may
 * face interesting recursion problems... */
void usbtenki_usbpoll_delay_ms(int ms)
{
	int i;

	for (i=0; i<(ms/10); i++) {
        wdt_reset();
		usbPoll();
		_delay_ms(10);
	}

}

static unsigned char xor_buf(unsigned char *buf, int len)
{
	unsigned char x=0;
	while (len--) {
		x ^= *buf;
		buf++;
	}
	return x;
}

void sensors_doTasks(void) __attribute((weak));
void sensors_doTasks(void)
{
}

int sensors_getCalibration(unsigned char id, unsigned char *dst) __attribute__((weak));
int sensors_getCalibration(unsigned char id, unsigned char *dst)
{
	return 0;
}

uchar   usbFunctionSetup(uchar data[8])
{
	static uchar    replyBuf[8];
	int replen=0, res;
	int total_channels;
	int sensors_channels;
	int num_adc_channels=EEPROM_ADC_CHIPS_SIZE;

	g_auto_mode = 0;
	sensors_channels = sensors_getNumChannels();
	total_channels = sensors_channels + num_adc_channels;

    usbMsgPtr = (usbMsgPtr_t)replyBuf;

	switch (data[1])
	{
		case USBTENKI_GET_CALIBRATION:
			if (data[2] >= sensors_channels)
				break;

			res = sensors_getCalibration(data[2], &replyBuf[1]);

			if (res<0) {
				replyBuf[0] = USBTENKI_ERROR;
				replen = 1;
				break;
			}

			replyBuf[0] = USBTENKI_GET_CALIBRATION;
			replyBuf[res+1] = xor_buf(replyBuf, res+1);
			replen = res + 2;
			break;

		case USBTENKI_GET_RAW:
			if (data[2] >= total_channels)
				break;

			replyBuf[0] = USBTENKI_GET_RAW;

			if (data[2] >= sensors_channels)
			{
				unsigned short val;
//				ADCSRA |= (1<<ADSC); /* start conversion */
//				while (!(ADCSRA & (1<<ADIF)))
//					{ /* do nothing... */ };
//				replyBuf[2] = ADCL;
//				replyBuf[1] = ADCH;
				// Take 5 samples at 10ms intervals and
				// average them.
				val = adc_sample(data[2] - sensors_channels,
								5,
								10);
				replyBuf[1] = val >> 8;
				replyBuf[2] = val & 0xff;
				res = 2;
			}
			else
			{
				res = sensors_getRaw(data[2], &replyBuf[1]);

				if (res<0) {
					replyBuf[0] = USBTENKI_ERROR;
					replen = 1;
					break;
				}
			}

			replyBuf[res+1] = xor_buf(replyBuf, res+1);
			replen = res + 2;
			break;

		case USBTENKI_GET_CHIP_ID:
			if (data[2] >= total_channels)
				break;

			replyBuf[0] = USBTENKI_GET_CHIP_ID;
			if (data[2] >= sensors_channels) {
				replyBuf[1] = g_eeprom_data.adc_chips[(data[2]-sensors_channels)];
			} else {
				replyBuf[1] = sensors_getChipID(data[2]);
			}
			replyBuf[2] = xor_buf(replyBuf, 2);
			replen = 3;
			break;

		case USBTENKI_GET_NUM_CHANNELS:
			replyBuf[0] = USBTENKI_GET_NUM_CHANNELS;
			replyBuf[1] = total_channels;
			replyBuf[2] = xor_buf(replyBuf, 2);
			replen = 3;
			break;

		case USBTENKI_SET_SERIAL:
			if (data[2] == 0xff) {
				serno_store();
			} else {
				serno_setChar(data[2], data[3]);
			}
			replyBuf[0] = USBTENKI_SET_SERIAL;
			replyBuf[1] = xor_buf(replyBuf, 1);
			replen = 2;
			break;

		case USBTENKI_SET_ADC_CHIP:
			if (data[2] >= num_adc_channels)
				break;

			g_eeprom_data.adc_chips[data[2]] = data[3];
			eeprom_commit();

			replyBuf[0] = USBTENKI_SET_ADC_CHIP;
			replyBuf[1] = xor_buf(replyBuf, 1);
			replen = 2;
			break;

		case USBTENKI_SET_ADC_REF:
			g_eeprom_data.use_aref = data[2];
			eeprom_commit();

			replyBuf[0] = USBTENKI_SET_ADC_REF;
			replyBuf[1] = xor_buf(replyBuf, 1);
			replen = 2;
			break;

		case USBTENKI_SET_RTD_CORR:
			g_eeprom_data.rtd_corr = data[2] | (data[3] << 8);
			eeprom_commit();

			replyBuf[0] = USBTENKI_SET_RTD_CORR;
			replyBuf[1] = xor_buf(replyBuf, 1);
			replen = 2;
			break;

    }

	return replen;
}

static void usbReset()
{
	/* [...] a single ended zero or SE0 can be used to signify a device
	 * reset if held for more than 10mS. A SE0 is generated by holding
	 * both th D- and D+ low (< 0.3V).
	 *
	 * Source: http://www.beyondlogic.org/usbnutshell/usb2.shtml
	 */
	PORTD &= ~(0x01 | 0x04); // Set D+ and D- to 0
	DDRD |= 0x01 | 0x04;
	_delay_ms(15);
	DDRD &= ~(0x01 | 0x04);
}

int main(void)
{

	PORTB = 0xff;
	DDRB = 0xff;

	// all input by default
	PORTC= 0xff;
	DDRC = 0x00;

	/*
	 * For port D, activate pull-ups on all lines except the D+, D- and bit 1.
	 *
	 * For historical reasons (a mistake on an old PCB), bit 1
	 * is now always connected with bit 0. So bit 1 configured
	 * as an input without pullup.
	 *
	 * Usb pin are init as output, low. (device reset). They are released
	 * later when usbReset() is called.
	 */
	PORTD = 0xf8;
	DDRD = 0x01 | 0x04;

	wdt_enable(WDTO_1S);

	eeprom_init();
	adc_init();
	serno_init();

	usbReset();
    usbInit();

	sei();

	if (sensors_init()) {
		while(1) { } /* watchdog will reset me! */
	}

    for(;;){    /* main event loop */
        wdt_reset();
		sensors_doTasks();
        usbPoll();
    }

    return 0;
}

