Thursday, July 13, 2023

0000 0000 1100 1100

XIAO ESP32S3 Problem Solver

I have two problems in my life at the moment. OK that is a gross simplification - I have hundreds of problems in my life at the moment, but two that might be able to be solved pretty easily with some hardware I have laying about the place and some small snippets of code. Plus - a tiny toy rubbish bin!

Firstly, our WiFi out here in the middle of nowhere is pretty patchy - also we rely on the internet for our "landline" phone as well. It has often been the case that we have been unaware that we are out of communication as the broadband has crashed.

I'd like to know if the WiFi strength is good, and ultimately if we are actually connected to the internet. An ESP32 with the right code should be able to:

1. test WiFi strength (strength = WiFi.RSSI())

2. test internet connection (internet = Ping.ping("www.google.com"))

Secondly - and this might be a uniquely Australian problem - I can never remember when the recycling bin is to be picked up! The normal household rubbish bin is collected each week, but the recycling only on every second week. Of course our local council website has been coded by the work experience kid.

Without checking our neighbour's choice or phoning the dysfunctional council in question (and there are so many questions) there is no way of knowing when the pickup is happening.

I decided that maybe if I could code an ESP32 to find out the date (via an ntp server) then I could maybe have an LED or two indicating which bin to put out for collection. This produced a bit of a side journey into ntp protocols etc - and also a little bit of trickery with the change over (check the time every four hours, but only change on the day after the collection, but only once that day - ouch my head).

There was some other code trickery I enjoyed solving including:

1. Save the bin choice to EEProm in case of power failure. I could have used preferences.h here, but I went with EEProm due to familiarity.

2. Configure the ESP32 as an AP as well as connected to the WiFi so that I can log into it to change the bin choice as well as monitor the WiFi availability and strength.

3. React to the loss of the internet as well as loss of WiFi signal. 

4. Serve up an HTML page on the AP so that I can change the bin choice.

Finally I decided to use a XIAO ESP32S3 that Seeed Studios sent me recently, and therefore due to lack of available GPIOs (it's tiny!) I whipped up a 74HC595/LED Bar Graph combo to output the signal strength.

There were some other little issues along the journey, but the full code is below for your dining pleasure.

/*
   The bin project aims to solve two problems:
   a) is the WiFi working?
   b) what bins do I put out this week

   The code is therefore doing this:
   1. Every "pollingtime" (set below) the ESP32 checks the strength of the WiFi signal
   2. If the signal is out, the ESP32 attempts reconnection and flashes red
   3. The ESP32 can be told which bins are out this week
   4. It saves the data then changes each "changeday" at 1am
   5. The ESP32 connects with an ntp server to keep track of time
   6. Data is saved to EEPROM in case of reset

   OneCircuit Sun 09 Jul 2023 13:20:25 AEST
   https://www.youtube.com/@onecircuit-as
   https://onecircuit.blogspot.com/

*/

// libraries used
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include "time.h"
#include <Arduino.h>
#include <AsyncTCP.h>
#include <EEPROM.h>
#include <ESP32Ping.h>

#define EEPROM_SIZE 1

// change for your AP as required
const char* binssid = "BinXMonitor";
const char* binpassword = NULL;

// server for time, and the offsets for location
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 32400;
const int daylightOffset_sec = 3600;

// Pin allocations for XIAO ESP32S3

// Pins for the 74HC595
const uint8_t latchPin = 1;
const uint8_t clockPin = 2;
const uint8_t dataPin = 3;

// Pins for the Green and Yellow LEDs
const uint8_t GreenLed = 4;
const uint8_t YellLed = 5;

bool YellLedState = LOW;

// your WiFi credentials - change as required
const char* ssid = "YourWifiSSID";
const char* password = "YourWiFiPass";

// the html page served up from the ESP32 to change bin status
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>Bin Selection</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<center>
  <h1>Select Bins for next week</h1>
    <form action="/" method="POST">
      <font size="+2">
      <input type="radio" name="bintype" value="Green Bin">
      <label for="GB">Green Bin only</label>
      <br>
      <input type="radio" name="bintype" value="Recycle Bin">
      <label for="RB">Recycle Bin too</label><br><br>
      <input type="submit" value="Enter Choice">
      </font>
    </form>
</center>
</body>
</html>
)rawliteral";

// params and variables for html polling
const char* PARAM_INPUT_1 = "bintype";
String bintype;
bool newRequest = false;

// variables for day and hour from ntp server
char Day[10];
int Hour;

// I'm going to check time every...
const unsigned long timelapse = 14400000;  // 4 hours in milliseconds
unsigned long currentMillis;
unsigned long previousMillis;

// which day and hour to change LED indicators
const String changeday = "Wednesday";
const int changehour = 1;

// after changing, don't change again until next week
// e.g. numchanges (7) x timelapse (4 hours) = 28 hours
uint8_t changecount = 0; // how many times checked timelapse?
const uint8_t numchanges = 7; // reset changecount after this

// how often to check WiFi status?
const int pollingtime = 2000;

// LED lights for bar graph
// see https://deepbluembedded.com/esp32-wifi-signal-strength-arduino-rssi/
uint8_t strength = 0;
const uint8_t lowstrength = 90; // change as required
const uint8_t maxstrength = 30; // change as required
uint8_t lights = 0;

// how many times for attempting WiFi and time check before restart?
const uint8_t wifitries = 50;

// start the webserver on the ESP32
AsyncWebServer server(80);

// array for bargraph - out to 74HC595 shift register
const uint8_t ledpattern[9] = {
  0b00000000,  // 0
  0b00000001,  // 1
  0b00000011,  // 2
  0b00000111,  // 3
  0b00001111,  // 4
  0b00011111,  // 5
  0b00111111,  // 6
  0b01111111,  // 7
  0b11111111   // 8
};

// get time from the ntp server
void GetLocalTime() {
  struct tm timeinfo;
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  int numtries = 0;
  while (!getLocalTime(&timeinfo)) {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[0]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[8]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    numtries = numtries + 1;
    if (numtries > wifitries) {
      numtries = 0;
      ESP.restart();
    }
  }
  strftime(Day, 10, "%A", &timeinfo);
  Hour = timeinfo.tm_hour;
}

// initiate the WiFi connection
void initWiFi() {
  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);
  int numtries = 0;
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[0]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[8]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    numtries = numtries + 1;
    if (numtries > wifitries) {
      numtries = 0;
      ESP.restart();
    }
  }
}

void setup() {
  // breathe
  delay(200);

  // 74HC595 pins to output
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // LEDs to output and start green (always on)
  pinMode(GreenLed, OUTPUT);
  digitalWrite(GreenLed, HIGH);
  pinMode(YellLed, OUTPUT);
  digitalWrite(YellLed, YellLedState);

  // 74HC595 "remembers" last state, so clear
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[0]);
  digitalWrite(latchPin, HIGH);

  // start WiFi
  initWiFi();

  // get time
  GetLocalTime();

  // set up AP for ESP32
  WiFi.softAP(binssid, binpassword);

  // what is in EEPROM? Set accordingly
  EEPROM.begin(EEPROM_SIZE);
  YellLedState = EEPROM.read(0);
  digitalWrite(YellLed, YellLedState);

  // Web Server Root URL
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    request->send(200, "text/html", index_html);
  });

  // Handle request (form)
  server.on("/", HTTP_POST, [](AsyncWebServerRequest* request) {
    int params = request->params();
    for (int i = 0; i < params; i++) {
      AsyncWebParameter* p = request->getParam(i);
      if (p->isPost()) {
        // HTTP POST input1 value (direction)
        if (p->name() == PARAM_INPUT_1) {
          bintype = p->value().c_str();
        }
      }
    }
    request->send(200, "text/html", index_html);
    newRequest = true;
  });

  server.begin();
}

void loop() {
  // keep track of time for checking ntp server
  currentMillis = millis();
  if ((currentMillis - previousMillis) >= timelapse) {
    previousMillis = currentMillis;
    GetLocalTime();
    String today = String(Day);
    // it's time to change LEDs and save to EEPROM
    if ((today == changeday) && (Hour >= changehour)) {
      if (changecount == 0) {
        YellLedState = !YellLedState;
        digitalWrite(YellLed, YellLedState);
        EEPROM.write(0, YellLedState);
        EEPROM.commit();
      }
      // after change LED, forward to next day to
      // prevent further changes on changeday
      changecount = changecount + 1;
      if (changecount >= numchanges) {
        changecount = 0;
      }
    }
  }

  // output loss of WiFi
  int numtries = 0;


  while (!(Ping.ping("www.google.com", 1))) {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[8]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[0]);
    digitalWrite(latchPin, HIGH);
    delay(100);
    numtries = numtries + 1;
    if (numtries > wifitries) {
      numtries = 0;
      ESP.restart();
    }
  }

  // adjust according to your WiFi strength
  // output strength to 74HC595
  strength = abs(WiFi.RSSI());
  lights = map(strength, lowstrength, maxstrength, 0, 8);
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, ledpattern[lights]);
  digitalWrite(latchPin, HIGH);

  // process any polling requests to change bins (and save)
  if (newRequest) {
    if (bintype == "Recycle Bin") {
      YellLedState = HIGH;
      digitalWrite(YellLed, YellLedState);
      EEPROM.write(0, YellLedState);
      EEPROM.commit();
    } else {
      YellLedState = LOW;
      digitalWrite(YellLed, YellLedState);
      EEPROM.write(0, YellLedState);
      EEPROM.commit();
    }
    newRequest = false;
  }

  delay(pollingtime);
}

The whole thing fits nicely and appropriately into a bin!


The video is below for your enjoyment - go put a like and comment on YouTube - it'd be great to see you there.




No comments:

Post a Comment