Rain Gauge Project – Raspberry Pi

Introduction

There are several reasons to move the project code to the RPi:

  • The capability to timestamp the log entries
  • File I/O
  • Internet connectivity

The first step in this phase is to convert the working Arduino code, into code for the Pi, which can be quite straightforward with C|C++ and the WiringPi library. The code can be found here, but the following discusses the changes from the Arduino code.

Discussion

The first thing I discovered is that the GPIO interrupt is not as responsive as the Arduino because the processor is busy sharing other computer tasks.  I opened up the timing range checks in the interrupt handler function to accommodate:

// global declarations
#define SYNC  600	
...
void handler() {
    static unsigned long duration = 0;
    static unsigned long lastTime = 0;
    static unsigned int syncSig = 0;
	
    // ignore if we haven't processed the previous full buffer
    if (bufferFilled == true) return;

    // calculating timing since last change
    long time = micros();
    duration = time - lastTime;
    lastTime = time;

    if (inSync) {
	if (ringIndex < 129) {
	    // store data in ring buffer
	    ringIndex = (ringIndex + 1) % RING_BUFFER_SIZE;
	    timings[ringIndex] = duration;
	    //printf("ringIndex=%d	%d\n", ringIndex, duration);
	    bufferFilled = false;
	    } else {
		// verify end of message
		if (timings[112] < 200 && timings[113] > 1000) {
		    bufferFilled = true;
		} else {
		    bufferFilled=false;
		}
		inSync = false;
		ringIndex = -1;
	    }
        } else {
	    if (duration > SYNC - 100 && duration < SYNC + 100) { 
                syncSig++; 
                if (syncSig > 7) {
	            //printf("Received %d consecutive square waves\n", syncSig);
	            ringIndex = -1;
	            inSync = true;
	            syncSig = 0;
                } 	
	    } else {
	        syncSig = 0;
	    }
    }
    return;	
}

During test and debug, it became apparent that disabling the interrupt is ineffective.  This seems to be a minor shortfall within the super-helpful WiringPi library, There is a suggested work-around that helps, although not completely; placing printf() statements within the interrupt handler will demonstrate that.  Hopefully a future Python rewrite will overcome this annoyance.

Timestamp

Knowing the time a reading was taken is at the heart of tracking the data.  The Pi reports this quite well.

// global declarations
time_t now;		// current time for timestamp
char timeStamp[40];	// formatted time
…

    // get time stamp for report once the buffer is filled
    now = time(0);
    strftime (timeStamp, 100, "%Y-%m-%d %H:%M", localtime(&now));

File I/O

The code saves the data into a CSV file as follows:

// global declaration
FILE* fileRain = NULL;			// rain log

    // save this value to the data log file once the rain measurement is properly extracted
    fileRain = fopen("rainlog.txt", "a+");
    if (!fileRain) { 
	printf("File open failure!");
    } else {
	sprintf(outbuff, "%s, %6.2f\n", timeStamp, (d/100));
	fwrite(outbuff, strlen(outbuff), 1, fileRain);
	fclose(fileRain);
    }

Also include a debug file for unexpected exceptions

// global declaration
FILE* fileDbug = NULL;		// debug log

// track errors for future analysis
void sayDebug(char when[]) {	// ‘when[]’ is the timestamp string

    char dbuff[100]; 
    // save this value to the data log file
    fileDbug = fopen("debug-rainlog.txt", "a+");
    if (!fileDbug) { 
	printf("Debug File open failure!");
    } else {
	sprintf(dbuff,"%s\n", when);
	fwrite(dbuff, strlen(dbuff), 1, fileDbug);
	// report timing information for debugging
	for (int i = 0; i < 113; i+=2){
	    sprintf(dbuff,"%d	%d	%d\n", i, (int)timings[i], 
                (int)timings[i+1]);
	    fwrite(dbuff, strlen(dbuff), 1, fileDbug);
	}
	
	// report all data formated as bytes
	int j = 0;
	char sTmp[100] = "";
	outbuff[0] = '\0';
	for (int i = 0; i < 111; i+=2) {
	    if (j % 8 == 0) {
		strcat(sTmp," ");
	    }
	    sprintf(outbuff,"%d", t2b( timings[i], 
                timings[i+1] ) );
	    strcat(sTmp, outbuff);
	    j++;
	}
	//rcat(sTmp, "\n");
	sprintf(dbuff,"%s\n", sTmp);
	fwrite(dbuff, strlen(dbuff), 1, fileDbug);
		
	sprintf(dbuff,"<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>\n");
	fwrite(dbuff, strlen(dbuff), 1, fileDbug);
    }
    fclose(fileDbug);
}

Redundant Log Entries

The rain gauge transmits about every 15-20 seconds, resulting in plentiful data, to say the least.  The following code limits logging to one entry every 5 minutes.

	// report the results every 5 minutes
	strftime(outbuff, 40, "%M",localtime(&now));
	mod = atoi(outbuff) % 5;
	//printf( "*** Minutes: %s Remainder: %d ***\n", outbuff,
            mod );
	if (mod % 5 < lastMod) {
	    //printf("5 minutes elapsed\n");
	    printf("%s, %6.2f\n", timeStamp, (d/100));
	
	    // save this value to the data log file
	    fileRain = fopen("rainlog.txt", "a+");
	    if (!fileRain) { 
		printf("File open failure!");
	    } else {
		sprintf(outbuff, "%s, %6.2f\n", timeStamp, (d/100));
		fwrite(outbuff, strlen(outbuff), 1, fileRain);
		fclose(fileRain);
	    }
	}
	lastMod = mod;

Next Steps

  1. Make the data accessible via internet
  2. Rewrite in Python

Why?

  • To make use of the GPIO routines offered by the RPi library that may disable interrupts during analysis.
  • There are plentiful examples within the Raspberry Pi community teaching internet connectivity, cloud database updates, etc.

 

Leave a Reply