|
// Written by Asaf Matan |
|
// GPS code is based on |
|
// https://www.hackster.io/SeeedStudio/wio-terminal-gps-ad70e2 Arduino/Wappsto |
|
// example |
|
|
|
#include <Adafruit_VCNL4040.h> |
|
#include <SPI.h> |
|
#include "TFT_eSPI.h" |
|
#include <Seeed_FS.h> |
|
#include <SoftwareSerial.h> |
|
#include <TinyGPS++.h> |
|
#include <WiFiClientSecure.h> |
|
#include <WiFiMulti.h> |
|
#include "Free_Fonts.h" |
|
#include "RawImage.h" |
|
#include "SD/Seeed_SD.h" |
|
#include "Wappsto.h" |
|
#include "WappstoValueTypes.h" |
|
#include "wappsto_config.h" |
|
#include "my_secrets.h" |
|
|
|
WiFiMulti wifiMulti; |
|
WiFiClientSecure client; |
|
Wappsto wappsto(&client); |
|
|
|
#define BUZZER_PIN WIO_BUZZER |
|
|
|
Network *myNetwork; |
|
Device *myDevice; |
|
Value *myLatitudeValue; |
|
Value *myLongitudeValue; |
|
|
|
DeviceDescription_t myDeviceDescription = { |
|
.name = "Location Device1", |
|
.product = "", |
|
.manufacturer = "", |
|
.description = |
|
"Example sending location shown on map in the Wappsto dashboard", |
|
.version = "1.0", |
|
.serial = "00001", |
|
.protocol = "Json-RPC", |
|
.communication = "WiFi", |
|
}; |
|
|
|
double myLatitude = MY_LAT; |
|
double myLongitude = MY_LON; |
|
|
|
void updateLocation(double valLatitude, double valLongitude) { |
|
myLatitudeValue->report(valLatitude); |
|
myLongitudeValue->report(valLongitude); |
|
} |
|
|
|
void refreshLocationCallback(Value *value) { |
|
// Nothing to read from wappsto no nothing to refresh |
|
} |
|
|
|
bool skip_wifi = false; |
|
|
|
const char *ssid = MY_SSID; |
|
const char *password = MY_PASS; |
|
|
|
Adafruit_VCNL4040 vcnl4040 = Adafruit_VCNL4040(); |
|
SoftwareSerial mySerial(0, 1); // RX, TX |
|
TinyGPSPlus gps; |
|
TFT_eSPI tft; |
|
|
|
TinyGPSCustom ExtLat(gps, "GPGGA", 3); // N for Latitude |
|
TinyGPSCustom ExtLng(gps, "GPGGA", 5); // E for Longitude |
|
|
|
const float pi = 3.1415; |
|
int menu = 0, p_menu = 3; |
|
int logging = 0, sat_n = 0; |
|
int coin_counter = 0; |
|
int wappsto_transmited_lines = 0; |
|
String F_name = "db.txt"; |
|
|
|
String p_hour, p_lat, p_lng, p_alt, p_sat, p_date; |
|
String p_coin_counter; |
|
|
|
// for Satellites position |
|
static const int MAX_SATELLITES = 40; |
|
|
|
TinyGPSCustom totalGPGSVMessages(gps, "GPGSV", |
|
1); // $GPGSV sentence, first element |
|
TinyGPSCustom messageNumber(gps, "GPGSV", |
|
2); // $GPGSV sentence, second element |
|
TinyGPSCustom satsInView(gps, "GPGSV", 3); // $GPGSV sentence, third element |
|
TinyGPSCustom satNumber[4]; // to be initialized later |
|
TinyGPSCustom elevation[4]; |
|
TinyGPSCustom azimuth[4]; |
|
TinyGPSCustom snr[4]; |
|
|
|
struct { |
|
bool active; |
|
int elevation; |
|
int azimuth; |
|
int snr; |
|
int dsp; |
|
} sats[MAX_SATELLITES]; |
|
|
|
String N_date, hour0; |
|
bool sd; |
|
String lat0, lng0; |
|
|
|
void disp_title() { |
|
if (menu == 0) { |
|
tft.setFreeFont(FF0); |
|
tft.fillScreen(TFT_BLACK); |
|
tft.setTextSize(3); |
|
tft.drawString("GPS", 120, 3); |
|
tft.setTextSize(2); |
|
|
|
tft.drawString("Date", 30, 42); |
|
tft.drawString("Time", 30, 74); |
|
tft.drawString("LAT", 30, 106); |
|
tft.drawString("LONG", 30, 138); |
|
tft.drawString("ALT", 30, 170); |
|
tft.drawString("Satellites", 30, 202); |
|
if (sd != true) { |
|
tft.drawChar(295, 223, 'S', TFT_WHITE, TFT_RED, 2); |
|
tft.drawChar(307, 223, 'D', TFT_WHITE, TFT_RED, 2); |
|
} |
|
p_hour = " "; |
|
p_lat = " "; |
|
p_lng = " "; |
|
p_alt = " "; |
|
p_sat = " "; |
|
p_date = " "; |
|
} else if (menu == 1) { |
|
tft.setFreeFont(FF0); |
|
tft.fillScreen(TFT_BLACK); |
|
tft.setTextSize(3); |
|
tft.drawString("Stats", 120, 3); |
|
tft.setTextSize(2); |
|
tft.drawString("Coins", 30, 42); |
|
p_coin_counter = " "; |
|
} |
|
} |
|
|
|
String doubleToString(double value, int precision) { |
|
char buffer[20]; |
|
dtostrf(value, 0, precision, buffer); |
|
return String(buffer); |
|
} |
|
|
|
void displayInfo() { |
|
if (gps.location.isValid()) { |
|
lat0 = doubleToString(gps.location.lat(), 6); |
|
lng0 = doubleToString(gps.location.lng(), 6); |
|
} else { |
|
lat0 = "0"; |
|
lng0 = "0"; |
|
} |
|
|
|
if (gps.date.isValid()) { |
|
N_date = String(gps.date.day()) + "/" + String(gps.date.month()) + "/" + |
|
String(gps.date.year()); |
|
} else { |
|
N_date = "00/00/0000"; |
|
} |
|
|
|
if (gps.time.isValid()) { |
|
hour0 = ""; |
|
if (gps.time.hour() < 10) hour0 = "0"; |
|
hour0 += gps.time.hour(); |
|
hour0 += ":"; |
|
if (gps.time.minute() < 10) hour0 += "0"; |
|
hour0 += gps.time.minute(); |
|
hour0 += ":"; |
|
if (gps.time.second() < 10) hour0 += "0"; |
|
hour0 += gps.time.second(); |
|
} else { |
|
hour0 = "00:00:00"; |
|
} |
|
} |
|
|
|
void displayInfoData(void) { |
|
if (menu == 0) { // menu 0 |
|
if (menu != p_menu) { |
|
disp_title(); |
|
p_menu = menu; |
|
} |
|
|
|
if (N_date != p_date) { // Date |
|
tft.fillRect(100, 42, 120, 16, TFT_BLACK); |
|
tft.drawString(N_date, 100, 42); |
|
p_date = N_date; |
|
} |
|
|
|
if (hour0 != p_hour) { // Time |
|
tft.fillRect(100, 74, 120, 16, TFT_BLACK); |
|
tft.drawString(hour0, 100, 74); |
|
p_hour = hour0; |
|
} |
|
|
|
if (lat0 != p_lat) { // Latitude |
|
tft.fillRect(100, 106, 132, 16, TFT_BLACK); |
|
tft.drawString(lat0, 100, 106); |
|
p_lat = lat0; |
|
} |
|
|
|
if (lng0 != p_lng) { // Longitude |
|
tft.fillRect(100, 138, 156, 16, TFT_BLACK); |
|
tft.drawString(lng0, 100, 138); |
|
p_lng = lng0; |
|
} |
|
|
|
if (String(gps.altitude.meters()) != p_alt) { // Altimeter |
|
tft.fillRect(100, 170, 60, 16, TFT_BLACK); |
|
tft.drawString(String(gps.altitude.meters()), 100, 170); |
|
p_alt = String(gps.altitude.meters()); |
|
} |
|
|
|
if (String(gps.satellites.value()) != p_sat) { // N of Satellites |
|
tft.fillRect(160, 202, 32, 16, TFT_BLACK); |
|
tft.drawString(String(gps.satellites.value()), 160, 202); |
|
p_sat = String(gps.satellites.value()); |
|
} |
|
} // end of menu=0 |
|
else if (menu == 1) { |
|
if (menu != p_menu) { |
|
disp_title(); |
|
p_menu = menu; |
|
} |
|
|
|
if (String(coin_counter) != p_coin_counter) { // Coin Counter |
|
tft.fillRect(100, 42, 120, 16, TFT_BLACK); |
|
tft.drawString(String(coin_counter), 100, 42); |
|
p_coin_counter = String(coin_counter); |
|
} |
|
} |
|
} |
|
|
|
void playTone(int tone, int duration) { |
|
for (long i = 0; i < duration * 1000L; i += tone * 2) { |
|
digitalWrite(BUZZER_PIN, HIGH); |
|
delayMicroseconds(tone); |
|
digitalWrite(BUZZER_PIN, LOW); |
|
delayMicroseconds(tone); |
|
} |
|
} |
|
|
|
void writeIntToSD(const char *filename, int value) { |
|
File file = SD.open(filename, FILE_WRITE); |
|
if (file) { |
|
file.print(value); |
|
file.close(); |
|
} else { |
|
Serial.println("Error writing to file"); |
|
} |
|
} |
|
|
|
int readIntFromSD(const char *filename) { |
|
File file = SD.open(filename, FILE_READ); |
|
int value = 0; |
|
if (file) { |
|
value = file.parseInt(); |
|
file.close(); |
|
} else { |
|
Serial.println("Error reading from file"); |
|
} |
|
return value; |
|
} |
|
|
|
String *parseCSV(String data, int &fieldCount) { |
|
String *fields = |
|
new String[data.length()]; // Allocate memory for the maximum |
|
// expected number of fields |
|
fieldCount = 0; |
|
|
|
// Split the data string into fields |
|
int lastIndex = 0; |
|
int commaIndex = data.indexOf(','); |
|
while (commaIndex != -1) { |
|
fields[fieldCount] = data.substring(lastIndex, commaIndex); |
|
fieldCount++; |
|
lastIndex = commaIndex + 1; |
|
commaIndex = data.indexOf(',', lastIndex); |
|
} |
|
fields[fieldCount] = data.substring(lastIndex); |
|
fieldCount++; |
|
|
|
return fields; |
|
} |
|
|
|
void initializeWifi(void) { |
|
Serial.println("Initializing WiFi"); |
|
wifiMulti.addAP(ssid, password); |
|
while (wifiMulti.run() != WL_CONNECTED) { |
|
Serial.print("."); |
|
delay(500); |
|
} |
|
Serial.print("IP address: "); |
|
Serial.println(WiFi.localIP()); |
|
} |
|
|
|
void proximity_handler(void) { |
|
int proximity_value; |
|
proximity_value = vcnl4040.getProximity(); |
|
if (proximity_value > 50) { |
|
if (sd == true) { |
|
Serial.println(F_name); |
|
File myFile = SD.open(F_name, FILE_APPEND); |
|
if (myFile) { |
|
myFile.print(N_date); |
|
myFile.print(","); |
|
myFile.print(hour0); |
|
myFile.print(","); |
|
myFile.print(lat0); |
|
myFile.print(","); |
|
myFile.print(lng0); |
|
myFile.print(","); |
|
myFile.println(gps.altitude.meters()); |
|
myFile.close(); |
|
} |
|
} |
|
|
|
Serial.println("Proximity Value:"); |
|
Serial.println(proximity_value); |
|
coin_counter += 1; |
|
// Store Coin Counter to SD |
|
writeIntToSD("coin_counter.txt", coin_counter); |
|
delay(1500); |
|
} |
|
} |
|
|
|
void wappsto_transmit(void) { |
|
if (digitalRead(WIO_KEY_B) == LOW) { |
|
int line_counter = 0; |
|
// Log to SD card |
|
if (sd == true) { |
|
File myFile = SD.open(F_name, FILE_READ); |
|
if (myFile) { |
|
while (myFile.available()) { |
|
int fieldCount; |
|
String line = myFile.readStringUntil('\n'); |
|
if (line.length() > 0) { |
|
Serial.println(line); |
|
String *fields = parseCSV(line, fieldCount); |
|
// Print the fields |
|
for (int i = 0; i < fieldCount; i++) { |
|
Serial.print("Field "); |
|
Serial.print(i); |
|
Serial.print(": "); |
|
Serial.println(fields[i]); |
|
if (i == 2) { |
|
myLatitude = fields[i].toDouble(); |
|
} else if (i == 3) { |
|
myLongitude = fields[i].toDouble(); |
|
} |
|
} |
|
delete[] fields; // Deallocate the memory used for the fields array |
|
if (line_counter >= wappsto_transmited_lines) { |
|
if (myLatitude > 0) { |
|
Serial.print("Transmited line: "); |
|
Serial.println(line_counter); |
|
updateLocation(myLatitude, myLongitude); |
|
wappsto.dataAvailable(); |
|
} |
|
wappsto_transmited_lines++; |
|
} |
|
line_counter++; |
|
} |
|
} |
|
myFile.close(); |
|
Serial.print("Updating transmitted lines to: "); |
|
Serial.println(wappsto_transmited_lines); |
|
writeIntToSD("wappsto_transmitted_lines.txt", wappsto_transmited_lines); |
|
} else { |
|
Serial.println("Failed to open file"); |
|
} |
|
while (digitalRead(WIO_KEY_B) == LOW) { |
|
} |
|
} |
|
} |
|
} |
|
|
|
void setup() { |
|
Serial.begin(57600); |
|
|
|
pinMode(WIO_KEY_A, INPUT_PULLUP); |
|
pinMode(WIO_KEY_B, INPUT_PULLUP); |
|
pinMode(WIO_KEY_C, INPUT_PULLUP); |
|
pinMode(WIO_5S_PRESS, INPUT_PULLUP); |
|
pinMode(BUZZER_PIN, OUTPUT); |
|
|
|
if (digitalRead(WIO_5S_PRESS) == HIGH) { |
|
mySerial.begin(9600); |
|
} |
|
|
|
// Let things stablize (better than !Serial for this case) |
|
delay(2000); |
|
|
|
tft.begin(); |
|
tft.setRotation(3); |
|
|
|
Serial.print("Initializing SD card..."); |
|
if (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI)) { |
|
Serial.println("initialization failed!"); |
|
sd = false; |
|
tft.drawChar(295, 223, 'S', TFT_WHITE, TFT_RED, 2); |
|
tft.drawChar(307, 223, 'D', TFT_WHITE, TFT_RED, 2); |
|
} else { |
|
Serial.println("initialization done."); |
|
sd = true; |
|
} |
|
|
|
// Display splash screen |
|
tft.setFreeFont(&FreeSerifBoldItalic12pt7b); |
|
tft.fillScreen(TFT_WHITE); |
|
tft.setTextColor(TFT_RED); |
|
tft.drawString("Charity Sower", 90, 10); |
|
|
|
drawImage<uint16_t>("coin.bmp", 100, 50); |
|
|
|
delay(5000); |
|
|
|
tft.fillScreen(TFT_BLACK); // Black background |
|
tft.setTextColor(TFT_YELLOW); |
|
|
|
// read the stored integer from the SD card |
|
coin_counter = readIntFromSD("coin_counter.txt"); |
|
|
|
// for satellites position |
|
// Initialize all the uninitialized TinyGPSCustom objects |
|
for (int i = 0; i < 4; ++i) { |
|
satNumber[i].begin(gps, "GPGSV", 4 + 4 * i); // offsets 4, 8, 12, 16 |
|
elevation[i].begin(gps, "GPGSV", 5 + 4 * i); // offsets 5, 9, 13, 17 |
|
azimuth[i].begin(gps, "GPGSV", 6 + 4 * i); // offsets 6, 10, 14, 18 |
|
snr[i].begin(gps, "GPGSV", 7 + 4 * i); // offsets 7, 11, 15, 19 |
|
} |
|
|
|
if (!vcnl4040.begin()) { |
|
Serial.println("Couldn't find VCNL4040 chip"); |
|
while (1) |
|
; |
|
} |
|
Serial.println("Found VCNL4040 chip"); |
|
|
|
if (digitalRead(WIO_5S_PRESS) == HIGH) { |
|
Serial.println("Skip Wifi Setup"); |
|
skip_wifi = true; |
|
} else { |
|
initializeWifi(); |
|
initializeNtp(); |
|
|
|
wappsto.config(network_uuid, ca, client_crt, client_key, 5, NO_LOGS); |
|
if (wappsto.connect()) { |
|
Serial.println("Connected to Wappsto"); |
|
} else { |
|
Serial.println("Could not connect"); |
|
} |
|
|
|
Serial.println("Create network"); |
|
myNetwork = wappsto.createNetwork("Location Example"); |
|
|
|
Serial.println("Create Device"); |
|
myDevice = myNetwork->createDevice(&myDeviceDescription); |
|
|
|
Serial.println("Create Default val"); |
|
myLatitudeValue = myDevice->createNumberValue(&defaultLatitudeParameter); |
|
myLongitudeValue = myDevice->createNumberValue(&defaultLongitudeParameter); |
|
Serial.println("Register callbacks"); |
|
myLatitudeValue->onRefresh(&refreshLocationCallback); |
|
myLongitudeValue->onRefresh(&refreshLocationCallback); |
|
Serial.println("read last entry transmitted to wappsto"); |
|
wappsto_transmited_lines = readIntFromSD("wappsto_transmitted_lines.txt"); |
|
Serial.print("Last transmitted line:"); |
|
Serial.println(wappsto_transmited_lines); |
|
} |
|
} |
|
|
|
void loop() { |
|
if (skip_wifi == false) { |
|
Serial.println('.'); |
|
wappsto.dataAvailable(); |
|
delay(500); |
|
} |
|
|
|
while (mySerial.available() > 0) { |
|
char c = mySerial.read(); |
|
// Serial.print(c); |
|
gps.encode(c); |
|
} |
|
|
|
displayInfo(); |
|
|
|
displayInfoData(); |
|
|
|
proximity_handler(); |
|
|
|
// Screen toggle |
|
if (digitalRead(WIO_KEY_C) == LOW) { // Page(menu) change |
|
menu++; |
|
if (menu > 1) menu = 0; |
|
while (digitalRead(WIO_KEY_C) == LOW) { |
|
} |
|
} |
|
|
|
if (coin_counter > 10) { |
|
playTone(500, 1000); |
|
coin_counter = 0; |
|
} |
|
|
|
// Handle transmitting of stored coordinates to wappstop incase we are in wifi |
|
// on mode. |
|
wappsto_transmit(); |
|
} |
|
|