ESP32 adjust settings over bluetooth with app

I recently, with the help of kind members of this forum, completed my first real ground up project which was an automatic out of class pass generator to save me time and interruptions in class. It is a thermal printer connected to an arduino with an rtc to keep the time and a few buttons to chose the reason the student is leaving class.

I have since had a friend ask me to make one for them, which got me thinking about how to improve it and make it more user friendly.

My biggest issue is that periodically the time kept on the rtc seems to get lost or corrupted, causing the tickets to print some interesting ideas of dates and times.When this happens i need to open it up, plug the arduino in and reupload the code to set the time, then comment that out and reupload again.

To the point of the post, what would i need to learn to be able to change the time and even what is printed on the tickets through an app connected to the device by bluetooth?

I have already managed to get the system working on an ESP32, but i have no idea what to look into to work out how to make the code editable via an app over bluetooth.

The idea is that on power up it can be discovered and connected to with a phone app, where you can sync the phones time to the device, but also maybe to customize the printouts.

I am hoping someone might have an idea of either some examples that are similar or articles/lessons etc. that would hopefully teach me what i need.

The reality is i dont know what i dont know so this may be way harder than i realise, but i am trying to learn new things, so i thought it worth a shot.

Thanks in advance,
AJ.

3 Likes

Hi Andrew,

The way I have done it in the past is actually through WiFi.

The ESP32 is powerful enough to host a small webpage and a WiFi access point, so you can have a form in that webpage to update data:

The advantage of this is it’s device-agnostic, and avoids all the do-my-devices-talk-this-bluetooth-protocol mess.

You’d likely want to print out a small manual for your friend with the AP/IP/username/password details so they don’t have to bug you for the settings :slight_smile:

-James

3 Likes

One option is to use a SD card. You add an adapter for the card and then include in your code procedures to read the SD card at startup. The additional code goes something like this:

At startup, read the SD card and get the indicator number (see below).
If the number matches the number already stored in memory then just continue on.
If the card does not match the number already stored in memory,
Update memory with the new indicator number
Read a file from the card and set the time to the time written into that file.

So to fix the time, put the card in a PC and write a new code and a suitable time - eg 09:00:00. Insert the card into the device, and at 9am the next morning turn it on.

You can also store each part of the printed message on the card. This could be read from the card at startup, or read form the card each time the message is printed.

(An alternative is to use a file to store the startup time. If the file exists, update the time. If it doesn’t exist, don’t update).
.
See:
Guide to SD Card Module with Arduino | Random Nerd Tutorials.
EEPROM Write | Arduino

4 Likes

Hi Andrew,

Awesome! I’d be keen to see some photos if you dont mind sharing :slight_smile:

Another method that could work if you’re able to connect to WiFi would be using an NTP server to get the time: Getting Date & Time From NTP Server With ESP32

It might be a hassle with your school network though!
Liam

4 Likes

thank you all for your suggestions! i have some reading and messing around to do it seems. In terms of photos of the contraption, i will certainly look to share soon, once i get it out of ‘beta’ so to speak. Thanks all and i will post an update when i make some progress, or due to a complete lack of progress, one or the other. :slight_smile:

1 Like

Hi James,

In response to your post specifically, firstly, thank you for taking the time to pass that on. The reason i was thinking bluetooth is because the school network is a no go (im not even allowed to connect my phone to it, i doubt they want me connecting a bunch of dodgy printer things), would this be possible to do with a phone hotspot? as long as the details of the hotspot?

1 Like

Hi Liam,

Thank you for your response. You are quite correct that the school network won’t be an option sadly. do you think it would be possible to do this with a periodic connection to a phone hotspot? on bootup it looks to connect to the set hotspot, if it detects it it updates the time, if not just runs as normal? im just not sure how compatible things like phone hotspots are with these little boards.

1 Like

thanks jeff for your response. This is a great idea if i cant get my head around the wifi or bluetooth. im going to try and work my way through both options so either way i will learn a ton and get a solution or maybe two,

thanks again!

1 Like

oh wait, use the esp32 as the access point. i get it now. i should have just read the links before i started asking more questions.

1 Like

I have been successful in setting the unit up as an access point.

What i am struggling to understand is how to call the current time from the RTC to be displayed on the webpage, then from there my next step would be to make it so if the time on the RTC doesn’t match the current time you can either press a button on the webpage to automatically set the time on the rtc to the current time or enter it manually, press a button in the webpage and it sends that info to the esp32 and stores it in the rtc.

i have included my code below, so if anyone with a few minutes has time to look it over and help me understand how to call the time from the rtc onto the webpage, as i understand it the html can’t recognise arduino variables and vice versa there are some massive holes in my knowledge here, so help would be wonderful.

#include "Adafruit_Thermal.h"
#include "SoftwareSerial.h"
#include "Wire.h"
#include "RTClib.h"
// Load Wi-Fi library
#include <WiFi.h>

#define TX_PIN 1 // DFRobot  GREEN WIRE  labeled RX on printer
#define RX_PIN 2 // DFRobot   YELLOW WIRE   labeled TX on printer
#define btnPin1 4
SoftwareSerial mySerial(RX_PIN, TX_PIN); // Declare SoftwareSerial obj first
Adafruit_Thermal printer(&mySerial);     // Pass addr to printer constructor

RTC_DS3231 rtc;
// Replace with your network credentials
const char* ssid     = "OOCP-AP";
const char* password = "123456789";
// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;


void setup() {
pinMode(btnPin1,INPUT_PULLUP);
 // DFRobot BLUE WIRE TO 27
  pinMode(27, OUTPUT); digitalWrite(27, LOW);
 //below
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  
  server.begin();
  //above 
  mySerial.begin(9600);


  printer.begin();        // Init printer (same regardless of serial type)
  printer.println(F("------------------------------"));

 
  printer.sleep();      // Tell printer to sleep
  delay(3000L);         // Sleep for 3 seconds
  printer.wake();       // MUST wake() before printing again, even if reset
  printer.setDefault(); // Restore printer to default


  // SETUP RTC MODULE
  if (! rtc.begin()) {
    printer.println("Couldn't find RTC");
    while (1);
  }
//   rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

void loop() {
    WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            
            // Web Page Heading
            client.println("<body><h1>ESP32 Web Server</h1>");
            
            // text to display 
            client.println("Current RTC date/time: ????i need to work this out.");
            client.println("</body></html>");
         // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
                   
        currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }         
DateTime now = rtc.now();
int Push_button_state = digitalRead(btnPin1);
if ( Push_button_state == LOW ){
 printer.println(F("------------------------------"));
 printer.println(F(""));
 printer.setSize('M');
 printer.justify('C');
 printer.println(F("OUT-OF-CLASS-PASS"));
 printer.setSize('L');
 printer.print(F("Date:  "));
 printer.print(now.day(), DEC);
 printer.print("/");
 printer.print(now.month(), DEC);
 printer.println(F(""));
 printer.setSize('M');
 printer.println(F("This student has permission to"));
 printer.println(F("leave the classroom."));
 printer.justify('C');
 char buffer [25];
 uint8_t sec, min, hour;
 sec = now.second();
 min = now.minute();
 hour = now.hour();
 sprintf (buffer, "Left class at: %02u:%02u\n", hour, min);
 printer.print (buffer);
 printer.println(F("Pass valid for 5 minutes."));
 printer.justify('L');
 printer.println(F("Thank you,"));
 printer.justify('C');
 printer.doubleHeightOn();
 printer.println(F("  MR. PATRICK  "));
 printer.println(F(""));
 printer.doubleHeightOff();
 printer.println(F(""));
 printer.println(F("------------------------------"));
}
}

I have got it working! sort of.
After a few late nights i have got it working after a fashion, but its not perfect and i think there is a glaring error staring me in the face that i am failing to recognize. The code is below, but here is the gist;

The idea is that by pressing a new button it will join the set wifi network, update the rtc from an ntp server then disconnect. This works great! i press the button, it connects, updates, disconnects, then when i press the original button i get a ticket with the correct time and date.

The problem however is that the original button will not work unless it has done the wifi update process, and i cannot work out why.

Any help would be greatly appreciated.

#include "Adafruit_Thermal.h"
#include "SoftwareSerial.h"
#include "Wire.h"
#include "time.h"
#include <WiFi.h>
#include "RTClib.h";
#include <NTPClient.h>;
#include <WiFiUdp.h>

#define TX_PIN 1 // DFRobot  GREEN WIRE  labeled RX on printer
#define RX_PIN 2 // DFRobot   YELLOW WIRE   labeled TX on printer
#define btnPin1 4
#define btnPin2 5

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "us.pool.ntp.org", 10*3600);

RTC_DS3231 rtc;


SoftwareSerial mySerial(RX_PIN, TX_PIN); // Declare SoftwareSerial obj first
Adafruit_Thermal printer(&mySerial);     // Pass addr to printer constructor

const char* ssid     = "hotspot";
const char* password = "password";

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 36000;
const int   daylightOffset_sec = 0;

void RTC_Update(){
  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();
  rtc.adjust(epochTime); 
}


void setup() {
  //button setup
pinMode(btnPin1,INPUT_PULLUP);
pinMode(btnPin2,INPUT_PULLUP);
 // Printer data TO 27
  pinMode(27, OUTPUT); digitalWrite(27, LOW);
//printer setup
  mySerial.begin(9600);
  printer.begin(); 
  printer.sleep();      // Tell printer to sleep
  delay(3000L);         // Sleep for 3 seconds
  printer.wake();       // MUST wake() before printing again, even if reset
  printer.setDefault(); // Restore printer to default

}
void loop() {

int Push_button_state = digitalRead(btnPin1);
if ( Push_button_state == LOW ){
  DateTime now = rtc.now();
   printer.println(F("------------------------------"));
 printer.println(F(""));
 printer.setSize('M');
 printer.justify('C');
 printer.println(F("OUT-OF-CLASS-PASS"));
 printer.setSize('L');
 printer.print(F("Date:  "));
 printer.print(now.day(), DEC);
 printer.print("/");
 printer.print(now.month(), DEC);
 printer.println(F(""));
 printer.setSize('M');
 printer.println(F("This student has permission to"));
 printer.println(F("leave the classroom."));
 printer.justify('C');
 char buffer [25];
 uint8_t sec, min, hour;
 sec = now.second();
 min = now.minute();
 hour = now.hour();
 sprintf (buffer, "Left class at: %02u:%02u\n", hour, min);
 printer.print (buffer);
 printer.println(F("Pass valid for 5 minutes."));
 printer.justify('L');
 printer.println(F("Thank you,"));
 printer.justify('C');
 printer.doubleHeightOn();
 printer.println(F("  MR. PATRICK  "));
 printer.println(F(""));
 printer.doubleHeightOff();
 printer.println(F(""));
 printer.println(F("------------------------------"));
}
//button for wifi update of time  
int Push_button_state2 = digitalRead(btnPin2);
if ( Push_button_state2 == LOW ){
  WiFi.begin(ssid, password);
    delay(5000);
  timeClient.begin();
  delay(2000);
  timeClient.update();  rtc.begin();
  RTC_Update();
  delay(2000);
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
}

}
1 Like

If anyone is interested, i have got everything working.
Now on boot up it checks if the year in the RTC is before 2022. If so, then it loads a wifi config ap. once you connect to it and give it some wifi details it will change to a station, connect to the network, download the time from an ntp server, save that time into the rtc and you are good to go.

I need to an an LED to indicate when the date is wrong so the user knows to open their phone and connect to it, and i also want to change the 6xAA batteries to a rechargable one with a port for charging by usb, but i need to do some research to understand how all that works.

So below is the code, i don’t understand all of it, and it may have errors or redundant bits in there, but it works, and i am thrilled.

I have learned a lot through this process!

#include "Adafruit_Thermal.h"
#include "SoftwareSerial.h"
#include "Wire.h"
#include "RTClib.h"
#include <NTPClient.h>
//#include <WiFi.h>
#include <WiFiManager.h>
#define TX_PIN 1 // to printer rx
#define RX_PIN 2 // to printer tx
#define btnPin1 4 //push button to print ticket
#define bcd2dec(bcd_in) (bcd_in >> 4) * 10 + (bcd_in & 0x0f) //no idea what this line does but it works 
#define dec2bcd(dec_in) ((dec_in / 10) << 4) + (dec_in % 10) //no idea what this line does but it works 

int timeout = 120; // for config portal

SoftwareSerial mySerial(RX_PIN, TX_PIN); // Declare SoftwareSerial obj first
Adafruit_Thermal printer(&mySerial);     // Pass addr to printer constructor

RTC_DS3231 rtc;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
byte mByte[0x07]; // holds the array from the DS3231 register
byte tByte[0x07]; // holds the array from the NTP server
const long gmtOffset = 36000; // 10 hours forward
const long summerTimeOffset = 0; 

int timeUpdated = 0;

void setup() {
  const uint32_t MagicDate = 2022;  //year to compare against stored time to check if update needed
  pinMode(btnPin1, INPUT_PULLUP);
  pinMode(27, OUTPUT); digitalWrite(27, LOW);
  WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP  
  
  mySerial.begin(9600);
  Serial.println("Printer starting....");
  printer.begin();        // Init printer (same regardless of serial type)
  printer.println(F("------------------------------")); 
  printer.sleep();      // Tell printer to sleep
  delay(3000L);         // Sleep for 3 seconds
  printer.wake();       // MUST wake() before printing again, even if reset
  printer.setDefault(); // Restore printer to default


   
// SETUP RTC MODULE
if (! rtc.begin()) {
printer.println("Couldn't find RTC");
while (1);
}
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));


 DateTime now = rtc.now();
 if (MagicDate > now.year()){ //if year older than 2022 then reset time
 Serial.println("Time needs resetting...");
 Serial.println("Starting config portal..");
 WiFiManager wm;    
 wm.resetSettings();
 wm.setConfigPortalTimeout(timeout);
 if (!wm.startConfigPortal("OnDemandAP")) {
 Serial.println("failed to connect and hit timeout");
 delay(3000);
 //reset and try again, or maybe put it to deep sleep
 delay(5000);
 }
 //  if you get here you have connected to the WiFi
 Serial.println("connected...");
 delay(4000);
// set RTC via call to ntp server
timeClient.begin();
  timeClient.setTimeOffset(gmtOffset + summerTimeOffset); 
  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();  
  tByte[0] = (int)timeClient.getSeconds();  
  tByte[1] = (int)timeClient.getMinutes();  
  tByte[2] = (int)timeClient.getHours();  
  tByte[3] = (int)timeClient.getDay();
  struct tm *ptm = gmtime ((time_t *)&epochTime);
  tByte[4] = (int)ptm->tm_mday;  
  tByte[5] = (int)ptm->tm_mon+1;  
  tByte[6] = (int)ptm->tm_year-100;
  if(mByte != tByte){
    Wire.beginTransmission (0x68);
    // Set device to start read reg 0
    Wire.write (0x00);
      for (int idx = 0; idx < 7; idx++){
        Wire.write (dec2bcd(tByte[idx]));
      }   
    Wire.endTransmission ();
    }
  
  WiFi.disconnect(true);
  Serial.println("Disconnecting WiFi");
  WiFi.mode(WIFI_OFF);

}
}

void loop() {
DateTime now = rtc.now();
int Push_button_state = digitalRead(btnPin1);
if ( Push_button_state == LOW ){
 printer.println(F("------------------------------"));
 printer.println(F(""));
 printer.setSize('M');
 printer.justify('C');
 printer.println(F("OUT-OF-CLASS-PASS"));
 printer.setSize('L');
 printer.print(F("Date:  "));
 printer.print(now.day(), DEC);
 printer.print("/");
 printer.print(now.month(), DEC);
 printer.println(F(""));
 printer.setSize('M');
 printer.println(F("This student has permission to"));
 printer.println(F("leave the classroom."));
 printer.justify('C');
 char buffer [25];
 uint8_t sec, min, hour;
 sec = now.second();
 min = now.minute();
 hour = now.hour();
 sprintf (buffer, "Left class at: %02u:%02u\n", hour, min);
 printer.print (buffer);
 printer.println(F("Pass valid for 5 minutes."));
 printer.justify('L');
 printer.println(F("Thank you,"));
 printer.justify('C');
 printer.doubleHeightOn();
 printer.println(F("  MR. PATRICK  "));
 printer.println(F(""));
 printer.doubleHeightOff();
 printer.println(F(""));
 printer.println(F("------------------------------"));
}
}
2 Likes