Mailbag #8
Mail is arriving! Happy days and here is the ritualised opening of the parcels ceremony writ large on YouTube:
I haven't put any links for the products because the prices/availability is quite "fluid" at the moment...
Each week I will feature a circuit based on one or more of the electronic parts I have ordered then forgotten about over the years.
Mail is arriving! Happy days and here is the ritualised opening of the parcels ceremony writ large on YouTube:
Part of the program to monitor and automate some things around the property naturally enough includes extending the WiFi network to cover a wider area. There are a number of ways of doing this, including:
1. Buying a decent router and channelling heaps of power into it for coverage2. Buying WiFi extenders/repeaters and wiring them in all over the place3. Coming up with some half-baked plan to use $1 ESP-01 devices running on solar power (in Tasmania!)
Of these options, which do you think this post is essentially about?
There are a number of options for setting up such a (cheap) extended network, and it may take a few blogs/videos to explore and evaluate some options. On my list is the following:
1. Can I program the ESP-01 to accept home WiFi?
2. Which extender/repeater software for the ESP-01 suits my needs?
3. Test the network under various conditions to help me make the decision
4. Work on Solar/Battery combinations that work for spreading the network around a rural property
5. Design and 3D print a weather-resistant project box
Then it's a matter of choosing your programming method - you can use Esptool, Espressif's tools or (as in my case) the Arduino IDE.
Once the connections and programming was sorted (i.e. can I blink a LED!) I then used the following OTA code modified from the ESP8266 WiFi extender code:
// NAPT example released to public domain #if LWIP_FEATURES && !LWIP_IPV6 #define HAVE_NETDUMP 0 #ifndef STASSID #define STASSID "*****************" #define STAPSK "*****************" #endif #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> #include <lwip/napt.h> #include <lwip/dns.h> #include <LwipDhcpServer.h> const char* ssid = STASSID; const char* password = STAPSK; const char* host = "HostName"; #define NAPT 1000 #define NAPT_PORT 10 void setup() { Serial.begin(115200); Serial.printf("\n\nNAPT Range extender\n"); Serial.printf("Heap on start: %d\n", ESP.getFreeHeap()); #if HAVE_NETDUMP #include <NetDump.h> void dump(int netif_idx, const char* data, size_t len, int out, int success) { (void)success; Serial.print(out ? F("out ") : F(" in ")); Serial.printf("%d ", netif_idx); // optional filter example: if (netDump_is_ARP(data)) { netDump(Serial, data, len); //netDumpHex(Serial, data, len); } } #endif #if HAVE_NETDUMP phy_capture = dump; #endif // first, connect to STA so we can get a proper local DNS server WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); } Serial.printf("\nSTA: %s (dns: %s / %s)\n", WiFi.localIP().toString().c_str(), WiFi.dnsIP(0).toString().c_str(), WiFi.dnsIP(1).toString().c_str()); // give DNS servers to AP side dhcpSoftAP.dhcps_set_dns(0, WiFi.dnsIP(0)); dhcpSoftAP.dhcps_set_dns(1, WiFi.dnsIP(1)); WiFi.softAPConfig( // enable AP, with android-compatible google domain IPAddress(172, 217, 28, 254), IPAddress(172, 217, 28, 254), IPAddress(255, 255, 255, 0)); WiFi.softAP("WiFi-Mesh", STAPSK); Serial.printf("AP: %s\n", WiFi.softAPIP().toString().c_str()); Serial.printf("Heap before: %d\n", ESP.getFreeHeap()); err_t ret = ip_napt_init(NAPT, NAPT_PORT); Serial.printf("ip_napt_init(%d,%d): ret=%d (OK=%d)\n", NAPT, NAPT_PORT, (int)ret, (int)ERR_OK); if (ret == ERR_OK) { ret = ip_napt_enable_no(SOFTAP_IF, 1); Serial.printf("ip_napt_enable_no(SOFTAP_IF): ret=%d (OK=%d)\n", (int)ret, (int)ERR_OK); if (ret == ERR_OK) { Serial.printf("WiFi Network '%s' with same password is now NATed behind '%s'\n", "Charis-Mesh", STASSID); } } Serial.printf("Heap after napt init: %d\n", ESP.getFreeHeap()); if (ret != ERR_OK) { Serial.printf("NAPT initialization failed\n"); } ArduinoOTA.setHostname(host); ArduinoOTA.setPassword("MyPass"); ArduinoOTA.onError([](ota_error_t error) { (void)error; ESP.restart(); }); /* setup the OTA server */ ArduinoOTA.begin(); Serial.println("Ready"); } #else void setup() { Serial.begin(115200); Serial.printf("\n\nNAPT not supported in this configuration\n"); } #endif void loop() { ArduinoOTA.handle(); }
The video below shows this system in a link of WiFi SSIDs stretching about 60m down the driveway as per this diagram:
I'm currently evaluating a slightly different way of putting the network "Mesh" together, but I'll save that for another video/blog.
The mail is dribbling in at the moment - here is a collection of items from the last week or so.
A little while ago I wrote about how to generate LFSR "random" 8-bit numbers using AVR assembly (specifically the ATTiny13).
The Padauk PFS154 has 3 11-bit PWM channels and so I've been doing a few videos on 16-bit arithmetic on an 8-bit micro.
The next part of this crazy project is to scale up the 8-bit random numbers to 16-bit. An LFSR basically works by a LSL/EOR combo, and the 16-bit version is no different.
; AVR ASM program to make "random" 16-bit numbers ; using LFSR with a seed, and two spanners! ; A N Peck Sat 21 Aug 2021 11:02:58 AEST .nolist .include "tn13Adef.inc" ; Define device ATtiny13A .list .equ span1 = 23 ; count before spanner1 thrown .equ spanum1 = 7 ; size of spanner1 .def spanner1 = r18 ; spanner1 counting register .equ span2 = 37 ; count before spanner2 thrown .equ spanum2 = 3 ; size of spanner2 .def spanner2 = r19 ; spanner2 counting register .equ seed = 2901 ; starting seed - happy birthday .equ poly1 = 13 ; change these .equ poly2 = 24 ; polynomials if need .dseg .org SRAM_START .cseg .org 000000 rjmp Main ; Reset vector Main: ; initialise stack ldi r16, Low(RAMEND) out SPL, r16 ; seed for LFSR loaded ldi r16, low(seed) mov r7, r16 ldi r16, high(seed) mov r6, r16 ; polynomial loaded ldi r24, poly1 ldi r25, poly2 ; spanners loaded ldi r18, span1 ldi r19, span2 Loop: ; generate random number rcall rand_16 checkspanner1: subi spanner1, 1 ; decrement spanner1 count brne nospanner ; branch if not at zero mov r16, r7 ; throw spanner1 (subtraction) subi r16, spanum1 breq nozero1 ; result must be nonzero mov r7, r16 nozero1: ldi spanner1, span1 ; reset spanner1 count checkspanner2: subi spanner2, 1 ; decrement spanner2 count brne nospanner ; branch if not at zero mov r16, r6 ; throw spanner2 (addition) subi r16, -spanum2 breq nozero2 ; result must be nonzero mov r6, r16 nozero2: ldi spanner2, span2 ; reset spanner2 count nospanner: ; save registers to file with modified nop nop rjmp loop rand_16: lsl r6 ; shift first rol r7 ; roll brcc noxor ; check flag eor r6, r24 ; XOR if needed eor r7, r25 noxor: ret
To test the randomness of the generated numbers I used a special version of Gerd's AVR Simulator (thanks Gerd!) which allowed me to capture the register values in a csv file. I then used CONCATENATE and HEX2DEC in a spreadsheet to make a long list of decimal values in the range 0 to 65535 (around 6000 values).
Finally I imported this data as raw input into Audacity and then listened to the noise resulting.
This allowed me to tinker with the code until a "white noise" signal resulted - I especially worked two "spanners" into the code whereby after a certain pre-determined number of numbers the random number was "jumped" up or down by a specific amount. Crude, but effective.
Another option would be to swap nibbles at "random" points (I may still code this).
The next part of this project is to see how easy it is to incorporate assembly code such as this (based on the ATTiny13 AVR instruction set) to the PFS154. Watch this space.
For such a long time I resisted the lure of the logic chip - why should I bother when a microcontroller can do all that and more for little more than spare change?
Well, now that microcontrollers (if you can buy them) cost millions of dollars each and logic chips are still relatively cheap - that side of the equation has changed!
But also I have to admit I've fallen a little in love with these "wire critters". One element of the change of heart has been reading Forrest Mims' excellent notebook series, many of which contain fascinating circuits based on single or combinations of different logic chips.
There are many useful circuits (non-microcontroller based) that can be constructed, such as the following button debouncing circuit made with the CD4001 (see last blog and video):
The problem has been that there is only one big red panic button in the average school laboratory used in an emergency to shutdown gas, electricity and/or water. I'd like to have one panic button at each "station" which can shut down the entire grid.
To accomplish this I built a simulated version and it seemed to work both for a single station, but also for multiple stations as you would find in an actual laboratory.
The simulation worked fine, so then I constructed the circuit on a breadboard and ran it through its paces.
The lab tech at work reckons that the idea is a winner, but is worried about students pushing the big red button just to annoy everyone. That is an entirely possible (probable) scenario, but can easily be addressed with the usual carrot/stick training that we employ for other "dangerous" or tricky laboratory items.
I've always been more of a coder learning electronics than an electronics peep learning coding, but I'm coming around to the notion of CMOS logic chips doing the work for simple circuits - particularly with the price of micro-controllers at the moment.
For around AU$0.06 each the CD4001 has a lot of handy uses. Using 0033mer as inspiration, I decided to make a classic relaxation oscillator feeding into a flasher suitable for car indicators.
I spent quite a few happy hours fiddling around with traffic light changers after my last blog and video which used the PFS154 and a button to make the changes. The crazy part is that there is not one single traffic light on my 35 minute commute to work - so...huh?
Anyway, first I looked at an interrupt driven change on the ATTiny13 version, then a simple loop driven change for an ESP32.
BTW, there are so many incarnations of the ESP32 that the list covers several pages on my Arduino-IDE.
After choosing the appropriate variant in the IDE I coded the following three versions:
1. Button press activated
2. WiFi activated
3. Bluetooth activated
Button Press:
int red = 13; int yellow = 12; int green = 14; int button = 23; int buttonValue = 0; void setup(){ pinMode(button,INPUT); pinMode(red,OUTPUT); pinMode(yellow,OUTPUT); pinMode(green,OUTPUT); pinMode(button,INPUT); digitalWrite(red,HIGH); } void loop(){ buttonValue = digitalRead(button); if (buttonValue == HIGH){ changeLights(); delay(4000); digitalWrite(green, LOW); digitalWrite(yellow, HIGH); delay(1000); digitalWrite(yellow, LOW); digitalWrite(red, HIGH); delay(2000); buttonValue = LOW; } } void changeLights(){ // red off, yellow for 1 seconds digitalWrite(red, LOW); digitalWrite(yellow, HIGH); delay(1000); // turn off yellow, then turn green on digitalWrite(yellow, LOW); digitalWrite(green, HIGH); }
Wifi:
#include "WiFi.h" int red = 13; int yellow = 12; int green = 14; // Replace with your network credentials const char* ssid = "**********"; const char* password = "**********"; WiFiServer server(80); String header; String buttonState = "red"; unsigned long currentTime = millis(); unsigned long previousTime = 0; const long timeoutTime = 2000; void setup() { pinMode(red, OUTPUT); pinMode(yellow, OUTPUT); pinMode(green, OUTPUT); digitalWrite(red, HIGH); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } Serial.begin(115200); Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void changeLights() { digitalWrite(red, LOW); digitalWrite(yellow, HIGH); delay(1000); digitalWrite(yellow, LOW); digitalWrite(green, HIGH); delay(4000); digitalWrite(green, LOW); digitalWrite(yellow, HIGH); delay(1000); digitalWrite(yellow, LOW); digitalWrite(red, HIGH); delay(2000); } void loop() { WiFiClient client = server.available(); if (client) { currentTime = millis(); previousTime = currentTime; String currentLine = ""; while (client.connected() && currentTime - previousTime <= timeoutTime) { currentTime = millis(); if (client.available()) { char c = client.read(); header += c; if (c == '\n') { if (currentLine.length() == 0) { client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); client.println("Connection: close"); client.println(); if (header.indexOf("GET /26/green") >= 0) { buttonState = "green"; } else if (header.indexOf("GET /26/red") >= 0) { buttonState = "red"; changeLights(); } 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:,\">"); client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"); client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;"); client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}"); client.println(".button2 {background-color: #555555;}</style></head>"); client.println("<body><h1>Traffic Light Changer</h1>"); if (buttonState == "red") { client.println("<p><a href=\"/26/red\"><button class=\"button\">GREEN</button></a></p>"); } else { client.println("<p><a href=\"/26/green\"><button class=\"button button2\">RED</button></a></p>"); } client.println("</body></html>"); client.println(); break; } else { currentLine = ""; } } else if (c != '\r') { currentLine += c; } } } header = ""; client.stop(); } }
Bluetooth (on the C3 version - BLE):
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> BLECharacteristic *pCharacteristic; bool deviceConnected = false; float txValue = 0;
// the following are the RGB leds for the C3 const byte red = 3; const byte green = 4; const byte blue = 5; #define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" #define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" void changeLights() { digitalWrite(red, HIGH); digitalWrite(green, HIGH); digitalWrite(blue, LOW); delay(2000); digitalWrite(red, LOW); digitalWrite(green, HIGH); digitalWrite(blue, LOW); delay(4000); digitalWrite(red, HIGH); digitalWrite(green, HIGH); digitalWrite(blue, LOW); delay(2000); digitalWrite(red, HIGH); digitalWrite(green, LOW); digitalWrite(blue, LOW); } class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); if (rxValue.length() > 0) { for (int i = 0; i < rxValue.length(); i++) { } if (rxValue.find("G") != -1) { changeLights(); } } } }; void setup() { pinMode(red, OUTPUT); pinMode(green, OUTPUT); pinMode(blue, OUTPUT); digitalWrite(red, HIGH); digitalWrite(green, LOW); digitalWrite(blue, LOW); BLEDevice::init("Traffic Light Changer"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristic->addDescriptor(new BLE2902()); BLECharacteristic *pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); pCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); pServer->getAdvertising()->start(); } void loop() { if (deviceConnected) { } delay(1000); }
Given the distinct dearth of traffic lights in Tasmania, I'm not sure how directly useful any of these devices will be in shortening my daily commute, but I can say that it opens up some possibilities for monitoring sensors and activating motors/pumps, etc, around the property. So perhaps overall it was a useful exercise!?
I also "enjoyed" the wrestle with the C3-BLE version of the ESP32, which I think may be very interesting to play with in the future.