תקציר
"זוֹרֵעַ צְדָקוֹת. מַצְמִיחַ יְשׁוּעוֹת" (סידור התפילה).
"וילבש צדקה כשריון וכובע ישועה בראשו"(ישעיה נ"ט).
מסביר האדמו"ר הזקן - "ודרשו רז"ל מה שריון זה כל קליפה וקליפה מצטרפת לשריון גדול אף צדקה כל פרוטה ופרוטה מצטרפת לחשבון גדול".
תאור הפרוייקט
רכיבים
- Seeed Wio Terminal Main controller
- Seeed GPS Module Air 530
- חיישן מרחק VCNL4040 של חברת Adafruit
- כבל USB Type A- TypeC עם מחבר זוויתי
הדפסות תלת מימד והרכבה
חומרה, חיווט ותוכנה
חיווט חשמלי
קוד המקור
// 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(); | |
} | |