Skip to content

Instantly share code, notes, and snippets.

@g1ra
Created December 7, 2023 10:57
Show Gist options
  • Save g1ra/d8fb2eae4e125098fe222ef9df3f790e to your computer and use it in GitHub Desktop.
Save g1ra/d8fb2eae4e125098fe222ef9df3f790e to your computer and use it in GitHub Desktop.
ESP32_S2_Entrance_GPS_Radar_Light_v2
// a few defines to abstract away the Serial-specific stuff
#define DEBUG 1
#if DEBUG == 1
#define PR(...) SERIAL_DEVICE.print(__VA_ARGS__)
#define PRF(...) SERIAL_DEVICE.printf(__VA_ARGS__)
#define PRLN(...) SERIAL_DEVICE.println(__VA_ARGS__)
#define LN() PRLN(' ')
#define LINES(n) for (uint8_t _bl=0; _bl<n; _bl++) { LN(); }
#else
#define PR(...)
#define PRF(...)
#define PRLN(...)
#define LN()
#define LINES(n)
#endif
#define SERIAL_BAUDRATE 115200
#define SERIAL_DEVICE Serial
#define TESTINGBUTTONPIN 16
#define POWERLED 15
#define MAXLEDTEMP 60
unsigned long detectTimeout = 2500;
unsigned long LCDOffTimeout = 10000; // LCD will turn off after X ms, if not movement detected
unsigned long detectLastSeen = 0;
// https://www.wemos.cc/en/latest/_static/boards/s2_pico_v1.0.0_4_16x9.png
// https://www.wemos.cc/en/latest/_static/files/sch_s2_pico_v1.0.0.pdf
// https://www.weigu.lu/microcontroller/tips_tricks/esp32_tips_tricks/index.html
// https://www.studiopieters.nl/esp32-s2-pinout/
// SDA IO8
// SCL IO9
/////////////////////////////////////////////////////////////////
#include <OneWire.h>
#include <DallasTemperature.h>
#define TEMPERATURE_PRECISION 9
#define ONE_WIRE_BUS_DS18B20 17
OneWire oneWireDS18B20(ONE_WIRE_BUS_DS18B20);
DallasTemperature sensorDS18B20(&oneWireDS18B20);
float tempDS18B20 = 0;
DeviceAddress tempDeviceAddress;
float getDS18B20Temp() {
sensorDS18B20.requestTemperatures(); // Send the command to get temperatures
// We use the function ByIndex, and as an example get the temperature from the first sensor only.
float tempC = sensorDS18B20.getTempCByIndex(0);
// Check if reading was successful
// if(tempC != DEVICE_DISCONNECTED_C) {
// PR("Temperature for the device 1 (index 0) is: ");
// PRLN(tempC);
// } else {
// PRLN("Error: Could not read temperature data");
// }
return tempC;
}
void printDS18B20Address(DeviceAddress deviceAddress)
{
for (uint8_t i = 0; i < 8; i++)
{
if (deviceAddress[i] < 16) PR("0");
PR(deviceAddress[i], HEX);
}
}
/////////////////////////////////////////////////////////////////
// DS3231
// DS3231S RTC chip’s fixed I2C address is 0x68
// EEPROM’s default I2C address is 0x57 (though the address range is 0x50 to 0x57).
#include <Wire.h>
#include <RtcDS3231.h>
TwoWire I2Cone = TwoWire(0); // for LCD and RTC
RtcDS3231<TwoWire> Rtc(I2Cone);
#define countof(a) (sizeof(a) / sizeof(a[0]))
void printDateTime(const RtcDateTime& dt)
{
char datestring[26];
snprintf_P(datestring,
countof(datestring),
PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
dt.Month(),
dt.Day(),
dt.Year(),
dt.Hour(),
dt.Minute(),
dt.Second() );
PR(datestring);
}
/////////////////////////////////////////////////////////////////
#include <Adafruit_ADS1X15.h>
//Adafruit_ADS1115 ads; /* Use this for the 16-bit version */
Adafruit_ADS1015 ads; /* Use this for the 12-bit version */
TwoWire I2Ctwo = TwoWire(1); // address 0x3C
bool initADC = false;
#define TEMPERATURENOMINAL 25
#define DEFAULT_BCOEF 3950
#define DEFAULT_NOMINAL_RES 10000
#define SERIESRESISTOR 10000
// GND -> THERMISTOR one leg
// THERMISTOR other leg <-> A0
// A0 <-> 100K resistor
// 100K resistor to 3.3Volt
float calcNTC() {
int16_t adc;
float volts;
adc = ads.readADC_SingleEnded(0);
volts = ads.computeVolts(adc);
// PRLN("-----------------------------------------------------------");
// PR("AIN0: "); PR(adc); PR(" "); PR(volts); PRLN("V");
float val = 3.3 / volts - 1;
float resistance = SERIESRESISTOR / val;
// PR("Thermistor resistance ");
// PRLN(resistance);
float steinhart;
steinhart = resistance / DEFAULT_NOMINAL_RES;
steinhart = log(steinhart);
steinhart /= DEFAULT_BCOEF;
steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15);
steinhart = 1.0 / steinhart;
steinhart -= 273.15;
return steinhart;
}
/////////////////////////////////////////////////////////////////
//------------------------------------------
#include <Bounce2.h> //https://github.com/thomasfredericks/Bounce2
Bounce2::Button testing_button = Bounce2::Button();
bool testing_state = false;
//------------------------------------------
//------------------------------------------
// https://github.com/contrem/arduino-timer
#include <arduino-timer.h>
auto timer = timer_create_default();
//------------------------------------------
//------------------------------------------
// must fix in /home/bob/Arduino/libraries/Chronos/src/chronosinc/timeExtInc.h line 43 : #include <TimeLib.h>
#include <Chronos.h> // https://inductive-kickback.com/projects/chronos/
Chronos::DateTime sunSetc;
Chronos::DateTime sunRisec;
bool invalidTime = false;
//------------------------------------------
//-----------------------------
#include <SolarCalculator.h>
double latitude = 49.3811;
double longitude = 16.4686;
int utc_offset = 1;
double transit, sunrise_d, sunset_d; // Event times, in hours (UTC)
bool sunset = false;
void calcSunset() {
Chronos::DateTime nowTime(Chronos::DateTime::now());
time_t utc = now();
calcSunriseSunset(utc, latitude, longitude, transit, sunrise_d, sunset_d);
Chronos::DateTime midnight(year(), month(), day(), 0, 0, 0);
sunSetc = midnight + Chronos::Span::Seconds(int(60*60*(sunset_d + utc_offset)));
sunRisec = midnight + Chronos::Span::Seconds(int(60*60*(sunrise_d + utc_offset)));
sunSetc.printTo(Serial); PR(" "); sunRisec.printTo(Serial); PR(" "); nowTime.printTo(Serial); LN();
if ( (sunSetc < nowTime) || (sunRisec > nowTime) ) {
sunset = true;
} else {
sunset = false;
}
}
//-----------------------------
// http://arduiniana.org/libraries/tinygps/comment-page-12/
#include <TinyGPSMinus.h>
#include <TimeLib.h>
// https://www.engineersgarage.com/gps-module-with-arduino/
#define GPSBAUD 9600
TinyGPSMinus gps;
//get datetime : libraries/Time/examples/TimeGPS/TimeGPS.ino
time_t prevDisplay = 0; // when the digital clock was displayed
const int tzoffset = 1; // Central European Time
// ESP32 S2 have only 2 UART . One is Serial , other can be user
#include <HardwareSerial.h> // https://microcontrollerslab.com/esp32-uart-communication-pins-example/
// https://microcontrollerslab.com/esp32-uart-communication-pins-example/
HardwareSerial HSerialGPS(1); // RX: 4, TX: 5
//-----------------------------
// LD1125H https://github.com/UsefulElectronics/esp32s3-gc9a01-lvgl
#define LEDPIN 10
#define RADARPIN 6
bool radarDetect = false;
// AM312
#define PIRPIN 7
bool pirDetect = false;
//-----------------------------
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeMonoBold9pt7b.h> // https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts
// #include <Font5x7FixedMono.h> // https://github.com/robjen/GFX_fonts
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET 18 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &I2Cone, OLED_RESET);
// 'sun', 32x32px
const unsigned char sun_bitmap [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00,
0x00, 0x01, 0x80, 0x00, 0x02, 0x01, 0x80, 0x40, 0x07, 0x00, 0x00, 0xe0, 0x03, 0x80, 0x01, 0xc0,
0x01, 0x87, 0xe3, 0x80, 0x00, 0x9f, 0xf1, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x7f, 0xfc, 0x00,
0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x7c, 0xff, 0xff, 0x3c,
0x7c, 0xff, 0xff, 0x3e, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x01, 0x87, 0xe3, 0x80,
0x03, 0x80, 0x01, 0xc0, 0x07, 0x00, 0x00, 0xe0, 0x06, 0x01, 0x80, 0x40, 0x00, 0x01, 0x80, 0x00,
0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'moon', 32x32px
const unsigned char moon_bitmap [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
0x00, 0xfc, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00,
0x07, 0xf0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00,
0x1f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 0x9c, 0x00, 0x00, 0x1f, 0x8e, 0x00, 0x00,
0x1f, 0xdf, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x1f, 0xff, 0x00, 0x00,
0x0f, 0xf7, 0x80, 0x00, 0x0f, 0xf8, 0x60, 0x00, 0x07, 0xff, 0xfc, 0x30, 0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xfe, 0x00,
0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
bool updateDS18B20Temp(void *) {
tempDS18B20 = getDS18B20Temp();
return true;
}
bool updateSystem(void *) {
// float tempDS18B20 = getDS18B20Temp();
// PR("tempDS18B20 : "); PRLN(tempDS18B20);
if ( !invalidTime ) {
calcSunset();
}
if (digitalRead(RADARPIN) == 1) {
radarDetect = true;
PR("RADAR ");
digitalWrite(LEDPIN, LOW); //turn LED ON
detectLastSeen = millis();
} else {
radarDetect = false;
digitalWrite(LEDPIN, HIGH); //turn LED OFF
}
if (digitalRead(PIRPIN) == 1) {
PR("PIR ");
pirDetect = true;
detectLastSeen = millis();
} else {
pirDetect = false;
}
float NTCtemp = 0;
PR("DS18B20 Temperature :"); PR(tempDS18B20); PR(" *C");
if (initADC) {
NTCtemp = calcNTC();
PR(" | NTC Temperature ");
PR(NTCtemp);
PR(" *C");
} else {
if (!ads.begin(0x48, &I2Ctwo)) {
PRLN("Failed to initialize ADS.");
initADC = false;
} else {
initADC = true;
}
}
PR("detectLastSeen: ");
PRLN( ( millis() - detectLastSeen) / 1000 );
if (testing_state) {
//testing POWERLED
digitalWrite(POWERLED, HIGH);
PRLN("testing_state HIGH");
} else {
PRLN("testing_state LOW");
if (millis() > detectLastSeen + detectTimeout) {
digitalWrite(POWERLED, LOW);
PRLN("NO MOVEMENT within detectTimeout. turn off POWERLED ");
} else {
digitalWrite(POWERLED, HIGH);
}
if ( invalidTime ) {
digitalWrite(POWERLED, LOW);
PR("invalid date/time. BAD Year . "); PRLN(year());
}
}
// if POWERLED temperature is greater than max allowed then turn POWERLED OFF
if (NTCtemp > MAXLEDTEMP) {
digitalWrite(POWERLED, LOW);
PRLN("MAX POWERLED temperature reached !");
}
LCDupdate();
return true;
}
void setup() {
SERIAL_DEVICE.begin(SERIAL_BAUDRATE);
PR("compiled: ");
PR(__DATE__);
PRLN(__TIME__);
pinMode(POWERLED, OUTPUT);
HSerialGPS.begin(GPSBAUD, SERIAL_8N1, 4, 5);
I2Ctwo.begin(33, 35);
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 2mV 0.125mV
if (!ads.begin(0x48, &I2Ctwo)) {
delay(2000);
PRLN("Failed to initialize ADS.");
initADC = false;
} else {
initADC = true;
}
pinMode(RADARPIN, INPUT_PULLUP);
pinMode(PIRPIN, INPUT_PULLUP);
pinMode(LEDPIN, OUTPUT);
I2Cone.begin(8, 9); // SCREEN_ADDRESS 0x3C LCD
/////////////////
Rtc.Begin();
RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
printDateTime(compiled);
// never assume the Rtc was last configured by you, so
// just clear them to your needed state
Rtc.Enable32kHzPin(false);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
/////////////////
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
PRLN(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
sensorDS18B20.begin();
delay(2000); // Pause for 1 seconds
int numberOfDevices = sensorDS18B20.getDeviceCount();
PR("Found ");
PR(numberOfDevices, DEC);
PRLN(" devices.");
for(int i=0;i<numberOfDevices; i++) {
if(sensorDS18B20.getAddress(tempDeviceAddress, i)) {
PR("Found device ");
PR(i, DEC);
PR(" with address: ");
printDS18B20Address(tempDeviceAddress);
LN();
PR("Setting resolution to ");
PRLN(TEMPERATURE_PRECISION, DEC);
// set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions)
sensorDS18B20.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION);
PR("Resolution actually set to: ");
PR(sensorDS18B20.getResolution(tempDeviceAddress), DEC);
PRLN();
} else {
PR("Found ghost device at ");
PR(i, DEC);
PR(" but could not detect address. Check power and cabling");
}
}
// BUTTON SETUP
testing_button.attach( TESTINGBUTTONPIN, INPUT_PULLUP ); // USE INTERNAL PULL-UP
testing_button.interval(5); // DEBOUNCE INTERVAL IN MILLISECONDS
// INDICATE THAT THE LOW STATE CORRESPONDS TO PHYSICALLY PRESSING THE BUTTON
testing_button.setPressedState(LOW);
calcSunset();
display.display();
delay(1000); // Pause for 1 seconds
// Clear the buffer
display.clearDisplay();
timer.every(500, updateSystem);
timer.every(2000, updateDS18B20Temp);
}
void loop() {
timer.tick(); // need for arduino-timer , must Call timer.tick() in the loop function
testing_button.update();
if (testing_button.pressed()) {
testing_state = !testing_state; // flip
}
if ( (timeStatus() != timeNotSet) && (year() != 2080) && (year() != 1970) ) {
invalidTime = false;
} else {
invalidTime = true;
}
// For one second we parse GPS data and report some key values
char lat[9], lon[10];
unsigned long age, date, time, chars = 0;
unsigned short sentences = 0, failed = 0;
while (HSerialGPS.available()) {
unsigned short sentences, failed_checksum;
unsigned long chars;
if (gps.encode(HSerialGPS.read())) { // process gps messages
// when TinyGPS reports new data...
// The stats method provides a clue whether you are getting good data or not.
// It provides statistics that help with troubleshooting.
gps.stats(&chars, &sentences, &failed_checksum);
// chars – the number of characters fed to the object
// sentences – the number of valid $GPGGA and $GPRMC sentences processed
// failed_checksum – the number of sentences that failed the checksum test
//PR("chars :"); PR(chars); PR(" sentences:"); PR(sentences); PR(" failed_checksum:"); PRLN(failed_checksum);
unsigned long age;
int Year;
byte Month, Day, Hour, Minute, Second;
gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age);
if (age < 500) {
// set the Time to the latest GPS reading
setTime(Hour, Minute, Second, Day, Month, Year);
adjustTime(tzoffset * SECS_PER_HOUR);
}
}
}
if (timeStatus()!= timeNotSet) {
if (now() != prevDisplay) { //update the display only if the time has changed
prevDisplay = now();
}
}
}
void LCDupdate(){
display.clearDisplay();
if (millis() > detectLastSeen + LCDOffTimeout) {
display.display();
return;
}
display.setTextSize(1); // Normal 1:1 pixel scale
display.setFont(NULL); //&FreeMonoBold9pt7b);
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 5); // Start at top-left corner
if (testing_state) {
display.print("T");
}
if ( !invalidTime ) {
display.print(year());
display.print("/");
display.print(month());
display.print("/");
display.print(day());
display.print(" ");
} else {
display.print("invalid ");
}
float NTCtemp;
NTCtemp = calcNTC();
display.println(NTCtemp);
if ( !invalidTime ) {
char uhours[8]; char uminutes[8]; char useconds[8];
sprintf(uhours, "%02u", hour());
sprintf(uminutes, "%02u", minute());
sprintf(useconds, "%02u", second());
display.print(uhours); display.print(":");
display.print(uminutes); display.print(":");
display.print(useconds);
} else {
display.print("--:--:--");
}
display.print(" ");
display.println(tempDS18B20);
if(pirDetect) {
display.print("PIR ");
}
if(radarDetect) {
display.print("RADAR ");
}
if (!invalidTime) {
if (sunset) {
display.drawBitmap(94, 0, moon_bitmap, 32, 32, WHITE);
} else {
display.drawBitmap(94, 0, sun_bitmap, 32, 32, WHITE);
}
}
if (millis() < detectLastSeen + detectTimeout) {
display.invertDisplay(true);
} else {
display.invertDisplay(false);
}
display.display();
}
void printDigits(int digits) {
// utility function for digital clock display: prints preceding colon and leading 0
display.print(":");
if(digits < 10)
display.print('0');
display.print(digits);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment