Nile Mittow
Created December 19, 2016 © GPL3+

DigitalDiviningRod

Show the drying/evaporation patterns of water from the topsoil in a small landscape using the AT&T IoT Starter Kit.

IntermediateWork in progressOver 1 day38
DigitalDiviningRod

Things used in this project

Hardware components

AT&T Cellular IoT Starter Kit
Tria Technologies AT&T Cellular IoT Starter Kit
×1
FRDM Board
NXP FRDM Board
This is the base platform for the AT&T IoT Starter Kit - https://products.avnet.com/opasdata/d120001/medias/docus/132/AES-ATT-M14A2A-IOT-SK-G-PB-v3.pdf
×1
Tria Technologies Avnet M14A2A Cellular Shield
The cellular shield that is part of the AT&T IoT Starter Kit.
×1
SparkFun Soil Moisture Sensor (with Screw Terminals)
SparkFun Soil Moisture Sensor (with Screw Terminals)
×2
Seeed Studio Xadow GPS v1
×1
Seeed Studio Xadow Breakout Board
×1
4 pin female header pins - 90 deg
Generic - needed to breakout one of the extra serial lines on the K64F board.
×1

Software apps and online services

AT&T Flow
AT&T Flow
Similar to Node Red but has a powerful set of libraries designed for interfacing with M2X & other cloud IoT platforms.
AT&T M2X
AT&T M2X
M2X provides syncing, storage, and cloud inter connectivity to this project. It handles the link between the local cell modem and the cloud resources.
ARM mbed platform

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)
Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Custom parts and enclosures

Moisture Sensor Carrage

Designed to be 3D printed with support material, then attached to pvc on both ends.

Schematics

Extra Uart Location

The K64F boasts 5 UART lines, however many of them are taken up by the Avnet cellular breakout. Solder header pins here for an extra UART breakout.

Code

GPRMCSentence.h

C/C++
Parser & Struct for parsing serial GPRMCSentences received from the Xadow GPS on the K64F
#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++
I started with the M2X WNCInterface Demo from the AT&T starter kit section of mbed and modified it to work with my flow & the selected hardware.
//
// 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);

    
  }
}

Credits

Nile Mittow
3 projects • 2 followers
Contact
Thanks to AT&T Sample Code.

Comments

Please log in or sign up to comment.