Charity is a vital aspect of building a compassionate and empathetic society. It involves voluntarily giving time, resources, or assistance to those in need without expecting anything in return. Charitable acts not only help the recipients but also provide a sense of purpose and fulfilment to the givers. Charity can help alleviate poverty, provide healthcare, education, and other basic necessities of life. It can also promote social cohesion and bring people together to work towards a common goal. Ultimately, charity serves as a reminder of our shared humanity and the importance of supporting each other, especially during difficult times.
Based on the following part of Jewish morning prayer:
To the Blessed God they sing; to the living King they utter hymns and praises. For God alone performs mighty deeds, making new things, sowing righteousness, causing deliverance to sprout forth, creating healing. Awesome in praise, Master of wonders, God, in His goodness, renews Creation day after day. So sang the Psalmist: “Praise the Creator of great lights, for God’s kindness is everlasting".
The reason for using the word 'sowing' in the context of the mitzvot of charity is because when a seed is planted in the ground, a plant grows that is greater in quantity and quality than the seed itself. This growth is achieved through the process of sowing, where the seed rots and is absorbed by the soil. By planting the seed in the soil, it activates the vegetative power of the soil, resulting in abundant growth. Similarly, charity works in the same way - by giving to others, we can create positive growth and change that far exceeds the initial donation.
And also by Yeshayahu (Isaiah) - Chapter 59, verse 19
And He donned righteousness like a coat of mail (shield), and a helmet of salvation is upon His head, and He donned garments of vengeance as His attire, and He was clad with zeal as a cloak.
I was inspired by the above sources to build a charity box that incorporates a GPS component and a distance sensor to track the act of giving and display it on a world map. At the end of each cycle, a set of coins is deposited into a separate compartment, and the money is later transferred to a charity through PayBox. This physical process of "sowing charity" in different locations is a powerful educational tool that helps to internalise the importance of giving as part of the mitzvah.
Project descriptionThe design of the charity box is built around the Seeed company's integrated system called Wio Terminal, which provides various components, including two Grove sockets for connecting peripheral devices from Seeed or I2C-based devices from other companies like Adafruit.
Excellent guide on the platform:
https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started
In this project, I utilized two peripheral components: the GPS Air 530 to locate the place of donation and the VCNL4040 distance sensor for detecting coin insertion into the cash register.
The project also includes 3D design and printing of the charity fund's body, which features a compartment for undonated coins and an additional compartment for donated coins. A beep sound is played when donating coins equal to the amount in the compartment for undonated coins, indicating that the total amount should be donated to the charitable institution via Paybox or other means.
The flowchart below illustrates the two activation options, with and without Wifi connection. Pressing the joystick in front of the screen while turning on the computer establishes a connection to the WiFi network. An internet connection is required only when transmitting all the collected coordinates to the Wappsto cloud service, which visually displays where the money was donated. To transmit all the collected coordinates to Wappsto, press the B button in WiFi mode.
The Wio Terminal's GPS and distance sensors are activated when the computer starts normally, with Wifi turned off. One screen displays the current location coordinates, while the other shows the number of donated coins. Use the left C button to browse between the screens. Additionally, I based this project on a resource for reading GPS data, which can be found at:
https://www.hackster.io/SeeedStudio/wio-terminal-gps-ad70e2.
- Seeed Wio Terminal Main controller
- Seeed GPS Module Air 530
- Adafruit VCNL4040 distance sensor
- USB Type A-TypeC cable with angled connector for the power bank
See explicit links in the "Things" section.
3D Design and printingI sketched the design and got lot of help from my brother Boaz Matan in the detailed design.
The three hardware components:
This project does not need soldering!, The connections to the GPS component and the proximity sensor were made possible by using standard Grove connectors.
Donation locations tracking after uploading the coordinates to the Wappsto cloud IoT service.
// 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();
}
Final ResultIt was really fun to work with Wio Terminal and Grove peripherals. The possibilities are endless and give me the freedom to explore my ideas. I would like to thank Seeed company for providing me the main parts for this project.
Some people may question the combination of spirituality and technology, but it's just a part of who I am. I'm eager to connect, share, and discuss with others who share similar perspectives.
Comments