/* 
	Wind Speed Display and Data Logger with APRS Weather Station Functionality
    Copyright (C) 2005 James Jefferson Jarvis

    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 2 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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

	Author: 
		James Jefferson Jarvis 
		<jj@aprsworld.net>
		PO Box 1264
		Winona, MN 55987

	Project website:
		http://www.windmonitor.net/windmonitor/
*/
#define APRS 0
#define DATALOG 1
#define APRS_ANALOG 0

#device PIC16F628
#include <16F628.h>
#FUSES RC,NOPROTECT,NOWDT,PUT,NOLVP,NOBROWNOUT, NOMCLR
#use delay(clock=4000000)
#use rs232(INVERT,baud=1200,xmit=PIN_A0,rcv=PIN_A3)
#id CHECKSUM

typedef short bit;
#include "628ee.c"
#include <stdlib.h>

#use standard_io(A)
#use standard_io(B)

#byte port_a = 5
#byte port_b = 6
/* 0x03 for bank 0    and 0x83 for bank 1 */
#byte status = 3

#define E_CAL	0x00
#define E_FEATURES	0x02
#define E_DELAY	0x03
#define E_APRS_DELAY	0x79

byte features;
#define F_DISP		0
#define F_SLEEP		1
#define F_LOG		2
#define F_LOG_DISP	3
#define F_APRS		4

#define FEATURES_LOG 		0b00001101	// should be 0100 for no disp
#define FEATURES_DOWNLOAD	0b00001001
#define FEATURES_DISPLAY	0b00001011
#define FEATURES_APRS		0b00011001


#rom (0x2100 + E_DELAY ) = { 0, 60 }
#rom (0x2100 + E_APRS_DELAY ) =  { 175 }

// Datalog
#rom (0x2100 + E_FEATURES) = { FEATURES_DOWNLOAD } // default to download
// APRS
//#rom (0x2100 + E_FEATURES) = { FEATURES_APRS }	// default to aprs mode
// Display
//#rom (0x2100 + E_FEATURES) = { FEATURES_DISPLAY }	// default to display mode


#rom (0x2100 + E_CAL) = { 0xcc }	// initial cal value
/* Rev 1e  */
#rom 0x2105 = { 'R', 'e', 'v', ' ', '1', 'e', ' ', '\0'}


/* Madeline Island Water Tower 4647.04N/09042.41W_ */
#rom 0x216b = { '4', '6', '4', '7', '.', '0', '4', 'N', '/', '0', '9', 
'0', '4', '2', '.', '4', '1', 'W', '_', '\0'}
#define TEMP_OFFSET + 6


#if 0
#define TEMP_OFFSET 0
/* K0NY's house */
#rom 0x216b = { '4', '4', '0', '3', '.', '6', '1', 'N', '/', '0', '9', 
'1', '4', '1', '.', '5', '3', 'W', '_', '\0'}
/* Wiscoy Weather station 4353.07N/09138.76W_ */
#rom 0x216b = { '4', '3', '5', '3', '.', '0', '7', 'N', '/', '0', '9', 
'1', '3', '8', '.', '7', '6', 'W', '_', '\0'}
/* Jim's Wilson Hall place */
#rom 0x216b = { '4', '2', '0', '0', '.', '8', '2', 'N', '/', '0', '9',
'3', '3', '9', '.', '0', '6', 'W', '_', '\0' }
/* KB0THN's house 4358.58N/09136.09W_ */
rom 0x216b = { '4', '3', '5', '8', '.', '5', '8', 'N', '/', '0', '9', 
'1', '3', '6', '.', '0', '9', 'W', '_', '\0'}
/* Madeline Island Water Tower 4647.04N/09042.41W_ */
#rom 0x216b = { '4', '6', '4', '7', '.', '0', '4', 'N', '/', '0', '9', 
'0', '4', '2', '.', '4', '1', 'W', '_', '\0'}
#endif


/* ending address 0x2178 */


byte clicks;
byte cal;
byte window;
int speed;	// speed to show on screen
int gust;
unsigned long rain;
bit current;	// which digit we are on
bit update;	// update flag

int tensdigit=0;
int onesdigit=0;
int windtensdigit=0;
int windonesdigit=0;


/* temperature to display */
int temptensdigit=0;
int temponesdigit=0;




#define BEFORESLEEP 255




#define	W_DATA	pin_a6
#include "1wire.h"


#include "printd.c"
#include "hw_i.c"
#include "disp_i.c"
#include "time_i.c"


/* This is the interrupt handler for the anemometer */
#int_ext
void anemometer_tick(void) {
	disable_interrupts(INT_RTCC);
	clicks++;
	enable_interrupts(INT_RTCC);
}


/* every time the real-time clock overflows we go here */
#int_rtcc
void tick(void) {
	static int sleepcount=BEFORESLEEP;
	static int raindebounce=0;
	static int cycle=0;

	/* turn off this interrupt so we don't get interrupted,
	probably not nescessary unless this ISR takes longer than
	it should */
	disable_interrupts(RTCC_ZERO);

	window--;
	if ( 0 == window ) {
		disable_interrupts(INT_EXT);
		window = cal;
		speed = clicks;
		if ( speed > gust )
			gust = speed;
		clicks = 0;
		update = true;
		enable_interrupts(INT_EXT);
	}
	
	/* have the debounce period for the rain be the same as the 
	sampling window for the wind */
#if APRS
	if ( raindebounce > 0 ) {
		raindebounce--;
	} else if ( 0 == input(RAIN_IN) ) {
		rain++;	/* we don't care if it rolls over */
		raindebounce=255;
	}
#endif

#if ! APRS
	/* if sleep has been zero for the last BEFORESLEEP interations,
	then we put the processor into sleep mode to save power. The next
	external interrupt (anemometer click) will wake it up */
	if ( bit_test(features,F_SLEEP) ) {
		if ( 0 == speed )
			sleepcount--;		
		else
			sleepcount=BEFORESLEEP;
		
		if ( 0 == sleepcount ) {
			sleepcount=BEFORESLEEP;	
			shutdown();
		}
	}
#endif
	
	if ( bit_test(features,F_DISP) ) {

		if ( update ) {
			update=false;
#if APRS			
			/* cycle is what we are displaying, wind speed, wind gust,
			temperature, battery voltage */
			if ( 0 == cycle ) {
				/* top segment */
				tensdigit=11;
				onesdigit=10;
				cycle=1;
			} else if ( 1 == cycle ) {	
				/* wind speed */
				update_digits(speed);
				windtensdigit=tensdigit;
				windonesdigit=onesdigit;
				cycle=2;
			} else 	if ( 2 == cycle ) {
				/* middle segment */
				tensdigit=12;
				onesdigit=10;
				cycle=3;
			} else if ( 3 == cycle ) {
				/* wind gust */
				update_digits(gust);
				cycle=4;
			} else if ( 4 == cycle ) {
				/* temperature */
				tensdigit=13;
				onesdigit=10;
				cycle=5;
			} else if ( 5 == cycle ) {
				/* temperature */
				onesdigit=temponesdigit;
				tensdigit=temptensdigit;
				cycle=0;
			}
#else
			/* default case we just show speed */
			update_digits(speed);
#endif			
		}
			

		/* alternate between left and right digit */		
		if ( 0 == current ) {
			current=1;
			output_high(TEN_LED);
			displayDigit(onesdigit);
			output_low(ONE_LED);
		} else {
			current=0;
			output_high(ONE_LED);
			/* don't zero pad */
			if ( 0 == tensdigit )
				displayDigit(BLANK);
				else
				displayDigit(tensdigit);
			output_low(TEN_LED);
		}
	}

	enable_interrupts(RTCC_ZERO);	// turn back on this interrupt
}


void init_628() {
	/* PIC16F628 stuff to make port_a digital instead of analog */
	setup_comparator(NC_NC_NC_NC);
	setup_ccp2(CCP_OFF);
	setup_vref(FALSE);
}


void init_interrupts() {
	set_rtcc(0);
	setup_counters( RTCC_INTERNAL, RTCC_DIV_32);
	enable_interrupts(RTCC_ZERO);
	enable_interrupts(INT_EXT); 
	enable_interrupts(H_TO_L);	// interrupt on high to low transition

	enable_interrupts(GLOBAL);
}

#inline
void startup_msg() {
	putchar('#');
	putchar(' ');
	print_ee_string(0x05,1);
	putchar('#');
	putchar(' ');
	printf(__DATE__);
	puts("");

/* we can make the startup smarter and flashier by doing a chasing snake */
	
	if ( FEATURES_APRS == features ) 
		port_b=0x03;
	else if ( FEATURES_DOWNLOAD == features )
		port_b=0x11;
	else if ( FEATURES_DISPLAY == features )
		port_b=0xd7;
	else if ( FEATURES_LOG == features )
		port_b=0xd5;
	else 
		port_b=0xef;

	output_low(TEN_LED);
	delay_ms(300);
	output_high(TEN_LED);
	output_low(ONE_LED);
	delay_ms(300);
	output_high(ONE_LED);
}

void disp_off() {
	output_high(ONE_LED);
	output_high(TEN_LED);
	bit_clear(features,F_DISP);
}

void disp_on() {
	bit_set(features,F_DISP);
}


void startLog() {
	EEPROM_ADDRESS l;

	unsigned long delay;
	byte lspeed;	// local speed, otherwise it might change
	
	bit_clear(features,F_SLEEP);	// no sleeping while logging

	delay = read_int_ulong(E_DELAY);
	
	printf("# Log interval: ");
	print_lint(delay,1);
	puts("");

	/* turn off display if requested */	
	if ( ! bit_test(features,F_LOG_DISP) )
		disp_off();
		
	
	init_ext_eeprom();
	
	for ( l=0 ; l<0x8000 ; l++ ) {
		delay_half_seconds(delay);	
		/* May need a mutex lock to prevent ISR from updating this while we are copying it */
		lspeed = speed;	

		/* make the next location 0xFF so we know where we left off */
		if ( l < 0x7fff )
			write_ext_eeprom(l+1,0xff);

		/* then write out current value, in case we power off
		while writting */
		write_ext_eeprom(l,lspeed);
	}
	
	disp_on();
}

void playLog() {
	EEPROM_ADDRESS l;
	char value;

	/* turn off display */
	disp_off();

	for ( l=0 ; l<0x8000 ; l++ ) {
		value = read_ext_eeprom(l);
		
		if ( 0xFF == value )
			 break;
			 
		print_int(value,0);
		puts("");
	}
	
	disp_on();
}


bit set_mode(char c) {

	/* if we don't get the mode, then we get it ourselves */
	if ( 0 == c ) {	
		c = getchar();
		delay_ms(10);
		putchar(c);
		c = tolower(c);

	}

	puts("");

#if DATALOG
	if ( 'd' == c ) {
		printf("# Datalog");
		features = FEATURES_LOG;
	} else 
#endif	

	if ( 'o' == c ) {
		printf("# Download");
		features = FEATURES_DOWNLOAD;
	} else if ( 'i' == c ) {
		printf("# Display");
		features = FEATURES_DISPLAY;
#if APRS
	} else if ( 'a' == c ) {
		printf("# APRS");
		features = FEATURES_APRS;
#endif
	} else {
		puts("# Invalid");
		return 1;
	}
	puts(" mode");

	/* save our new mode */
	write_int_eeprom(E_FEATURES,features);

#if 0
// for alden barlett so datalogger sleeps after going into datalogger mode
	if ( features == FEATURES_LOG ) {
		sleep();
		
	}
#endif
	return 0;
}


void reset_cpu(void) {
	/* we need to disable interrupts otherwise they appear to retain
	the state upon restarting */
	disable_interrupts(GLOBAL);
			
	#asm
	MOVLW 0
	MOVWF 0x0a
	GOTO 0
	#endasm
}

void getMode(void) {
	disable_interrupts(GLOBAL);
		
	do {

#if DATALOG
		printf("(d)atalogger, ");
#endif
		printf("d(o)wnload, d(i)splay");
#if APRS
		printf("(a)PRS");
#endif
		puts("");		

	} while ( set_mode(0) );


	delay_ms(500);	
	reset_cpu();
	
}


void write_int_eeprom_hex(void) {
	write_int_eeprom(gethex(),gethex());
}

void read_int_eeprom_hex(void) {
	print_hex(read_int_eeprom(gethex()));
}

void aprsPacket(void) {
	byte c;
	unsigned long l;
	
	/* our hundreds digit - need to calculate it because printf is dumb */
	if ( speed > 99 ) {
		c='1';
	} else {
		c='0';
	}

		
	putchar('!');
	print_ee_string(0x6b,0);  // 4358.58N/09136.09W

//	printf(".../");	// wind direction
	printf("000/"); // wind direction
	putchar(c); // speed greater than 99 miles
	putchar(windtensdigit+'0'); // tens
	putchar(windonesdigit+'0');	// ones

	disable_interrupts(GLOBAL);	// is this one needed
	putchar('g');
	print_int(gust,1);
	gust=0;	// we are the only people using gust

	putchar('t');
	enable_interrupts(GLOBAL);
	printAprsTemp();
		
	putchar('#');
	print_lint(rain,1);


#if APRS_ANALOG
	setup_adc( ADC_CLOCK_INTERNAL );
	setup_adc_ports( ALL_ANALOG);
	set_adc_channel(5);

	/* 
	we may have an analog input in pin_a6 ... 
	if the 8-bit ADC value is > 250 or < 50 we will assume that it is 
	being used as MCLR and won't bother printing it
	*/
	l = 0;
	l = (byte) read_adc(ADC_START_AND_READ);

	for ( c=0 ; c<4 ; c++ ) {
		l += read_adc();
		delay_ms(10);
	}
	/* divide by four */	
	l = l >> 2;
	c = (byte) l;
	
	if (  < 250 || c > 50 ) { 
		putchar('v');
		print_int( c, 1 );
	}
#endif

	printf("PIC\r");
		
	enable_interrupts(GLOBAL);

}

/*
APRS function is very simple. All of the relevant TNC settings like

UNPROTO APRS via WIDE
MY MYCALLSIGN-SSID

need to be set in advance. All this function does is kick the TNC in 
conververse mode and sends the information portion of the packet
*/
void aprs_h(void) {
	unsigned long l;
		
	/* only way to leave APRS mode is to reprogram the features byte
	or to bring PIN_A4 high on startup */
	if ( input(PIN_A4) ) {
		set_mode('i');
		reset_cpu();
	}

	/* upon startup send an e-mail to Jim */
	delay_seconds(5);
	putchar(3);	// send control c
	delay_ms(100);
	putchar('k');
	putchar('\r');
	printf(":KB0THN-E :jj@aprsworld.net WX Startup (");
	printf(__DATE__);
	printf(")\r");
	delay_seconds(5);


	while (1) {
		putchar(3);	// send control c
		delay_ms(100);
		putchar('k');
		putchar('\r');

		
		aprsPacket();
		disable_interrupts(INT_EXT);
		delay_seconds(5);
		enable_interrupts(INT_EXT);
				
		putchar(3);	// send control c		
			
		for ( l=0 ; l<600; l++ ) 
			delay_ms(1000);
	}
}

void trip(void) {
	if ( speed > 5 )
		output_high(PIN_A0);
	else
		output_low(PIN_A0);

}



main(void) {
	byte c;
	speed=0; // PCM doesn't seem to be initializing globals
	gust=0;
	clicks=0;	
	rain=0;

	set_tris_b(0x01);	// RB0 input, RB1 - RB7 output

	init_628();
	
	features=read_int_eeprom(E_FEATURES);
	window=cal=read_int_eeprom(E_CAL);

#if ! APRS		
	startup_msg();
#endif	
	
	init_interrupts();	
	

#if DATALOG
	/* immediately start logging, but next time come up in download mode */
	if ( bit_test(features,F_LOG) ) {
		write_int_eeprom(E_FEATURES, 0b00001001);
		startLog();
	}
#endif


#if 0
/* raise pin_a0 if wind speed > 5 MPH */
while (1) {
	trip();
}
#endif

#if APRS
	if ( FEATURES_APRS == features )
		aprs_h();
#endif
	
	while ( true ) {
		printf("cmd: ");
		c = getchar();
		putchar(c);
		puts("");
		switch (tolower(c)) {
			/* Mode of operation */
			case 'm':	getMode();
					break;

#if DATALOG
			/* Logging */					
			case 'g':	startLog();
					break;
			case 'p':	playLog();
					break;
#endif			

#if APRS
			case 'a':	aprsPacket();
					break;
#endif


			/* Set anemometer calibration */
			case '-':	cal--;
					printCal(); 
					break;
 			case '+':	cal++;
					printCal(); 
					break;

			/* computer setup */
			case 'r':	read_int_eeprom_hex();
					break;
			case 'w':	write_int_eeprom_hex();
					break;
			default:	printf("?\r\n");
		}

	}
}
