Skip to content

Instantly share code, notes, and snippets.

@richard-to
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richard-to/9738497 to your computer and use it in GitHub Desktop.
Save richard-to/9738497 to your computer and use it in GitHub Desktop.
Some Arduino sample code for SD card, LinkSprite Sim900 shield, and EEPROM
#include <SD.h>
#include <String.h>
const int CS_PIN = 10;
char BACKUP_FILE[] = "backup.txt";
char PENDING_FILE[] = "pending.txt";
void setup()
{
pinMode(CS_PIN, OUTPUT);
Serial.begin(9600);
if (!SD.begin(CS_PIN))
{
Serial.println("Error: Cannot connect to SD card. Make sure pin 10 is connected to CS.");
}
}
void loop()
{
if (Serial.available())
{
switch(Serial.read())
{
case 'b':
sd_readBackupData();
break;
case 'r':
sd_readPendingData();
break;
case 'p':
sd_processPendingData("300,300,300");
break;
case 'd':
sd_deleteAllFiles();
break;
}
}
}
void sd_processPendingData(String data)
{
sd_writeFile(PENDING_FILE, data);
String output = sd_readFile(PENDING_FILE);
int countLines = 0;
int index = 0;
int length = output.length();
while(index < length)
{
if (output[index] == '\n')
{
++countLines;
}
++index;
}
Serial.print("Pending lines: ");
Serial.println(countLines);
if (countLines == 4)
{
Serial.println("Backing up data...");
output.replace("\n", ";");
sd_writeBackupData(output);
sd_clearFile(PENDING_FILE);
Serial.println("Backing up data...done");
}
}
void sd_writeBackupData(String data)
{
sd_writeFile(BACKUP_FILE, data);
}
void sd_readBackupData()
{
sd_readFile(BACKUP_FILE);
}
void sd_readPendingData()
{
sd_readFile(PENDING_FILE);
}
void sd_deleteAllFiles()
{
sd_deleteFile(BACKUP_FILE);
sd_deleteFile(PENDING_FILE);
}
void sd_writeFile(char *filename, String data)
{
Serial.println("CMD: Write data to SD card.");
File fileHandle = SD.open(filename, FILE_WRITE);
if (fileHandle)
{
Serial.println("Writing data...");
fileHandle.println(data);
fileHandle.close();
Serial.println("Writing data...done.");
}
else
{
Serial.println("Error: Could not open file on SD card to write.");
}
}
void sd_clearFile(char *filename)
{
Serial.println("CMD: Clear file contents.");
File fileHandle = SD.open(filename, (O_WRITE | O_CREAT | O_TRUNC));
if (fileHandle)
{
Serial.println("Clearing file contents...done");
fileHandle.close();
}
else
{
Serial.println("Error: Could not open file on SD card to write.");
}
}
String sd_readFile(char *filename)
{
char c;
String buffer = "";
Serial.println("CMD: Read data from SD card.");
File fileHandle = SD.open(filename);
if (fileHandle)
{
Serial.println("Reading data...");
while (fileHandle.available())
{
c = fileHandle.read();
buffer += c;
}
Serial.println(buffer);
fileHandle.close();
Serial.println("Reading data...done.");
}
else
{
Serial.println("Error: Could not open file on SD card to read.");
}
return buffer;
}
void sd_deleteFile(char *filename)
{
Serial.println("CMD: Delete data from SD card.");
File fileHandle = SD.open(filename);
if (fileHandle)
{
fileHandle.close();
if (SD.remove(filename))
{
Serial.println("Deleting data...done.");
}
else
{
Serial.println("Error: Could not delete file.");
}
}
else
{
Serial.println("Error: Could not open file. Does it exist?");
}
}
#include <LowPower.h>
#include <math.h>
#include <SdFat.h>
#include <SPI.h>
#include <String.h>
#include <Time.h>
// Analog Pins
#define THERMISTOR_PIN 5
#define ULTRASONIC_PIN 6
// Digital Pins
#define MOSFET_RTC_PIN 3
#define MOSFET_SD_PIN 4
#define MOSFET_US_PIN 5
#define MOSFET_GSM_PIN 6
#define MOSFET_THERM_PIN 7
#define RTC_CS_PIN 8
#define GSM_PWRKEY 9
#define SD_CS_PIN 10
// Settings
// ----------------------------------
#define SEND_DATA_AFTER_X_READINGS 4
#define SLEEP_CYCLES 4
#define NUM_THERM_READINGS 5
#define THERM_READING_DELAY 20
#define NUM_DISTANCE_READINGS 3
#define DISTANCE_READING_DELAY 200
#define DATA_DELIM ';'
#define BACKUP_FILENAME "backup.txt"
#define UNSENT_FILENAME "unsent.txt"
#define ERROR_FILENAME "error.txt"
#define PHONE_NUMBER "+16505033896"
#define GSM_TIMEOUT 250
#define SMS_TIMEOUT 100
#define ERROR_GSM "GSM Failed"
#define ERROR_SMS "SMS Failed"
// Custom Datatypes
// -------------------------------------
typedef struct {
int distance;
int temperature;
time_t timestamp;
} SensorReading;
// Global Variables
// -------------------------------------
int numCachedReadings = 0;
SensorReading sensorReadings[SEND_DATA_AFTER_X_READINGS];
SdFat sd;
SdFile fh;
void setup()
{
pinMode(SD_CS_PIN, OUTPUT);
pinMode(MOSFET_THERM_PIN, OUTPUT);
}
void loop()
{
// 1. Wake up.
// -----------
// Enter idle state for 8 s with the rest of peripherals turned off.
// 98 sleep cycles will take a reading approximately every 13 minutes.
for (int i = 0; i < SLEEP_CYCLES; ++i)
{
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
// 2. Turn on thermistor.
// ----------------------
digitalWrite(MOSFET_THERM_PIN, HIGH);
// 3. Take 5 thermistor readings. (one every 20ms)
// -----------------------------------------------
int thermReadings[NUM_THERM_READINGS];
for (int i = 0; i < NUM_THERM_READINGS; ++i)
{
thermReadings[i] = analogRead(THERMISTOR_PIN);
delay(THERM_READING_DELAY);
}
// 4. Turn off thermistor.
// -----------------------
digitalWrite(MOSFET_THERM_PIN, LOW);
delay(500);
// 5. Average 5 thermistor readings.
// ---------------------------------
double sumTherm = 0;
for (int i = 0; i < NUM_THERM_READINGS; ++i)
{
sumTherm += thermReadings[i];
}
double avgTherm = sumTherm / NUM_THERM_READINGS;
avgTherm = 1023 / avgTherm - 1;
double R = 10000 / avgTherm;
// 6. Convert average thermistor reading into temperature.
// -------------------------------------------------------
// Steinhart-Hart, modified:
double avgTemperature = ( 3950.0 / (log( R / (10000.0 * exp( -3950.0 / 298.13 ) ) ) ) ) - 273.13;
// 7. Turn on ultrasonic sensor (MOSFET).
// --------------------------------------
digitalWrite(MOSFET_US_PIN, HIGH);
// Calibration time
delay(3000);
// 8. Take 3 distance readings. (One every 200ms)
// ----------------------------------------------
int distanceReadings[NUM_DISTANCE_READINGS];
for (int i = 0; i < NUM_DISTANCE_READINGS; ++i)
{
distanceReadings[i] = analogRead(ULTRASONIC_PIN);
delay(DISTANCE_READING_DELAY);
}
// 9. Turn off ultrasonic sensor (MOSFET).
// ---------------------------------------
digitalWrite(MOSFET_US_PIN, LOW);
delay(500);
// 10. Average 3 distance measurements.
// ------------------------------------
double sumDistance = 0.0;
for (int i = 0; i < NUM_DISTANCE_READINGS; ++i)
{
sumDistance += distanceReadings[i];
}
double avgDistance = sumDistance / NUM_DISTANCE_READINGS;
// 11. Use average temperature to calculate actual distance.
// ---------------------------------------------------------
double adjustedDistance = ( ( 331.1 + .6 * avgTemperature ) / 344.5 ) * avgDistance;
int roundedDistance = round(adjustedDistance);
int roundedTemperature = round(avgTemperature);
// 12. Get time from RTC Shield.
// -----------------------------
digitalWrite(MOSFET_RTC_PIN, HIGH);
delay(500);
rtc_init();
time_t unixTime = rtc_readTimeDate();
digitalWrite(MOSFET_RTC_PIN, LOW);
delay(500);
// 13. Combine time, distance, and temperature into a single string.
// -----------------------------------------------------------------
String dataString = String(unixTime) + " " + String(roundedDistance) + " " + String(roundedTemperature);
// Cache distance and time in global array variable
sensorReadings[numCachedReadings].distance = roundedDistance;
sensorReadings[numCachedReadings].temperature = roundedTemperature;
sensorReadings[numCachedReadings].timestamp = unixTime;
numCachedReadings += 1;
// 14. Turn on SD Card.
// 15. Save data string to text file on SD card: "1399506809 1000 100" (roughly 20 characters)
// -------------------------------------------------------------------------------------------
//Turn on SD MOSFET.
digitalWrite(MOSFET_SD_PIN, HIGH);
// Try to turn on SD card. Should only need to be called once.
sd.begin(SD_CS_PIN, SPI_HALF_SPEED);
sd_writeToFile(BACKUP_FILENAME, dataString);
delay(1000);
// Turn off SD MOSFET.
digitalWrite(MOSFET_SD_PIN, LOW);
delay(500);
// 16. Are there 4 unsent data strings?
// 17. Yes. Send 4 unsent data strings in one SMS. Go to 18.
// -------------------------------------
if (numCachedReadings == SEND_DATA_AFTER_X_READINGS)
{
// 18. Prepare text message
// ---------------------
String textMessage = String(sensorReadings[0].timestamp) + " " + String(sensorReadings[0].distance) + " " + String(sensorReadings[0].temperature);
time_t startTime = sensorReadings[0].timestamp;
int minutesElapsed = 0;
// Format data to send as text message
for (int i = 1; i < numCachedReadings; ++i)
{
minutesElapsed = (sensorReadings[i].timestamp - startTime) / 60;
textMessage += String(DATA_DELIM) + String(minutesElapsed) + " " + String(sensorReadings[i].distance) + " " + String(sensorReadings[i].temperature);
}
// 19. Turn on GSM Shied.
// ---------------------
//Turn on GSM MOSFET.
digitalWrite(MOSFET_GSM_PIN, HIGH);
delay(1000);
// GSM baud rate. Start communication link with GSM shield.
Serial.begin(19200);
delay(100);
// Turn on GSM power pin.
digitalWrite(GSM_PWRKEY, HIGH);
delay(1000);
// Turn off GSM power pin.
digitalWrite(GSM_PWRKEY, LOW);
delay(1000);
// 20. Send text message if GSM Ready
if (gsm_ready(GSM_TIMEOUT) == true)
{
gsm_sendTextMessage(textMessage);
if (gsm_smsSent(SMS_TIMEOUT) == false)
{
// Log SMS failure
digitalWrite(MOSFET_SD_PIN, HIGH);
delay(1000);
sd.begin(SD_CS_PIN, SPI_HALF_SPEED);
sd_writeToFile(ERROR_FILENAME, String(unixTime) + ": " + ERROR_SMS);
sd_writeToFile(UNSENT_FILENAME, textMessage);
digitalWrite(MOSFET_SD_PIN, LOW);
delay(1000);
}
}
else
{
// Log GSM failure
digitalWrite(MOSFET_SD_PIN, HIGH);
delay(1000);
sd.begin(SD_CS_PIN, SPI_HALF_SPEED);
sd_writeToFile(ERROR_FILENAME, String(unixTime) + ": " + ERROR_GSM);
sd_writeToFile(UNSENT_FILENAME, textMessage);
digitalWrite(MOSFET_SD_PIN, LOW);
delay(1000);
}
// Reset number of cached readings
numCachedReadings = 0;
// 20. Turn off GSM.
// -----------------
// Turn on GSM power pin.
digitalWrite(GSM_PWRKEY, HIGH);
delay(1000);
// Turn off GSM power pin.
digitalWrite(GSM_PWRKEY, LOW);
delay(1000);
// Turn off GSM MOSFET.
digitalWrite(MOSFET_GSM_PIN, LOW);
delay(1000);
}
}
void gsm_sendTextMessage(String message)
{
// Because we want to send the SMS in text mode
Serial.print("AT+CMGF=1\r");
delay(500);
// Send sms message, be careful need to add a country code before the cellphone number
Serial.println("AT + CMGS = \"" + String(PHONE_NUMBER) + "\"");
delay(500);
// The content of the message
Serial.println(message);
delay(500);
// The ASCII code of the ctrl+z is 26
Serial.println((char)26);
delay(500);
}
// Clears the GSM buffer to make sure we don't unexpected characters
void gsm_clearBuffer()
{
char c;
while (Serial.available() > 0)
{
c = Serial.read();
}
}
// Polls GSM buffer for "Call Ready" signal.
boolean gsm_ready(int timeout)
{
char gsmReady[] = "Call Ready";
char gsmReadyLength = 10;
int matched = 0;
char c;
while (timeout > 0)
{
while (Serial.available())
{
c = Serial.read();
if (c == gsmReady[matched])
{
matched += 1;
if (matched == gsmReadyLength)
{
return true;
}
} else {
matched = 0;
}
}
timeout -= 1;
delay(100);
}
return false;
}
// Polls GSM buffer for two "OK" messages or one "ERROR" to
// check if SMS was sent successfully.
boolean gsm_smsSent(int timeout)
{
char smsOK[] = "OK";
int smsOKLength = 2;
int numOKNeeded = 2;
char smsError[] = "ERROR";
int smsErrorLength = 5;
char c;
int matchedError = 0;
int matchedOK = 0;
int foundOK = 0;
while (timeout > 0)
{
while (Serial.available())
{
c = Serial.read();
if (c == smsError[matchedError])
{
matchedError += 1;
matchedOK = 0;
if (matchedError == smsErrorLength)
{
return false;
}
}
else if (c == smsOK[matchedOK])
{
matchedOK += 1;
matchedError = 0;
if (matchedOK == smsOKLength)
{
foundOK += 1;
}
if (foundOK == smsOKLength)
{
return true;
}
} else {
matchedOK = 0;
matchedError = 0;
}
}
timeout -= 1;
delay(100);
}
return false;
}
// Saves data to specified file on SD Card
//
// Returns true if file written successfully, otherwise returns false.
//
boolean sd_writeToFile(char *filename, String data)
{
if (fh.open(filename, O_RDWR | O_CREAT | O_AT_END))
{
fh.println(data);
fh.close();
return true;
}
else
{
return false;
}
}
// Reads file from sd card
// TODO(richard-to): Need to test this implementation.
// Not currently used in sketch.
String sd_readFile(char *filename)
{
char c;
String buffer = "";
if (fh.open(filename, O_READ))
{
while (fh.available())
{
c = fh.read();
buffer += c;
}
fh.close();
}
return buffer;
}
// Initializes RTC Clock
int rtc_init()
{
pinMode(RTC_CS_PIN, OUTPUT);
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE3);
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(0x8E);
SPI.transfer(0x60);
digitalWrite(RTC_CS_PIN, HIGH);
delay(10);
}
// Sets time on RTC CLOCK
//
// Notes:
// * Year is calculated as `current year - 1970`. So 2014 would be 44 and not 14.
// * Time is in UTC
// * day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59)
//
int rtc_setTimeDate(int d, int mo, int y, int h, int mi, int s)
{
int timedate[7] = {s, mi, h, 0, d, mo, y};
for (int i = 0; i <= 6; ++i)
{
if (i == 3)
{
i++;
}
int b = timedate[i] / 10;
int a = timedate[i] - b * 10;
if (i == 2)
{
if (b == 2)
{
b = B00000010;
}
else if (b == 1)
{
b = B00000001;
}
}
timedate[i] = a + (b << 4);
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(i+0x80);
SPI.transfer(timedate[i]);
digitalWrite(RTC_CS_PIN, HIGH);
}
}
// Gets unix timestamp from RTC clock
time_t rtc_readTimeDate()
{
int timedate[7];
for (int i = 0; i <= 6; ++i)
{
if (i == 3)
{
i++;
}
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(i + 0x00);
unsigned int n = SPI.transfer(0x00);
digitalWrite(RTC_CS_PIN, HIGH);
int a = n & B00001111;
if (i == 2)
{
int b = (n & B00110000) >> 4;
if (b == B00000010)
{
b = 20;
} else if (b == B00000001)
{
b = 10;
}
timedate[i] = a + b;
}
else if (i == 4)
{
int b = (n & B00110000) >> 4;
timedate[i] = a + b * 10;
}
else if (i == 5)
{
int b = (n & B00010000) >> 4;
timedate[i] = a + b * 10;
}
else if (i == 6)
{
int b = (n & B11110000) >> 4;
timedate[i] = a + b * 10;
}
else
{
int b = (n & B01110000) >> 4;
timedate[i] = a + b * 10;
}
}
TimeElements tm;
tm.Year = timedate[6];
tm.Month = timedate[5];
tm.Day = timedate[4];
tm.Hour = timedate[2];
tm.Minute = timedate[1];
tm.Second = timedate[0];
return makeTime(tm);
}
// TODO(richard-to): Table ID and API key should be stored as user parameters.
// This can be found in your Google Fusion tables url. Look for "docid" query parameter
var tableId = "Table-ID-Here";
// Access key is needed for read permissions https://developers.google.com/fusiontables/docs/v1/using#auth
var apiKey = "API-Key-Here";
// Main Google scripts web app entry point
function doGet() {
// Start with 1 day for example purposes
var startDate = '5/12/2014';
var endDate = '5/13/2014';
// Gets data from Google Fusion tables
var data = queryFusionDataWithDateRange(tableId, apiKey, startDate, endDate);
// Populates data table that will be used to build charts
var table = populateDataTable(data);
// Present our data as a scatter chart
var scatterChart = buildScatterChartView();
// Create a control to filter specific sensors
var sensorFilter = Charts.newCategoryFilter()
.setFilterColumnLabel("Sensor")
.setLabel("Select Sensors")
.build();
var app = UiApp.createApplication();
// Create dashboard to layout controls and charts
var dashboard = buildDashboard(app, table, [sensorFilter], [scatterChart]);
// Create a dropdown menu to change chart types
var chartSelectorLabel = app.createInlineLabel("Select chart");
var chartSelector = app.createListBox(false).setName('chart_selector');
chartSelector.addItem('Data table').addItem('Scatter plot').setSelectedIndex(1);
var chartSelectorPanel = app.createHorizontalPanel();
chartSelectorPanel.add(chartSelectorLabel).add(chartSelector).setSpacing(20);
// Create controls to filter by date
var startDateBox = app.createDateBox().setId("start_datebox").setValue(new Date(startDate));
var startDateLabel = app.createInlineLabel("Start");
var endDateBox = app.createDateBox().setId("end_datebox").setValue(new Date(endDate));
var endDateLabel = app.createInlineLabel("End");
var dateFilterButton = app.createButton("Filter by date");
var datePanel = app.createHorizontalPanel();
datePanel.add(startDateLabel).add(startDateBox)
.add(endDateLabel).add(endDateBox)
.add(dateFilterButton).setSpacing(20);
// When filter date button pressed or chart selected, send chart type, start date, end date to update charts
var updateChartHandler = app.createServerHandler("updateChartHandler")
.addCallbackElement(startDateBox).addCallbackElement(endDateBox).addCallbackElement(chartSelector);
dateFilterButton.addClickHandler(updateChartHandler);
chartSelector.addChangeHandler(updateChartHandler);
// Add chart selector panel and dashboard to app to make them visible
app.add(chartSelectorPanel);
app.add(datePanel);
app.add(dashboard);
return app;
}
// This code is executed when use clicks "Filter date button" or selects a different chart type
// TODO(richard-to): Need to clean up code so code isn't duplicated from `doGet`
function updateChartHandler(eventInfo) {
var app = UiApp.getActiveApplication();
var chartType = eventInfo.parameter.chart_selector;
var startDate = eventInfo.parameter.start_datebox;
var endDate = eventInfo.parameter.end_datebox;
var formattedStartDate = null;
var formattedEndDate = null;
if (startDate)
{
formattedStartDate = toFusionDate(startDate);
}
if (endDate)
{
endDate.setDate(endDate.getDate() + 1);
formattedEndDate = toFusionDate(endDate);
}
// Remove old charts
app.remove(app.getElementById('dashboard'));
// Gets data from Google Fusion tables
var data = queryFusionDataWithDateRange(tableId, apiKey, formattedStartDate, formattedEndDate);
// Populates data table that will be used to build charts
var table = populateDataTable(data);
// Present our data as a scatter chart or data table depending on chart type
var chartView = null;
if (chartType == 'Scatter plot')
{
chartView = buildScatterChartView();
}
else
{
chartView = buildDataTableChartView();
}
// Create a control to filter specific sensors
var sensorFilter = Charts.newCategoryFilter()
.setFilterColumnLabel("Sensor")
.setLabel("Select Sensors")
.build();
// Create dashboard to layout controls and charts
var dashboard = buildDashboard(app, table, [sensorFilter], [chartView]);
// Add updated charts back to app
app.add(dashboard);
return app;
}
// Converts Javascript Date object to a formatted date usable for Fusion table queries
function toFusionDate(date)
{
return (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear();
}
function populateDataTable(data)
{
var table = Charts.newDataTable()
.addColumn(Charts.ColumnType.DATE, "Date")
.addColumn(Charts.ColumnType.NUMBER, "Distance")
.addColumn(Charts.ColumnType.NUMBER, "Temperature")
.addColumn(Charts.ColumnType.STRING, "Sensor");
var rows = data.rows;
for (var i = 0; i < rows.length; ++i)
{
table.addRow([new Date(rows[i][0]), parseInt(rows[i][1]), parseInt(rows[i][2]), rows[i][3]]);
}
table.build();
return table;
}
function buildDataTableChartView()
{
return Charts.newTableChart()
.setDimensions(1024, 500)
.build();
}
function buildScatterChartView()
{
// Present our data as a scatter chart
return Charts.newScatterChart()
.setTitle('Distance and Temperature')
.setXAxisTitle('Date Time')
.setYAxisTitle('Distance/Chart')
.setDimensions(1024, 500)
.setLegendPosition(Charts.Position.NONE)
.setPointStyle(Charts.PointStyle.TINY)
.setDataViewDefinition(Charts.newDataViewDefinition().setColumns([0,1,2]))
.build();
}
// Create Dashboard to layout controls and charts
function buildDashboard(app, table, controls, charts)
{
var dashboard = Charts.newDashboardPanel()
.setDataTable(table)
.bind(controls, charts)
.build();
var controlPanel = app.createHorizontalPanel();
for (var i = 0; i < controls.length; ++i)
{
controlPanel.add(controls[i]);
}
var verticalPanel = app.createVerticalPanel().setSpacing(20);
verticalPanel.add(controlPanel);
for (var i = 0; i < charts.length; ++i)
{
verticalPanel.add(app.createHorizontalPanel().add(charts[i]));
}
dashboard.add(verticalPanel);
dashboard.setId('dashboard');
return dashboard;
}
// Get data from fusion tables with option to filter by date range
function queryFusionDataWithDateRange(tableId, apiKey, startDate, endDate)
{
var query = "SELECT 'Date', Distance, Temperature, Sensor FROM " + tableId;
if (startDate || endDate)
{
query += " WHERE";
}
if (startDate)
{
query += " 'Date' >= '" + startDate + "'";
}
if (startDate && endDate)
{
query += " AND";
}
if (endDate)
{
query += " 'Date' <= '" + endDate + "'";
}
return fetchFusionData(apiKey, encodeURIComponent(query));
}
// Performs the HTTP GET request to get fusion data in JSON format
function fetchFusionData(apiKey, query) {
var URL = "https://www.googleapis.com/fusiontables/v1/query?key=" + apiKey + "&sql=" + query;
var response = UrlFetchApp.fetch(URL, {method: "get",});
return JSON.parse(response.getContentText());
}
#include <SoftwareSerial.h>
#include <String.h>
SoftwareSerial Sim900(7, 8);
#define TIMEOUT 250
#define GSM_READY_MSG "Call Ready"
#define GSM_READY_MSG_LEN 10
void setup()
{
Serial.begin(9600);
Sim900.begin(19200);
}
void loop()
{
if (Serial.available())
{
switch(Serial.read())
{
case 'm':
gsm_clearBuffer();
gsm_sendTextMessage();
if (gsm_smsSent(TIMEOUT))
{
Serial.println("SMS Sent!");
}
else
{
Serial.println("SMS Not Sent!");
}
break;
case 'c':
gsm_command();
break;
case 't':
gsm_checkTime();
break;
case 'p':
gsm_clearBuffer();
gsm_power();
if (gsm_isReady(GSM_READY_MSG, GSM_READY_MSG_LEN, TIMEOUT))
{
Serial.println("GSM Ready!");
}
else
{
Serial.println("GSM Not Ready!");
}
break;
}
}
if (Sim900.available())
{
Serial.write(Sim900.read());
}
}
void gsm_power()
{
Serial.println("Toggle GSM Power");
digitalWrite(9, HIGH);
delay(1000);
digitalWrite(9, LOW);
}
void gsm_clearBuffer()
{
char c;
while (Sim900.available() > 0)
{
c = Sim900.read();
}
}
boolean gsm_isReady(char keyword[], int length, int timeout)
{
char c;
int matched = 0;
while (timeout > 0)
{
while (Sim900.available())
{
c = Sim900.read();
if (c == keyword[matched])
{
matched += 1;
if (matched == length)
{
return true;
}
} else {
matched = 0;
}
}
timeout -= 1;
delay(100);
}
return false;
}
boolean gsm_smsSent(int timeout)
{
char smsOK[] = "OK";
int smsOKLength = 2;
int numOKNeeded = 2;
char smsError[] = "ERROR";
int smsErrorLength = 5;
char c;
int matchedError = 0;
int matchedOK = 0;
int foundOK = 0;
while (timeout > 0)
{
while (Sim900.available())
{
c = Sim900.read();
if (c == smsError[matchedError])
{
matchedError += 1;
matchedOK = 0;
if (matchedError == smsErrorLength)
{
return false;
}
}
else if (c == smsOK[matchedOK])
{
matchedOK += 1;
matchedError = 0;
if (matchedOK == smsOKLength)
{
foundOK += 1;
}
if (foundOK == smsOKLength)
{
return true;
}
} else {
matchedOK = 0;
matchedError = 0;
}
}
timeout -= 1;
delay(100);
}
return false;
}
void gsm_sendTextMessage()
{
Sim900.print("AT+CMGF=1\r");
delay(100);
Sim900.println("AT + CMGS = \"+19076029802\"");
delay(100);
Sim900.println("A test message");
delay(100);
Sim900.println((char)26);
delay(100);
}
void gsm_checkTime()
{
String time = gsm_getTime();
Serial.println(time);
}
String gsm_getTime()
{
char c;
boolean foundTime = false;
String time = "";
Sim900.println("AT+CCLK?");
delay(100);
while (Sim900.available() != 0)
{
c = Sim900.read();
if (c == '"' && foundTime == false)
{
foundTime = true;
}
else if (c == '"' && foundTime == true)
{
foundTime = false;
}
else if (foundTime == true)
{
time += c;
}
}
return time;
}
void gsm_command()
{
Serial.println("Enter AT command: ");
char buffer[100];
int inputLength = 0;
while (!Serial.available()) {
delay(100);
}
inputLength = Serial.readBytesUntil('\n', buffer, 99);
buffer[inputLength] = '\0';
Sim900.println(buffer);
delay(100);
}
#include <LowPower.h>
#include <math.h>
#include <SD.h>
#include <SPI.h>
#include <String.h>
#include <Time.h>
// Digital Pins
#define RTC_CS_PIN 8
void setup()
{
Serial.begin(9600);
rtc_init();
// day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59)
rtc_setTimeDate(7, 5, 44, 13, 21, 0);
}
void loop()
{
Serial.println(rtc_readTimeDate());
delay(1000);
}
// Initializes RTC Clock
int rtc_init()
{
pinMode(RTC_CS_PIN, OUTPUT);
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE3);
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(0x8E);
SPI.transfer(0x60);
digitalWrite(RTC_CS_PIN, HIGH);
delay(10);
}
// Sets time on RTC CLOCK
//
// Notes:
// * Year is calculated as `current year - 1970`. So 2014 would be 44 and not 14.
// * Time is in UTC
//
// d = day, mo = month, y = year, h = hours, mi = minutes, s = seconds
//
int rtc_setTimeDate(int d, int mo, int y, int h, int mi, int s)
{
int timedate[7] = {s, mi, h, 0, d, mo, y};
for (int i = 0; i <= 6; ++i)
{
if (i == 3)
{
i++;
}
int b = timedate[i] / 10;
int a = timedate[i] - b * 10;
if (i == 2)
{
if (b == 2)
{
b = B00000010;
}
else if (b == 1)
{
b = B00000001;
}
}
timedate[i] = a + (b << 4);
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(i+0x80);
SPI.transfer(timedate[i]);
digitalWrite(RTC_CS_PIN, HIGH);
}
}
// Gets unix timestamp from RTC clock
time_t rtc_readTimeDate()
{
int timedate[7];
for (int i = 0; i <= 6; ++i)
{
if (i == 3)
{
i++;
}
digitalWrite(RTC_CS_PIN, LOW);
SPI.transfer(i + 0x00);
unsigned int n = SPI.transfer(0x00);
digitalWrite(RTC_CS_PIN, HIGH);
int a = n & B00001111;
if (i == 2)
{
int b = (n & B00110000) >> 4;
if (b == B00000010)
{
b = 20;
} else if (b == B00000001)
{
b = 10;
}
timedate[i] = a + b;
}
else if (i == 4)
{
int b = (n & B00110000) >> 4;
timedate[i] = a + b * 10;
}
else if (i == 5)
{
int b = (n & B00010000) >> 4;
timedate[i] = a + b * 10;
}
else if (i == 6)
{
int b = (n & B11110000) >> 4;
timedate[i] = a + b * 10;
}
else
{
int b = (n & B01110000) >> 4;
timedate[i] = a + b * 10;
}
}
TimeElements tm;
tm.Year = timedate[6];
tm.Month = timedate[5];
tm.Day = timedate[4];
tm.Hour = timedate[2];
tm.Minute = timedate[1];
tm.Second = timedate[0];
return makeTime(tm);
}
#include "LowPower.h"
#include <SD.h>
#include <String.h>
// Digital Pins
const int SD_CS_PIN = 10;
char BACKUP_FILENAME[] = "backup.txt";
void setup()
{
pinMode(SD_CS_PIN, OUTPUT);
SD.begin(SD_CS_PIN);
Serial.begin(9600);
}
void loop()
{
for (int i = 0; i < 2; i++)
{
LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_ON,TWI_OFF);
}
delay(5000);
Serial.println("READY");
test_write();
}
void test_write()
{
String dataString = "TEST";
if (sd_writeToFile(BACKUP_FILENAME, dataString)) {
Serial.println("SUCESS");
}
else
{
Serial.println("FAILED");
}
}
// Saves data to specified file on SD Card
//
// Returns true if file written successfully, otherwise returns false.
//
boolean sd_writeToFile(char *filename, String data)
{
File fileHandle = SD.open(filename, FILE_WRITE);
if (fileHandle)
{
fileHandle.println(data);
fileHandle.close();
return true;
}
else
{
return false;
}
}
#include <SdFat.h>
#include <LowPower.h>
// Replace SS with the chip slect pin for your SD.
const uint8_t sdChipSelect = SS;
// SD file system.
SdFat sd;
// Log file.
SdFile file;
void setup()
{
// Make sure to turn on mosfet to enable sd
digitalWrite(3, HIGH);
delay(100);
sd.begin(sdChipSelect);
file.open("DATA.CSV", O_CREAT | O_WRITE | O_APPEND);
}
uint32_t n = 0;
void loop()
{
digitalWrite(3, LOW);
delay(100);
for (int i = 0; i < 2; ++i)
{
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
digitalWrite(3, HIGH);
delay(100);
file.println(n++);
file.sync();
}
// Fetches the list of existing Sheets from the ScriptDb instance.
//
// Params:
// db: ScriptDb instance
//
// Returns:
// A hash table with a Sheet metadata associated with name
// of the the Sheet.
function fetchSheetMetaFromDb(db)
{
var data = {};
var result = db.query({});
while (result.hasNext()) {
var item = result.next();
data[item.name] = item;
}
return data;
}
// Gets the sheet object to add sensor readings to.
//
// If a sheet does not exist for the specific sensor (identified
// by phone number, then a new sheet is created and saved to the ScriptDb
// instance.
//
// Params:
// sheet_name: The name of the Sheet. Should be unique since sensor is id'd by phone number
// sheet_meta: Metadata for sheet object. Just name and url right now.
// db: ScriptDb instance
//
// Returns:
// Sheet object
//
// Notes:
//
// If the url points to a sheet that does not exist, then the sheet metadata will
// be deleted from ScriptDb instance.
//
// Beware that moving the Sheet to the trash means that the sheet is still valid. The
// The sheet will need to be removed completely
//
// TODO:
//
// If a sheet is moved to the trash, it should not be considered an active sheet.
function getSheetObjectByName(sheet_name, sheet_meta, db)
{
var sheet = undefined;
if (sheet_meta[sheet_name] !== undefined) {
try {
sheet = SpreadsheetApp.openByUrl(sheet_meta[sheet_name].url);
} catch(e) {
db.remove(sheet_meta[sheet_name]);
sheet = undefined;
sheet_meta[sheet_name] = undefined;
}
}
if (sheet === undefined) {
sheet = SpreadsheetApp.create(sheet_name);
var item = {
name: sheet_name,
url: sheet.getUrl()
};
sheet_meta[sheet_name] = db.save(item);
}
return sheet;
}
// Gets starred messages from Gmail and adds parsed data to Sheet
//
// Params:
// sheet_meta: A hash table with existing sheets
// db: ScriptDb instance
//
// TODO:
//
// - Need to keep an eye out for errors due to the automatic line breaks.
//
// Notes:
//
// - Gmail throttles the script if too many actions are taken (ie, fetch/read/star/unstar msgs)
// Should not be a problem at a rate of one email every five hours.
// - Ideally there should be a way to prevent cases where a message is processed
// but not unstarred due to a Gmail error. This would lead to a message being processed twice.
// However the probability of that happening is very low so long as you are not processing
// greater than 100 messages, in which case Gmail will throttle the script or AppScript max execution
// time will be reached.
function processStarredThreads(sheet_meta, db) {
var threads = GmailApp.getStarredThreads();
for (var t in threads) {
var thread = threads[t];
var subject = thread.getFirstMessageSubject();
var msgs = thread.getMessages();
var msg_count = thread.getMessageCount();
var sheet = getSheetObjectByName(subject, sheet_meta, db);
var active_sheet = sheet.setActiveSheet(sheet.getSheets()[0]);
// Loop through thread of messages. Each thread contains messages
// from a single sensor up to 100 messages.
for (var i = 0; i < msg_count; ++i) {
// Starred messages have not been processed yet.
if (msgs[i].isStarred()) {
// Get the body text from the email message.
var text = msgs[i].getPlainBody();
// Break the message into separate lines
// The emails seem to add an automatic line
// break at about 73-75 characters.
var lines = text.split("\r\n");
var data = "";
// The data we need will be on 2-3 lines if
// we maxed out most of the text message length.
//
// Hence we need to combine the lines again.
for (var j = 0; j < lines.length; ++j) {
if (lines[j].length > 0) {
data += lines[j];
} else {
break;
}
}
// For some reason a double space is present in the
// recombined message data. This extra space occurs exactly
// at the line break.
//
// Here we split the sensor readings into individual
// readings of - timestamp distance temperature.
data = data.trim().replace(" ", " ").split(';');
var readings = [];
for (var j = 0; j < data.length; ++j) {
readings.push(data[j].split(' '));
}
// The first timestamp should be a full unix timestamp.
// We will need this to calculate the other timestamps.
// The other timestamps consist of minutes after the first
// reading in the message.
var startTime = parseInt(readings[0][0]);
sheet.appendRow([startTime, readings[0][1], readings[0][2],data.toString()]);
for (var j = 1; j < readings.length; ++j) {
sheet.appendRow([
startTime + (60 * parseInt(readings[j][0])),
readings[j][1],
readings[j][2]
]);
}
// Unstar the message now that we've added the reading to the Sheet.
msgs[i].unstar();
}
// Every fifteen readings, let's pause for one second.
// Not sure if this helps avoid the Gmail throttling though.
if (i % 15 == 0) {
Utilities.sleep(1000);
}
}
}
}
// Main program to execute.
function main_program()
{
var sheet;
var db = ScriptDb.getMyDb();
var sheet_meta = fetchSheetMetaFromDb(db);
processStarredThreads(sheet_meta, db);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment