Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 2 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
| |||||
![]() |
| |||||
| ||||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
![]() |
| |||||
![]() |
|
This project is designed as a way to quickly map out the water drainage pattern of a small landscape.
The target user is a landscape designer or homeowner who wants to renovate the various plant matter in some somewhat small location. The user places an array of these devices around their yard or landscape, and the devices will then broadcast their location and read water moisture levels periodically.
After some period of time, the user visits a webpage which displays a history of the water moisture levels of the various devices as color-coded google maps markers (cycle through pictures to view the range of colors).
Which are displayed on a map embedded in a webpage:
Additionally, there is a slider at the top of the page which can be used to view the last 48 hours worth of moisture data history:
The device itself leverages the FRDM K64F NXP microcontroller (arm based) as part of the AT&T IoT Starter Kit https://starterkit.att.com/ with a Sparkfun moisture sensor and Xadow GPS unit, & some PVC pipe.
The device sends moisture data once every 15 min via AT&T's M2X api where it is stored and viewed via a webpage in AT&T's Flow platform (https://flow.att.io/nmittow/watertracker_public/home , will need account to view).
Wiring up was fairly straight forward after I found the hidden unsoldered UART connection on the K64F board:
GPRMCSentence.h
C/C++#define TIMEMAX 6
#define LATMAX 20
#define LONGMAX 20
#define SPEEDMAX 6
#define TRACKMAX 6
#define DATEMAX 6
#define MAGVARMAX 6
#define CHECKSUMMAX 10
#define DEVNULMAX 20
#define CRLF2 "\n\r"
/** Tfile provides a means for parsing serially received GPRMC sentences into a location struct **/
typedef struct {
char time[TIMEMAX+1];
char status;
char latitude[LATMAX+1];
double dlatitude;
char latdir;
char longitude[LONGMAX+1];
double dlongitude;
char longdir;
char speed[SPEEDMAX+1];
char trackangle[TRACKMAX+1];
char date[DATEMAX+1];
char magvar[MAGVARMAX+1];
char magvardir;
//char checksum[CHECKSUMMAX+1];
} GPRMCSentence;
double charStrToDouble(char* str) {
uint16_t decpos = 0;
double num = 0.0;
while((str[decpos]!='.')&&(str[decpos]!='\0')) decpos++;
uint16_t i = 0;
double multiplier = 1.0;
for(uint16_t i = 1;(decpos-i)>=0;i++) {
num += (multiplier*(str[decpos-i]-48));
multiplier *= 10.0;
}
multiplier = (1.0/10.0);
for(uint16_t i = 1;str[decpos+i]!='\0';i++) {
num += (multiplier*(str[decpos+i]-48));
multiplier /= 10.0;
}
return num;
}
uint16_t parseStringToGPRMCStructEntry(char* gprmcstr, char* gprmcentry, uint16_t start, uint16_t length) {
int16_t i=0;
while((gprmcstr[start] != ',') &&
(gprmcstr[start] != '\0') &&
(gprmcstr[start] != '\n') &&
(i < length)) {
gprmcentry[i] = gprmcstr[start];
start++;i++;
}
while(i<=length) {
gprmcentry[i] = '\0';
i++;
}
return start + 1;
}
void moveDecimalLeft(char* num, uint16_t places) {
if (places == 0) return;
else places--;
uint16_t i = 0;
while(num[i] != '\0') {
if ((i >= places) && (num[i] == '.')) {
uint16_t j;
for(j=i;j>(i-places);j--) num[j] = num[j-1];
num[j] = num[j-1];
num[j-1] = '.';
break;
}
else i++;
}
}
void negateCharStrNumber(char* num, uint16_t len) {
for(uint16_t i=(len-1); i>0;i--) num[i] = num[i-1];
num[0] = '-';
}
/************************
parseGPRMCStrToStruct:
In: gprmcstr: raw char array string of gprmc sentence
In: GPRMCSentece: struct object representing gprmc sentence to parse to
Out: integer: < 0 : not a gprmc sentence
= 0 : not valid
> 0 : valid
*/
int8_t parseGPRMCStrToStruct(char* gprmcstr, GPRMCSentence* gprmstruct) {
uint16_t pos = 7;
char devnull[DEVNULMAX+1];
if ((gprmcstr[0] == '$') &&
(gprmcstr[1] == 'G') &&
(gprmcstr[2] == 'P') &&
(gprmcstr[3] == 'R') &&
(gprmcstr[4] == 'M') &&
(gprmcstr[5] == 'C') &&
(gprmcstr[6] == ',')) {
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->time,pos,TIMEMAX);
pos = parseStringToGPRMCStructEntry(gprmcstr,devnull,pos,DEVNULMAX); // to get rid of time decimal
if (gprmcstr[pos] != ',') {gprmstruct->status = gprmcstr[pos];pos++;}
else gprmstruct->status = '\0';
pos++;
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->latitude,pos,LATMAX);
moveDecimalLeft(gprmstruct->latitude,2);
gprmstruct->dlatitude = charStrToDouble(gprmstruct->latitude);
if (gprmcstr[pos] != ',') {gprmstruct->latdir = gprmcstr[pos];pos++;}
else gprmstruct->latdir = '\0';
if(gprmstruct->latdir == 'S') {
negateCharStrNumber(gprmstruct->latitude, LATMAX);
gprmstruct->dlatitude *= -1;
}
pos++;
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->longitude,pos,LONGMAX);
moveDecimalLeft(gprmstruct->longitude,2);
gprmstruct->dlongitude = charStrToDouble(gprmstruct->longitude);
if (gprmcstr[pos] != ',') {gprmstruct->longdir = gprmcstr[pos];pos++;}
else gprmstruct->longdir = '\0';
if(gprmstruct->longdir == 'W') {
negateCharStrNumber(gprmstruct->longitude, LONGMAX);
gprmstruct->dlongitude *= -1;
}
pos++;
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->speed,pos,SPEEDMAX);
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->trackangle,pos,TRACKMAX);
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->date,pos,DATEMAX);
pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->magvar,pos,MAGVARMAX);
if (gprmcstr[pos] != ',') {gprmstruct->magvardir = gprmcstr[pos];pos++;}
else gprmstruct->magvardir = '\0';
pos++;
//pos = parseStringToGPRMCStructEntry(gprmcstr,gprmstruct->checksum,pos,CHECKSUMMAX);
if (gprmstruct->status == 'A') return 1;
else return 0;
}
else return -1;
}
void printGPRMCSentence(GPRMCSentence* gprmcstruct, MODSERIAL *serial) {
serial->printf("Time: %s" CRLF2,gprmcstruct->time);
serial->printf("Status: %c" CRLF2,gprmcstruct->status);
serial->printf("Lat: %s" CRLF2,gprmcstruct->latitude);
serial->printf("LatDir: %c" CRLF2,gprmcstruct->latdir);
serial->printf("Long: %s" CRLF2,gprmcstruct->longitude);
serial->printf("LongDir: %c" CRLF2,gprmcstruct->longdir);
serial->printf("Speed: %s" CRLF2,gprmcstruct->speed);
serial->printf("Angle: %s" CRLF2,gprmcstruct->trackangle);
serial->printf("Date: %s" CRLF2,gprmcstruct->date);
serial->printf("MagVar: %s" CRLF2,gprmcstruct->magvar);
serial->printf("MagDir: %c" CRLF2,gprmcstruct->magvardir);
}
main.cpp
C/C++//
// This file modifies the WNCInterface_M2Xdemo program to run with the WaterTracker Flow.
//
#include "mbed.h"
#include "WNCInterface.h"
#include "GPRMCSentence.h"
#include "keysAndSecrets.h"
#define MBED_PLATFORM
#define M2X_ENABLE_READER
#include <jsonlite.h>
#include "M2XStreamClient.h"
#define CRLF "\n\r"
#define GPSBUFFSIZE 256
char deviceId[] = DEVICEID // Device you want to post to
char m2xKey[] = M2XKEY; // Your M2X API Key or Master API Key
const char *hstreamName = "humidity";
const char *tstreamName = "temperature";
const char *mstreamName = "moisture0";
const char *streamNames[] = { tstreamName, hstreamName };
char name[] = "Loc"; // Name of current location of datasource
int counts[] = { 2, 1 };
double elevation = 52.00;
char fromTime[]= "1969-12-31T19:00:01.000Z"; // yyyy-mm-ddTHH:MM:SS.SSSZ
char endTime[25];
WNCInterface eth;
Client client;
M2XStreamClient m2xClient(&client, m2xKey);
TimeService timeService(&m2xClient);
MODSERIAL pc(USBTX,USBRX,256,256);
MODSERIAL xgps(PTC15, PTC14, GPSBUFFSIZE, GPSBUFFSIZE);
unsigned gpsSentenceIndex = 0;
char gpsSentence[GPSBUFFSIZE];
volatile bool newline_detected = false;
GPRMCSentence lastPosition;
void clearGpsSentence() {
for(int i=0;i<GPSBUFFSIZE;i++) gpsSentence[i] = '\0';
gpsSentenceIndex = 0;
}
void gpsRxCallback(MODSERIAL_IRQ_INFO *q) {
MODSERIAL *serial = q->serial;
if ( serial->rxGetLastChar() == '\n') {
serial->move(gpsSentence,GPSBUFFSIZE,'\n');
serial->rxBufferFlush();
if (parseGPRMCStrToStruct(gpsSentence, &lastPosition) > 0) newline_detected = true;
else newline_detected = false;
}
}
void on_data_point_found(const char* at, const char* value, int index, void* context, int type) {
pc.printf(">>Found a data point, index: %d type: %d" CRLF, index, type);
pc.printf(">>At: %s" CRLF " Value: %s" CRLF, at, value);
}
void on_command_found(const char* id, const char* name, int index, void *context) {
pc.printf(">>Found a command, index: %d" CRLF, index);
pc.printf(">>ID: %s\n Name: %s" CRLF, id, name);
}
int main() {
char timestamp[25];
int length = 25;
char amb_temp[6];
char amb_humd[6];
int response, cnt=1;
double temp=0.00;
double humid=0.00;
AnalogIn moistureLevel(PTB2);
float moistureFloat = 0;
pc.baud(115200);
pc.printf("initializing GPS module serial port" CRLF);
xgps.baud(9600);
clearGpsSentence();
xgps.attach(&gpsRxCallback, MODSERIAL::RxIrq);
pc.printf("gps Ready" CRLF);
pc.printf("Start m2x-demo-all by initializng the network" CRLF);
response = eth.init();
pc.printf("WNC Module %s initialized (%02X)." CRLF, response?"IS":"IS NOT", response);
uint16_t tries = 0;
while((!response) && (tries < 10)) {
pc.printf("Init Fail %u, retry in 10sec" CRLF, tries);
tries++;
delay(1000);
response = eth.init();
pc.printf("WNC Module %s initialized (%02X)." CRLF, response?"IS":"IS NOT", response);
}
if (tries >= 10) while(1);
response = eth.connect();
pc.printf("IP Address: %s " CRLF CRLF, eth.getIPAddress());
pc.printf("initialize the M2X time service" CRLF);
if (!m2x_status_is_success(timeService.init()))
pc.printf("Cannot initialize time service!" CRLF);
else {
timeService.getTimestamp(timestamp, &length);
pc.printf("Current timestamp: %s" CRLF, timestamp);
strcpy(endTime,timestamp);
}
while (true) {
moistureFloat = moistureLevel.read();
// update location
pc.printf("updateLocation..." CRLF);
response = m2xClient.updateLocation(deviceId, name, lastPosition.dlatitude, lastPosition.dlongitude, elevation);
pc.printf("updateLocation response code: %d" CRLF, response);
if (response == -1) while (true) ;
// update moisture
pc.printf("updateMoisture..." CRLF);
response = m2xClient.updateStreamValue(deviceId, mstreamName, moistureFloat);
pc.printf("updateMoisture response code: %d" CRLF, response);
if (response == -1) while (true) ;
timeService.getTimestamp(timestamp, &length);
pc.printf("Waiting... (%s)" CRLF CRLF CRLF, timestamp);
// wait 15 min then restart
delay(900000);
}
}
Comments
Please log in or sign up to comment.