/******************************************** Main Code Medicine Dispenser Reminder System By : Sydney Oskin, Bryce Dickerson, Aaliyah Smith, Eric Zheng, Kara Garvey For : Senior Design 2024 *********************************************/ #include #include #include "time.h" #include "sntp.h" #include #include #include // Hardware-specific library #include #include #include #include #include "ESPAsyncWebServer.h" #include "AsyncTCP.h" #include #include // put function declarations here: /*************************** WiFi Setup ***************************/ const char* ssid = "ND-guest"; const char* password = ""; /******************************** Auxiliary Control Function Setup *********************************/ int Aux_flag; esp_now_peer_info_t slave; int chan; enum MessageType {PAIRING, DATA,}; MessageType messageType; int counter = 0; // Set your Board and Server ID //uint8_t broadcastAddress[] = {0x3C, 0xE9, 0x0E, 0x08, 0xEA, 0xFC}; // Structure to obtain and display critical medication data on the display typedef struct webData { char objectId[15]; char medicationName[30]; char brandName[30]; char dosage[10]; char timesPerDay[10]; bool morning; bool afternoon; bool night; char dietaryAdvice[50]; char createdAt[25]; char updatedAt[25]; } webData; // Create a webData called myData webData myData; // PARSE CREDENTIALS const char* ParseServer = "https://parseapi.back4app.com/classes/Medication"; const char* ParseAppId = "NniWf4piiW3OmtZNXTLx3BshSv3uhLFNRyhkWIQe"; const char* ParseJavascriptKey = "oznMQJWSxGvvVOFYA2CUssdgzGYv8CNc2hTKzXQ8"; const char* ParseRestAPIKey = "gHvQjepqRBTFw3sShh2ZSfYCGnF9bURsHVL3wGfI"; const char* ParseMasterKey = "B6hwOUkGjmXtvx0xdDWfJrGflSoANbBN78S2AMyN"; const int httpsPort = 443; // Structure example to receive data // Must match the sender structure typedef struct struct_message { uint8_t msgType; uint8_t id; uint8_t testval; //float hum; unsigned int readingId; } struct_message; typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; struct_message incomingReadings; struct_message outgoingSetpoints; struct_pairing pairingData; AsyncWebServer server(80); AsyncEventSource events("/events"); const char index_html[] PROGMEM = R"rawliteral( ESP-NOW DASHBOARD

ESP-NOW DASHBOARD

BOARD #1 - TEMPERATURE

°C

Reading ID:

BOARD #1 - HUMIDITY

%

Reading ID:

BOARD #2 - TEMPERATURE

°C

Reading ID:

BOARD #2 - HUMIDITY

%

Reading ID:

)rawliteral"; void readDataToSend() { outgoingSetpoints.msgType = DATA; outgoingSetpoints.id = 0; outgoingSetpoints.testval = Aux_flag; //outgoingSetpoints.hum = random(0, 100); outgoingSetpoints.readingId = counter++; } // ---------------------------- esp_ now ------------------------- void printMAC(const uint8_t * mac_addr){ char macStr[18]; snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); } bool addPeer(const uint8_t *peer_addr) { // add pairing memset(&slave, 0, sizeof(slave)); const esp_now_peer_info_t *peer = &slave; memcpy(slave.peer_addr, peer_addr, 6); slave.channel = chan; // pick a channel slave.encrypt = 0; // no encryption // check if the peer exists bool exists = esp_now_is_peer_exist(slave.peer_addr); if (exists) { // Slave already paired. Serial.println("Already Paired"); return true; } else { esp_err_t addStatus = esp_now_add_peer(peer); if (addStatus == ESP_OK) { // Pair success Serial.println("Pair success"); return true; } else { Serial.println("Pair failed"); return false; } } } // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("Last Packet Send Status: "); Serial.print(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success to " : "Delivery Fail to "); printMAC(mac_addr); Serial.println(); } void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Serial.print(len); Serial.print(" bytes of data received from : "); printMAC(mac_addr); Serial.println(); StaticJsonDocument<1000> root; String payload; uint8_t type = incomingData[0]; // first message byte is the type of message switch (type) { case DATA : // the message is data type memcpy(&incomingReadings, incomingData, sizeof(incomingReadings)); // create a JSON document with received data and send it by event to the web page root["id"] = incomingReadings.id; root["test val"] = incomingReadings.testval; //root["humidity"] = incomingReadings.hum; //root["readingId"] = String(incomingReadings.readingId); serializeJson(root, payload); Serial.print("event send :"); serializeJson(root, Serial); events.send(payload.c_str(), "new_readings", millis()); Serial.println(); break; case PAIRING: // the message is a pairing request memcpy(&pairingData, incomingData, sizeof(pairingData)); Serial.println(pairingData.msgType); Serial.println(pairingData.id); Serial.print("Pairing request from: "); printMAC(mac_addr); Serial.println(); Serial.println(pairingData.channel); if (pairingData.id > 0) { // do not replay to server itself if (pairingData.msgType == PAIRING) { pairingData.id = 0; // 0 is server // Server is in AP_STA mode: peers need to send data to server soft AP MAC address WiFi.softAPmacAddress(pairingData.macAddr); pairingData.channel = chan; Serial.println("send response"); esp_err_t result = esp_now_send(mac_addr, (uint8_t *) &pairingData, sizeof(pairingData)); addPeer(mac_addr); } } break; } } void initESP_NOW(){ // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); } /*************************** Structure Setup Web Connect LCD Screen ***************************/ // Function to populate the struct from JSON object void populateStructFromJson(JsonObject medication, webData &data) { strncpy(data.objectId, medication["objectId"].as(), sizeof(data.objectId) - 1); strncpy(data.medicationName, medication["medicationName"].as(), sizeof(data.medicationName) - 1); strncpy(data.brandName, medication["brandName"].as(), sizeof(data.brandName) - 1); strncpy(data.dosage, medication["dosage"].as(), sizeof(data.dosage) - 1); strncpy(data.timesPerDay, medication["timesPerDay"].as(), sizeof(data.timesPerDay) - 1); data.morning = medication["morning"].as(); data.afternoon = medication["afternoon"].as(); data.night = medication["night"].as(); strncpy(data.dietaryAdvice, medication["dietaryAdvice"].as(), sizeof(data.dietaryAdvice) - 1); strncpy(data.createdAt, medication["createdAt"].as(), sizeof(data.createdAt) - 1); strncpy(data.updatedAt, medication["updatedAt"].as(), sizeof(data.updatedAt) - 1); } /*void parseAndPrintResponse(String jsonResponse) { // Print or process the extracted details as needed Serial.println("objectId: " + String(myData.objectId)); Serial.println("Medication Name: " + String(myData.medicationName)); Serial.println("Brand Name: " + String(myData.brandName)); Serial.println("Dosage: " + String(myData.dosage)); Serial.println("Times Per Day: " + String(myData.timesPerDay)); Serial.println("Morning: " + String(myData.morning ? "true" : "false")); Serial.println("Afternoon: " + String(myData.afternoon ? "true" : "false")); Serial.println("Night: " + String(myData.night ? "true" : "false")); Serial.println("Dietary Advice: " + String(myData.dietaryAdvice)); Serial.println("Created At: " + String(myData.createdAt)); Serial.println("Updated At: " + String(myData.updatedAt)); Serial.println("-----------------------------"); }*/ /*************************** Real Time Setup ***************************/ #define AM_Time 9 #define PM_Time 18 const char* ntpServer1 = "pool.ntp.org"; const char* ntpServer2 = "time.nist.gov"; const long gmtOffset_sec = -18000; const int daylightOffset_sec = 3600; const char* time_zone = "CET-1CEST,M3.5.0,M10.5.0/3"; int reminder_sys_o_f = 0; void printLocalTime(){ struct tm timeinfo; if(!getLocalTime(&timeinfo)){ Serial.println("No time available (yet)"); return; } Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); } void timeavailable(struct timeval *t){ Serial.println("Got time adjustment from NTP!"); printLocalTime(); } /*************************** Millis Delay Setup ***************************/ unsigned long DELAY_TIME = 10000; // 5 minutes = 300000, 1min = 60000 unsigned long delayStart = 0; // the time the delay started bool delayRunning = false; // true if still waiting for delay to finish /*************************** Motor Setup ***************************/ // Motor A int motor1Pin1 = 17; int motor1Pin2 = 22; int enable1Pin = 25; int runMotorFlag = 1; /******************************************************** Function: runMotor Purpose: runs motor to turn the medicine dispenser lid one space Runs once per time of day called *********************************************************/ void runMotor() { // Rotates the Motor A digitalWrite(motor1Pin1, HIGH); digitalWrite(motor1Pin2, LOW); delay(925); // Stop the DC motor1 digitalWrite(motor1Pin1, LOW); digitalWrite(motor1Pin2, LOW); delay(1000); runMotorFlag = 0; } /*************************** LCD_TFT Setup ***************************/ TFT_eSPI tft = TFT_eSPI(); // Invoke custom library #define TFT_GREY 0x5AEB // New colour /******************************************************** Function: writeLCD Purpose: writes to LCD information on medicine Runs at each reminder cycle *********************************************************/ void writeLCD(int hour){ if(hour == AM_Time) { tft.fillScreen(TFT_BLACK); tft.setCursor(0, 0, 2); tft.setTextColor(TFT_WHITE,TFT_BLACK); tft.setTextSize(5); tft.println(myData.medicationName); tft.setTextFont(1); tft.println(myData.dietaryAdvice); } else if(hour == PM_Time) { tft.fillScreen(TFT_BLACK); tft.setCursor(0, 0, 2); tft.setTextColor(TFT_WHITE,TFT_BLACK); tft.setTextSize(5); tft.println(myData.medicationName); tft.setTextFont(1); tft.println(myData.dietaryAdvice); } } /*************************** Button Interrupt Setup ***************************/ struct Button { const uint8_t PIN; uint32_t numberKeyPresses; bool pressed; }; Button button1 = {26, 0, false}; unsigned long button_time = 0; unsigned long last_button_time = 0; /******************************************************** Function: Button interrupt Purpose: interrupts function when button is pressed to turn off reminder system Runs when button on GPIO26 is pressed *********************************************************/ void IRAM_ATTR isr() { //Turn off LCD tft.fillScreen(TFT_BLACK); tft.setCursor(0, 0, 2); //Turn off auxiliary reminders Aux_flag = 0; //turn off button interrupt detachInterrupt(button1.PIN); //turn off reminder system reminder_sys_o_f = 0; //Example code button_time = millis(); if(button_time - last_button_time > 250) { button1.numberKeyPresses++; Aux_flag = 0; reminder_sys_o_f = 0; Serial.printf("Button has been pressed %u times\n", button1.numberKeyPresses); last_button_time = button_time; } } /******************************************************** Function: Setup Purpose: Initialize all components and set needed flags Only runs once *********************************************************/ void setup() { // put your setup code here, to run once: Serial.begin(115200); /*************************** Real Time Setup ***************************/ sntp_set_time_sync_notification_cb( timeavailable ); sntp_servermode_dhcp(1); //optional configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2); /*************************** WiFi Setup ***************************/ //new for esp now Serial.println(); Serial.print("Server MAC Address: "); Serial.println(WiFi.macAddress()); WiFi.mode(WIFI_AP_STA); //from before Serial.printf("Connecting to %s ", ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" CONNECTED"); //new for esp now Serial.print("Server SOFT AP MAC Address: "); Serial.println(WiFi.softAPmacAddress()); chan = WiFi.channel(); Serial.print("Station IP Address: "); Serial.println(WiFi.localIP()); Serial.print("Wi-Fi Channel: "); Serial.println(WiFi.channel()); initESP_NOW(); // Start Web server server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html); }); // Events events.onConnect([](AsyncEventSourceClient *client){ if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); } // send event with message "hello!", id current millis // and set reconnect delay to 1 second client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); // start server server.begin(); /*************************** Turn on Reminders ***************************/ reminder_sys_o_f = 1; /*************************** Button Interrupt Setup ***************************/ pinMode(button1.PIN, INPUT_PULLUP); attachInterrupt(button1.PIN, isr, FALLING); /*************************** Millis Delay Setup ***************************/ delayStart = millis(); delayRunning = true; /*************************** TFT_LCD Setup ***************************/ tft.init(); tft.setRotation(1); /*************************** Motor Setup ***************************/ pinMode(motor1Pin1, OUTPUT); pinMode(motor1Pin2, OUTPUT); pinMode(enable1Pin, OUTPUT); } /******************************************************** Function: void loop Purpose: main functional code Runs indefinitely *********************************************************/ void loop() { //Get Real Time info struct tm timeinfo; if(!getLocalTime(&timeinfo)){ Serial.println("No time available (yet)"); return; } int hour = timeinfo.tm_hour; //Check if reminder system is on if(reminder_sys_o_f == 1){ //Check if its the right time if(hour == AM_Time || hour == PM_Time) { Serial.println(hour); //turn on button interrupt attachInterrupt(button1.PIN, isr, FALLING); //run motors if (runMotorFlag == 1){ runMotor(); runMotorFlag = 0; } String url = String(ParseServer) + "/" + "7OmdJ29c9Y"; // Make HTTP GET request to Back4App API HTTPClient http; http.begin(url); // Specify the API endpoint http.addHeader("X-Parse-Application-Id", ParseAppId); // Add necessary headers http.addHeader("X-Parse-REST-API-Key", ParseRestAPIKey); int httpResponseCode = http.GET(); // Send GET request if (httpResponseCode == HTTP_CODE_OK) { String payload = http.getString(); // Get response payload DynamicJsonDocument doc(2048); // Adjust capacity based on your JSON response size DeserializationError error = deserializeJson(doc, payload); if (error) { Serial.print("deserializeJson() failed: "); Serial.println(error.c_str()); return; } // Extract and assign data to struct variables JsonObject medication = doc.as();; // Assuming response is a single object populateStructFromJson(medication, myData); // Parse the JSON response //parseAndPrintResponse(payload); delay(2000); } http.end(); delay(2000); //run LCD screen writeLCD(hour); //Play auxiliary reminders Aux_flag = 1; //} } //ESP_now static unsigned long lastEventTime = millis(); static const unsigned long EVENT_INTERVAL_MS = 5000; if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) { events.send("ping",NULL,millis()); lastEventTime = millis(); readDataToSend(); esp_now_send(NULL, (uint8_t *) &outgoingSetpoints, sizeof(outgoingSetpoints)); } //Millis delay int delay_flag = 0; while(delay_flag == 0) { if (delayRunning && ((millis() - delayStart) >= DELAY_TIME)) { delayStart += DELAY_TIME; // this prevents drift in the delays delay_flag = 1; } } //Turn reminder system on an hour before it needs to check for reminders if(hour == (AM_Time-1) || hour == (PM_Time-1)){ reminder_sys_o_f = 1; runMotorFlag = 1; } }