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.




Wednesday, June 28, 2023

0000 0000 1100 1011

Art for Art's Sake

Coaxed out of a hiatus bought about by an impending house move by Seeed Studio I embarked upon a project which both them and I will probably regret!

Firstly I'd been consuming too much Ben Eater goodness around his 6502 based breadboard computer and the idea of emulating an early Apple - coincidentally the first computer I used to learn programming was an AppleII, way back in 1978!

Secondly I'd recently found two amazing 6502 resources:

1. The online assembler, emulator and instructions set from Mass:Werk.

2. The amazing KimUno project - how to run a 6502 on a custom PCB with an Arduino Pro Mini, or a STM32 or even an ESP32 (introductory manual)

With my head full of this nostalgia and the tools to match, in comes Seeed Studio with their XIAO range of minuscule but powerful modules. "Would you like to try some XIAO modules?" they said. "Yes!"

I thought it would be aesthetically pleasing to shoehorn the KimUno project into the XIAO.

Of course there were some issues! Chiefly I wanted to run the XIAO ESP32C3 over serial, but the module only has ACM serial as standard - and it was being severely interfered with by Pin Assignments. I was able to get around this initially by using a Frankenstein combination of a CP2102 UART to USB module and some resistors on the strapping pins, but...

Considering there was no hardware KimUno - I simply wrote the pins out of the code as per the green area below! The red represents the original ESP32 pin assignments.


One weird artefact of using the /dev/ttyACM0 interface was fixed serial speed of 115200 baud. It just ignores the code and you get what you get!

After way too many false starts, head scratching and dead ends, finally success! The joy of running MicroChess on this thumb size device is difficult to describe.

See for yourself - enjoy the video.



Thursday, May 18, 2023

0000 0000 1100 1010

The Dirty Dozen - Part Six: USB-C Nano, LIS3DH & Channel Update

The packet reveals an Arduino Nano? Hardly exciting, although I'd never had a Nano with a USBC interface before, but still it wasn't exactly a challenging unit!

So I dived backward to Dirty Dozen #5, dragged out the LIS3DH and interrogated it's temperature abilities. It's a little unusual in that the unit only reports the relative temperature based on when it was booted - strange!

I decided to use that with three LEDs to indicate relative temperature - here is the code:

/*
  Based on ADCUsage.ino

  Marshall Taylor @ SparkFun Electronics
  Nov 16, 2016
  https://github.com/sparkfun/LIS3DH_Breakout
  https://github.com/sparkfun/SparkFun_LIS3DH_Arduino_Library

  OneCircuit https://www.youtube.com/@onecircuit-as
  Mon 15 May 2023 11:02:06 AEST

*/

#include "SparkFunLIS3DH.h"
#include "Wire.h"

LIS3DH myIMU;
int myroomtemp = 0;
int currenttemp = 0;
int difftemp = 0;
#define blueled 4
#define greenled 3
#define yellowled 2

void setup() {

  Serial.begin(57600);
  delay(1000);
  pinMode(blueled, OUTPUT);
  pinMode(greenled, OUTPUT);
  pinMode(yellowled, OUTPUT);
  myIMU.settings.adcEnabled = 1;
  myIMU.settings.tempEnabled = 1; // from ADC3
  myIMU.begin();

  // take the temp
  for (byte stable = 0; stable < 5; stable++) {
  myroomtemp = myIMU.read10bitADC3(); // temp
  delay(100);    
  }
}

void loop()
{
  currenttemp = myIMU.read10bitADC3();
  difftemp = myroomtemp - currenttemp;

  Serial.println(difftemp);

  if (difftemp < 0) {
    digitalWrite(blueled, HIGH);
    digitalWrite(greenled, LOW);
    digitalWrite(yellowled, LOW);
  }
  if ((difftemp > 2) && (difftemp < 20)) {
    digitalWrite(blueled, LOW);
    digitalWrite(greenled, HIGH);
    digitalWrite(yellowled, LOW);
  }
  if (difftemp > 22) {
    digitalWrite(blueled, LOW);
    digitalWrite(greenled, LOW);
    digitalWrite(yellowled, HIGH);
  }
  delay(100);
}

It worked well and I can see some definite applications (e.g. is my module overheating!)

Some news as well in this video - we are moving house after 7 years. It is just down a road a bit, but the packing up and unpacking of the lab will take a few weeks.

If I get a moment I will try to post a video or two - but I might also go a bit "dark" over the Tasmanian winter.

Apologies if you have just joined the channel and blog - I hope the back catalogue of 200 videos can keep you busy while I rearrange my life!

Take care and see you on the other side.



Friday, May 12, 2023

0000 0000 1100 1001

200 videos - no way!

Nearly four years ago I started (belatedly) a blog and YT channel in order to bring some semblance of order to the chaos of my online electronics shopping addiction!

Some of the projects have been educative (for me!), some dismal failures (the great ferrules disaster of 2022), some excellent solutions for issues around the house (lighting, timing and temperature) and all of them a privilege and a joy.

They are all my children and of course I'm not supposed to have favourites, but I have cobbled together a 5+2 "best of" for the video below.

Thank you for your company on this journey - and I hope you'll stick around for the next 200! <gulp>



Friday, May 5, 2023

0000 0000 1100 1000

The Dirty Dozen - Part Five - 3 axes accelerometer as Fall Detector

Those of us who fall off things may want immediate help or maybe even notifications sent to relevant authorities. Perhaps you have a relative or friend, prone to falling, who needs some gentle silicon monitoring?

The LIS3DH Triaxial Acceleration Temperature Sensor might be just the ticket. When I pulled it out of the Dirty Dozen box I thought immediately that if I could talk to the thing and get some sense out of it then I'd take a crack at a fall detector.

The theory is that if there is sudden acceleration of the module (or the object in which it is contained) then the device would output, in three dimensions, the magnitude of that "overall" event.

To characterise it as a single event I thought I might use a virtual 3D box and plot values from the ideal position (no acceleration for zero) to a calculated threshold value (found in my case by experimentation).


That High School math sure does come in handy some days!

I went looking for all sorts of information on this module only to discover that I think it's a cheap knockoff of an existing product (surprise!).

Using a fair amount of optomistic pixie dust I sprinkled in some code from the legitimate product found here at this link.

After successfully "talking" to the module with the legit code and library, I then embarked upon coding the fake version. It took a bit longer than expected as my perfection gene kicked in halfway through the coding process.

/*
   based on code by Marshall Taylor @ SparkFun Electronics
   https://github.com/sparkfun/LIS3DH_Breakout
   https://github.com/sparkfun/SparkFun_LIS3DH_Arduino_Library

   OneCircuit https://www.youtube.com/@onecircuit-as
   Tue 18 Apr 2023 14:03:27 AEST
*/

#include "SparkFunLIS3DH.h"
#include "Wire.h"
#include "SPI.h"

#define alarmLED 4

float xmove = 0;
float ymove = 0;
float zmove = 0;
float falldetect = 0;   // how hard is fall?
boolean fell = false;
float biggestfall = 0;
int falltrigger = 300;  // how hard to fall?
long timer = 0;
long currenttime = 0;
long bigfalltime = 5000;
LIS3DH myIMU(I2C_MODE, 0x19);

void setup() {
  Serial.begin(57600);
  pinMode(alarmLED, OUTPUT);

  myIMU.settings.adcEnabled = 1;
  myIMU.settings.tempEnabled = 0;
  // in Hz: 0,1,10,25,50,100,200,400,1600,5000
  myIMU.settings.accelSampleRate = 200;
  // Max G force: 2, 4, 8, 16
  myIMU.settings.accelRange = 16;
  myIMU.settings.xAccelEnabled = 1;
  myIMU.settings.yAccelEnabled = 1;
  myIMU.settings.zAccelEnabled = 1;

  myIMU.begin();
  delay(1000); // settle petal

}

// here is where you would txt, email or call for help
void raisealarm() {
  digitalWrite(alarmLED, HIGH);
  fell = true;
  timer = millis();
}

void loop()
{
  // time at start of loop
  currenttime = millis();

  // measure and convert each axis
  xmove = abs(myIMU.readFloatAccelX() * 100);
  ymove = abs(myIMU.readFloatAccelY() * 100) - 5;
  zmove = abs(myIMU.readFloatAccelZ() * 100) - 93;

  // has the device fallen?
  falldetect = sqrt(xmove * xmove + ymove * ymove + zmove * zmove);
  if (falldetect > falltrigger) {
    raisealarm();
  }

  Serial.print(xmove);
  Serial.print(",");
  Serial.print(ymove);
  Serial.print(",");
  Serial.print(zmove);
  Serial.print(",");
  Serial.print(falldetect);
  Serial.print(",");
  Serial.println(biggestfall);

  if (falldetect > biggestfall) {
    biggestfall = falldetect;
  }

  if (((currenttime - timer) > bigfalltime) && fell) {
    digitalWrite(alarmLED, LOW);
    fell = false;
    biggestfall = 0;
    timer = 0;
  }

  delay(20);
}

The results were, I don't mind saying, pretty impressive! I'd like to maybe further develop this project to include an "IoT" twist - such that maybe fall detection is accompanied by a text or email message.

Your thoughts, as always, appreciated!



Thursday, April 27, 2023

0000 0000 1100 0111

Two out of three ain't bad

Recently an LED in the house went dark, and usually I'd just throw it out and replace it. This time I thought it was a bit rude as the bulb is nominally rated 30000 hours and it had only been in for around 100 hours.


Two possibilities:

1. The bulb was not up to spec

2. The variations in AC voltage at our place was too much for it

Either way I could see some clear marks from a possible short, and so I wanted to open it up and take a look.

The result is a little bit of a boring video, but nonetheless "illuminating". Also, breaking into the device was a bit of a laugh.


Friday, April 21, 2023

0000 0000 1100 0110

The Dirty Dozen - Part Four - unreachable exotica

I saw a beautiful shiny bauble in the online shop that turned out to be unobtainium

Recently Great Scott did an AliExpress based video where he was trawling through Chinese datasheets trying to gain access to a particular piece of hardware.

Oh, Great Scott - how much time I have wasted in this exact same situation!

Case in point, a lovely looking Air105 based module. Would I have bought it in hindsight? Yes! Who doesn't like unobtainium and unicorns??

For around $5 I received a very impressive module AND a camera to suit. Yummy!

And look at those specs!

Of course, unicorns may fly very fast, but first you have to catch one and communicate with it - not so easy!

The main issues for me revolved around having to:

1. learn a new programming language to talk to this thing (lua) as well as,

2. installing Windows (when I use Linux exclusively) and finally,

3. the majority of the support/software is Chinese based as well!

After much faffing about and grumbling I had to eventually concede defeat. I am going to check in from time to time to see if some geniuses have made this lovely piece of hardware accessible for dumbos like myself (e.g. via an Arduino IDE based library)

Until then - I'll enjoy looking at it.