Thursday, March 2, 2023
Monday, January 16, 2023
Friday, September 16, 2022
Morse Code (CW) Paddle becomes Bug
-----
CW or Morse Code is a way ham radio operators send messages with a simple tone of two durations. It was a
must before the days of wireless voice communications and still remains
popular because it is a fun challenge. Like most things, Morse keys range from low cost to expensive. Generally speaking Morse keys come in three styles: straight key, paddle, and bug. Each has pros and cons requiring unique skills to operate with the "bug" typically being more expensive and requiring more skill.
To use this rig, you don't need a ham radio license unless you connect to
an actually ham radio transmitter; which is kinda the whole point. My
quick ham radio advertisement: Get a FCC Amateur Radio license; it's not hard and is an extremely interesting hobby. BTW, learning Morse Code is no longer a requirement.
-----
We wanted to try a Morse bug key, but didn't want one 'forever' and didn't want to spend the money. Plus like most ham radio operators we think any project is a good project regardless of it's usefulness. Basically, with a bug key the dahs (dashes) are made manually and the dits (dots) are generated semi automatically via a spring mechanism. The design is mechanically challenging so we opted for a different method.
-----
The dah (dash) part is easy. For the semi automatic dash (dit) part the traditional mechanical spring system was replaced with a $10 signal generator kit. Pressing the dit key powers up a 7404 and a square wave output is passed through the 7404 simply to provide the "umph" for the relay to open and close providing the dits. The signal generator frequency control allows for an easy way to adjust the sending WPM (words per minute).
The video shows our very first unpracticed test of the rig. After a bit of practice we put the key on the air for a few SKCC QSOs. Fun was had on our end and tolerance was shown by the OPs decoding our sig,
-----
Here are a few pics of the rig on the bench:
-----
And here's the schematic:
Of course, not the traditional Morse key bug, but a nice afternoon project diversion. 73!!!
-----
Saturday, July 30, 2022
@Cheerlights Project: Now with Sound
-----
We did a @cheerlights project that used a Raspberry PI Zero and a Blinkt! LED bar to keep track of the last eight color changes the cheerlights server received. We put the rig in the garage and enjoyed the Blinkt! LED bar change color as The Internet masses dictated what rainbow of colors would be displayed. But, that wasn't enough. To control CheerLights, send a tweet to @cheerlights or include “cheerlights” somewhere in your message with the name of a color. for example, @CheerLights Paint the town red. [To learn more about how to control any cheerlights project go to https://cheerlights.com/]
-----
The Amazon impulse buy AI suggested that our life could be complete only if we spent $7.99US on the DollaTEK DY-SV17F mini MP3 module and an idea was born. What if, we added sound to the existing @cheerlights project and a short MP3 sound clip was played in addition to the LED color change? If The Internet masses demand 'purple' we change an LED to 'purple' and play a clip from the song "Purple Rain". 'pink' would trigger "Pink Cadillac". 'red' triggers "Red Red Wine". You get the idea....
-----
The DollaTEK DY-SV17F module has a UART mode to reduce the pins needed to control it, but the module is big on features and short on documentation for use with the Raspberry PI. After not being able to get UART mode to work (probably user error) it was decided to use the plentiful GPIO pins on the Raspberry PI Zero and go with a parallel interface solution. Here is a short video of the result:
-----
Connection was straightforward:
Packaged and mounted in the garage:
For the rig to work the two python programs below run on the RasPI at the same time; cheerlights.py and cheerlights_sound.py.
-----
The cheerlights.py source code below updates the LEDs on the Blinkt! LED bar:
#!/usr/bin/env python
# Cheerlights with Pimoroni BlinkT module and RasPI Zero
#
# A tweet to @Cheerlights will change the LEFT most color of the BlinkT (LED#7)
# Color history maintained by shifting old color to the RIGHT (LED#0)
# Valid color tweets to @Cheerlights are:
# RED GREEN BLUE CYAN WHITE OLDLACE PURPLE MAGENTA YELLOW ORANGE PINK
#
# Project details at:
# WhiskeyTangoHotel.Com
#
# MARCH 2022
# JULY 2022 add DollaTek DY-SV17F to play sound clip pwr color as independent and seperate program
#
import time
import sys
try:
import requests # needed to poll @Cheerlights
except ImportError:
exit("Install needed to run. Use the command: sudo pip install requests")
from blinkt import set_clear_on_exit, set_pixel, show, set_brightness, clear # https://shop.pimoroni.com/products/blinkt#
LED_delay = 0.5
brightness = 0.1 # 0.05 is lowest useable dim. 1.0 is full bright (the BLINKT is *really* bright if you want!)
#Set up a matrix for r, g, b values (m stands for matrix)
rm = [0,1,2,3,4,5,6,7,8]
gm = [0,1,2,3,4,5,6,7,8]
bm = [0,1,2,3,4,5,6,7,8]
# set_pixel(pixel_no, red, green, blue, brightness)
print "Testing LEDs..."
print "---------------------------------------------"
for i in range(3):
for j in range (0,8):
set_pixel(j, 30, 0, 0, brightness)
print "RED"
show()
time.sleep(LED_delay)
for j in range (0,8):
set_pixel(j, 0, 30, 0, brightness)
print "GREEN"
show()
time.sleep(LED_delay)
for j in range (0,8):
set_pixel(j, 0, 0, 30, brightness)
print "BLUE"
show()
time.sleep(LED_delay)
# all LEDs off
for j in range (0,8):
set_pixel(j, 0, 0,0)
show()
print "LED Self test complete!"
for j in range (0,8): # set values that we know are 'wrong' to enter main loop
rm[j] = 73.73
gm[j] = 73.73
bm[j] = 73.73
print " "
while True:
try:
r = requests.get('http://api.thingspeak.com/channels/1417/field/2/last.json')
col = r.json()['field2']
r, g, b = tuple(ord(c) for c in col[1:].lower().decode('hex'))
time.sleep(5) # delay until we look for a new color change
if (r != rm[7]) or (g != gm[7]) or (b != bm[7]): # new color selected
print "New color placed far LEFT. Shift old colors RIGHT one place"
for j in range (0,7): #shift in the new values
rm[j] = rm[j+1]
gm[j] = gm[j+1]
bm[j] = bm[j+1]
set_pixel(j, rm[j], gm[j], bm[j], brightness)
show()
time.sleep(LED_delay) # show change as a sweep
rm[7] = r
gm[7] = g
bm[7] = b
set_pixel(7, rm[7], gm[7], bm[7], brightness)
show()
# All LED off on control C
except KeyboardInterrupt:
print("stopping ...")
sys.exit(0)
except:
time.sleep(1)
-----
The cheerlights_sound.py source code below runs the DollaTEK DY-SV17F:
# Cheerlights with DY-SV17F module for sound and RasPI Zero
#
# A tweet to @Cheerlights will play sound file
# Valid color tweets to @Cheerlights are:
# RED GREEN BLUE CYAN WHITE OLDLACE PURPLE MAGENTA YELLOW ORANGE PINK
#
# Project details at:
# WhiskeyTangoHotel.Com
#
# MARCH 2022
# JULY 2022 add DollaTek DY-SV17F to play sound clip pwr color as independent and seperate program
#
# DallaTEK DY-SV17F Files mapped to Cheerlights color. MP3s vary from ~10-30 secs
# 00001.mp3 = red (red red wine)
# 00002.mp3 = green (green green gras of home)
# 00003.mp3 = blue (blue bayoe)
# 00004.mp3 = cyan (call me cyan)
# 00005.mp3 = white (whiter shade of pale)
# 00006.mp3 = oldlace (leather and lace)
# 00007.mp3 = purple (purple rain [of course])
# 00008.mp3 - magenta (some song calll 'magenta')
# 00009.mp3 = yellow (yellow submarine)
# 00010.mp3 = orange (orange crush)
# 00011.mp3 = pink (pink cadi)
# 00012.mp3 = start speaker test file (start me up)
import time
import requests # needed to poll @Cheerlights
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD) # to use Raspberry PI board pin numbers
#Define some pins and vars
IO_0 = 31
IO_1 = 29
IO_2 = 3
IO_3 = 5
IO_4 = 7
IO_5 = 11
IO_6 = 13
IO_7 = 15
CON_1 = 19
CON_2 = 21
CON_3 = 23
current_color_mp3 = "clear" # set color value that we know is 'wrong' to enter main loop
delta_time = 0 # track seconds since last color change
#Setup the GPIO and make sure the speaker is OFF
GPIO.setwarnings(False) # To disable warnings.
GPIO.setup(IO_0, GPIO.OUT)
GPIO.setup(IO_1, GPIO.OUT)
GPIO.setup(IO_2, GPIO.OUT)
GPIO.setup(IO_3, GPIO.OUT)
GPIO.setup(IO_4, GPIO.OUT)
GPIO.setup(IO_5, GPIO.OUT)
GPIO.setup(IO_6, GPIO.OUT)
GPIO.setup(IO_7, GPIO.OUT)
GPIO.setup(CON_1, GPIO.OUT)
GPIO.setup(CON_2, GPIO.OUT)
GPIO.setup(CON_3, GPIO.OUT) #HIGH = speaker sound. LOW = sound off
GPIO.output(CON_1, GPIO.LOW)
GPIO.output(CON_2, GPIO.LOW)
GPIO.output(CON_3, GPIO.LOW) #HIGH = speaker sound. LOW = sound off
# Test the speaker at startup
print "Testing speaker..."
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.LOW)
GPIO.output(IO_3, GPIO.LOW)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(5) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
print "Speaker test complete!"
print
while True: # main loop
# ID the Color and Play the MP3 file asscd with that color
r = requests.get('http://api.thingspeak.com/channels/1417/field/1/last.json')
color_mp3 = r.json()['field1']
#color_mp3 = "pink" # override color_mp3 var for debug.
print str(delta_time * 5) + " seconds spent waiting for color change..."
time.sleep(5) # delay until we look for a new color change
delta_time = delta_time + 1
if (color_mp3 != current_color_mp3): # new color selected
current_color_mp3 = color_mp3
delta_time = 0
print color_mp3 + " found and..."
if (color_mp3 == "red"): # is 00001.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(19) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "green"): # is 00002.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(33) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "blue"): # is 00003.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(20) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "cyan"): # is 00004.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.LOW)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(16) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "white"): # is 00005.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.LOW)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(19) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "oldlace"): # is 00006.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.LOW)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(27) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "purple"): # is 00007.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.LOW)
GPIO.output(IO_3, GPIO.HIGH)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(25) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "magenta"): # is 00008.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.LOW)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(26) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "yellow"): # is 00009.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.HIGH)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.LOW)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(18) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "orange"): # is 00010.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.HIGH)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.LOW)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(22) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
if (color_mp3 == "pink"): # is 00010.mp3
GPIO.output(CON_3, GPIO.HIGH) # turn ON the speaker
GPIO.output(IO_0, GPIO.LOW)
GPIO.output(IO_1, GPIO.LOW)
GPIO.output(IO_2, GPIO.HIGH)
GPIO.output(IO_3, GPIO.LOW)
GPIO.output(IO_4, GPIO.HIGH)
GPIO.output(IO_5, GPIO.HIGH)
GPIO.output(IO_6, GPIO.HIGH)
GPIO.output(IO_7, GPIO.HIGH)
time.sleep(25) # secs in this mp3
GPIO.output(CON_3, GPIO.LOW) # turn OFF the speaker
print color_mp3 + " playing completed!"
print
-----
Monday, June 20, 2022
Harbor Freight vs. 3M Infrared Thermometer
Way back in 2013 we purchased an infrared thermometer from Harbor Freight for less than ten bucks.
----
Recently we got our hands on a 3M Model IR-750B "Scotchtrak Heat Tracer" infrared thermometer and decided to compare the two units. Right away it's obvious that the 3M Model IR-750B has a superior feel.
Plus. the 3M Model IR-750B comes in a nice carrying case. We were not able to find pricing on the 3M Model IR-750B, but something tells us the price was more the generic Harbor Freight model. Both units run on a single 9VDC battery.-----
So..... How do their measurements compare?
-----
Pretty close! Which unit is correct? We don't have a calibrated source so we have no way of knowing. What we do know is they produce temperature readings that are closely collaborated.
-----
Sunday, March 13, 2022
@Cheerlights Control of BLINKT via Raspberry PI Zero
On Twitter we kept seeing messages sent to @cheerlights follow by a color. Turns out tweeting a color to @cheerlights turns a whole bunch of LEDs around the world a different color. And now, thanks to this project, the LEDs in my garage.
----
The concept seemed interesting and we had a Raspberry PI Zero and the Pimoroni BLINKT LED module laying around already. Time to put them to use for this project. Honestly, the @cheerlights API is so easy and well documented we won't cover that here. Same is true with the Pimoroni BLINKT so we will just go straight to the Raspberry PI python source code.
-----
The Python scripts "listens" for if a color change tweet has been sent to @cheerlights. If it "hears" one the far left LED is set to that color. All other LED colors are shifted right to show the history of the last eight colors.
-----
Here's the python source code for those that want to join in on the @cheerlights fun.
#!/usr/bin/env python
# Cheerlights with Pimoroni BlinkT module and RasPI Zero
#
# A tweet to @Cheerlights will change the LEFT most color of the BlinkT (LED#7)
# Color history maintained by shifting old colors to the RIGHT (towards LED#0)
# Valid color tweets to @Cheerlights are:
# RED GREEN BLUE CYAN WHITE OLDLACE PURPLE MAGENTA YELLOW ORANGE PINK
#
# Project details at:
# WhiskeyTangoHotel.Com
#
# MARCH 2022
#
import time # needed for delays
import sys # for requests
try:
import requests # needed to poll @Cheerlights
except ImportError:
exit("Install needed to run. Use the command: sudo pip install requests")
from blinkt import set_clear_on_exit, set_pixel, show, set_brightness,
clear # https://shop.pimoroni.com/prod
LED_delay = 1
brightness = 0.3 # 0.05 is lowest useable dim. 1.0 is full bright
(the BLINKT is *really* bright if you want!)
#Set up a matrix for r, g, b values (m stands for matrix)
rm = [0,1,2,3,4,5,6,7,8]
gm = [0,1,2,3,4,5,6,7,8]
bm = [0,1,2,3,4,5,6,7,8]
# set_pixel(pixel_no, red, green, blue, brightness)
print "Testing LEDs..."
print "---------------------------------------------"
for i in range(3):
for j in range (0,8):
set_pixel(j, 30, 0, 0, brightness)
print "RED"
show()
time.sleep(LED_delay)
for j in range (0,8):
set_pixel(j, 0, 30, 0, brightness)
print "GREEN"
show()
time.sleep(LED_delay)
for j in range (0,8):
set_pixel(j, 0, 0, 30, brightness)
print "BLUE"
show()
time.sleep(LED_delay)
# all LEDs off
for j in range (0,8):
set_pixel(j, 0, 0,0)
show()
print "Self test complete..."
for j in range (0,8): # set values that we know are 'wrong' to get us into the test loop
rm[j] = 73.73
gm[j] = 73.73
bm[j] = 73.73
print " "
while True:
try:
r = requests.get('http://api.thing
col = r.json()['field2']
r, g, b = tuple(ord(c) for c in col[1:].lower().decode('hex'))
time.sleep(5) # delay until we look for a new color change
if (r != rm[7]) or (g != gm[7]) or (b != bm[7]): # new color selected
print "New color placed far LEFT. Shift old colors RIGHT one place"
for j in range (0,7): #shift in the new values
rm[j] = rm[j+1]
gm[j] = gm[j+1]
bm[j] = bm[j+1]
set_pixel(j, rm[j], gm[j], bm[j], brightness)
show()
time.sleep(LED_delay) # show change as a sweep
rm[7] = r
gm[7] = g
bm[7] = b
set_pixel(7, rm[7], gm[7], bm[7], brightness)
show()
# All LED off on control C
except KeyboardInterrupt:
print("stopping ...")
sys.exit(0)
except:
time.sleep(1)
-----
Sunday, February 20, 2022
Simpson 260 VOM answers, "Is the internet up?"
It is a pretty commonly uttered question (sometimes loudly) around the home or office; "Is the internet up?" As the go to IT Support Manager around the house this can get a little tiresome. Most of the time the internet is up and it's a user or personal device problem that can be solved with a re-boot, re-load, re-etc. Rarely is the internet really down requiring a router and cable modem reboot or a call to the ISP. Wouldn't a simple visual check that anyone could quickly understand be helpful?
-----
I recently found my father's old Simpson 260 meter. He let me borrow it anytime I wanted with the warning of "DON'T COOK IT!". My memory recalls only using it for continuity and batteries which is good because I did not have a clue as to what would "cook it". I decided to put this heirloom to use as an internet monitor. This project is amazingly useful and simple to duplicate.
-----
The rig uses an ESP8266 to ping different servers. The ping time (in mS) is displayed on the Simpson 260 and 'percent of full scale'. In other words a ping of 73mS would be 73% of full scale on the Simpson 260. We set the source code (see below) to cycle through ten servers and show the ping result every 15 seconds.
-----
If the LAN is down or a ping error is detected the meter "wags" back and forth 5 times and tries another server. The message to the house is, "If the needle ain't wagging back and forth then your problem ain't with the internet connection!"
Upload the Arduino IDE based source code below to the ESP8266. Connection to the Simpson 260 is easy.
/*
* A vintage Simpson 260 meter to shows network PING times
* and if there is a connection to the internet.
*
* Deflect the Simpson meter from 0-100% of the 2.5V full scall
* based on ping times of servers.
*
* 10mS = 10% of full scale.
* 45mS = 55% of full scale.
* 73mS = 73% if full scale.
* XXmS = XX% of full scale, etc....
* Anything over 100ms is consider terrible and just maxes to 100%
*
* A bad ping (site not found, network down, etc) will 'wag' the meter
* back and forth from 0% to 100% five times then try next ping site.
*
* ESP8266 NodeMCU Board BAUD 115200
*
* Full documetation at:
* WhiskyTangoHotel.Com
*
* FEB2022
*
*/
#include <ESP8266WiFi.h>
#include <Pinger.h>
const char* ssid = "yourSSID"; // These vars are your private WIFI
const char* password = "yourPASSWORD"; // connection information
// Define some 'ping friendly' sites. ARRAY starts a 0
String PingSite[] = {
"whiskeytangohotel.com",
"google.com",
"yahoo.com",
"bing.com",
"abc.com",
"cbs.com",
"cnn.com",
"apple.com",
"totalping.com",
"mailinator.com"
}; // end ping array define
int Number_of_Ping_Sites = 10; // counted from the list above
int Secs_Between_Pings = 15;
float Min_Ping_Result = 999;
const int PINGOUT = 2; // Drives the S260. Blue onboard LED and ~D4
Pinger pinger;
void setup() {
analogWrite(PINGOUT, 0);
delay(50);
Serial.begin(115200);
delay(100);
// Connect to the WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// For the ping dot h features https://github.com/bluemurder/esp8266-ping
pinger.OnEnd([](const PingerResponse& response)
{
// Print time information
if(response.TotalReceivedResponses > 0)
{
Serial.printf("Approximate round trip times in milli-seconds:\n");
Serial.printf(
" Minimum = %lums, Maximum = %lums, Average = %.2fms\n",
response.MinResponseTime,
response.MaxResponseTime,
response.AvgResponseTime);
Min_Ping_Result = response.MinResponseTime;
}
// Print host data
Serial.printf("Destination host data:\n");
Serial.printf(
" IP address: %s\n",
response.DestIPAddress.toString().c_str());
if(response.DestMacAddress != nullptr)
{
Serial.printf(
" MAC address: " MACSTR "\n",
MAC2STR(response.DestMacAddress->addr));
}
if(response.DestHostname != "")
{
Serial.printf(
" DNS name: %s\n",
response.DestHostname.c_str());
Serial.println("Minimum ping was: " + String(Min_Ping_Result) + "mS." + " Meter to " + String(int(Min_Ping_Result)) + "% of full scale.");
Serial.println("Delay to next ping is " + String(Secs_Between_Pings) + " seconds...");
Serial.println("---------------------");
}
return true;
}); // end ping features
//Self Test the meter by moving the meter full scale
// Increase meter value and on board LED brightness
for(int dutyCycle = 0; dutyCycle < 200; dutyCycle++){
// changing the LED brightness with PWM;
analogWrite(PINGOUT, dutyCycle);
Serial.println(String(dutyCycle) + " increasing meter self test...");
//analogWrite(PINGOUT, testval);
delay(10);
} // end meter increase
// Decrease meter value and on board LED brightness
for(int dutyCycle = 200; dutyCycle > 0; dutyCycle--){
// changing the LED brightness with PWM
analogWrite(PINGOUT, dutyCycle);
Serial.println(String(dutyCycle) + " decreasing meter self test...");
//analogWrite(PINGOUT, testval);
delay(10);
} // end meter decrease
Serial.println("Self test complete!!!");
Serial.println("---------------------");
} // end void setup
// dutytCycle/2 = ~ the % of 2.5V scale on S260
// 0 dutytCycle =
// 50 dutytCycle = 28%
// 100 dutytCycle = 53%
// 150 dutytCycle = 77%
// 200 dutytCycle = 100%
// Set S260 to +DC. The + lead to D4. Neg lead to GND
void loop() { // loop until the Cowboys win a Super Bowl
for (int i = 0; i <= (Number_of_Ping_Sites - 1); i++) { // don't always use the same PingSite; cycle them.
Serial.println("PingSite[" + String(i) + "]: " + PingSite[i]);
if(pinger.Ping(PingSite[i]) == false)
{
Serial.println("Error during ping command. Walk the meter 5 times.");
// Walk the meter back and forth to symbol 'ping error' or network down
for (int walk = 0; walk<=4; walk++) {
// Increase meter value and on board LED brightness
for(int dutyCycle = 0; dutyCycle < 200; dutyCycle++){
// changing the LED brightness with PWM;
analogWrite(PINGOUT, dutyCycle);
Serial.println(String(dutyCycle) + "Showing FAIL increasing...");
delay(10);
} // end fail meter increase
// Decrease meter value and on board LED brightness
for(int dutyCycle = 200; dutyCycle > 0; dutyCycle--){
// changing the LED brightness with PWM
analogWrite(PINGOUT, dutyCycle);
Serial.println(String(dutyCycle) + "Showing FAIL decreasing...");
delay(10);
} // end fail meter decrease
} // end for fail meter back forth walk
} // end if pinger.Ping
// Write Ping value to the meter. Low is better.
// We basically make percent of full scale equal the ping in mS, ie; 45mS = 45%...
// Anything over a 100mS is a crappy ping so we make 100mS (100% of scale)
if (Min_Ping_Result > 100) { Min_Ping_Result = 100; }
analogWrite(PINGOUT, Min_Ping_Result * 2); // move to meter to display the ping value
delay(Secs_Between_Pings * 1000); // delay for next ping
} // end for/next to cycle the PingSites
} // end loop until the Cowboys win the Super Bowl
-----
Monday, February 7, 2022
Retirement Clock Goes Digital
-----
We overheard in a conversation recently something like: "Now that I'm retired I really only focus on what day of the week it is and the general time of day." We then discovered this awful looking Day of the Week Clock and the vision of a more modern version came to mind.
-----
The rig is based around this MakerFocus ESP32 Development Board mainly because it works with the Arduino IDE and has WiFi with a display on board. The code (source below) gets it's time/date from a NTP server and then simply figures out and displays the day of the week and what percentage of the day has past. Evidently this is all the critical information needed to guide the non-working through their day.
-----
To dress up the look a case was 3D printed. After seeing the result we needed to celebrate with a beer. As shown in the video below it was a bit early in the day for a drink, but as the ol' saying goes "It's 71% somewhere!".
-----
Retirement Clock Source Code:
/*
* HelTec Automation(TM) ESP32 Series Dev boards OLED "Retirement Clock"
* Adruino IDE Board Setting: WiFi Kit32, Disabled, 240MHz, 921600, None
*
* "Retirement Clock" shows only Day of Week and percentage time left in the day.
*
* FEB 2022
* Search whiskeytangohotel.com for project details.
*
*/
#include "Arduino.h"
#include "heltec.h"
#include <TimeLib.h>
#include <WiFi.h>
#include <WiFiUdp.h>
const char ssid[] = "xxxxxxx"; // your network SSID (name)
const char pass[] = "xxxxxxx"; // your network password
long Sync_Delay = 60000; // 60000 is sync every ten minutes, We aren't going for precision here. LOL
int DOWNUM = 0; // DOWNUM from NTP (expect 0 - 6)
String DOW = "DOW"; // DOWNUM to a Day of Week String
int hourNUM = 0; // hour() value from NTP
float minuteNUM = 0; // minute() value from NPT
// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org";
const int timeZone = 0; // leave as 0 for UTC. We do the offset math in void setup
WiFiUDP Udp;
unsigned int localPort = 8888; // local port to listen for UDP packets
time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);
void setup() {
//NTP setup
Serial.begin(115200);
while (!Serial) ; // Needed for Leonardo only
delay(250);
Serial.println("TimeNTP");
Serial.print("Connecting to... ");
Serial.println(ssid);
WiFi.disconnect();
WiFi.mode(WIFI_MODE_STA);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("IP number assigned by DHCP is: ");
Serial.println(WiFi.localIP());
Serial.println("Starting UDP");
Udp.begin(localPort);
Serial.print("Local port: ");
// Serial.println(Udp.localPort());
Serial.println("waiting for sync... ");
setSyncProvider(getNtpTime);
setSyncInterval(Sync_Delay); // defines how often to check NTP time
// Hetec setup
Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
Heltec.display->flipScreenVertically();
Heltec.display->setFont(ArialMT_Plain_24);
} // end setup link
time_t prevDisplay = 0; // when the digital clock was last displayed timer
void loop() { // main program loop
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
}
}
// Parse time info and send info to serial monitor (for debug)
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(".");
Serial.print(month());
Serial.print(".");
Serial.print(year());
Serial.print(" : ");
if (int(year()) < 2022) {
Serial.print(String(int(year())) + " is the NTP server year. That aint right, so try again!!!");
Serial.println();
setup();
} // the NTP server return something 'wierd' (probably 1970) so try again
//Covert Time in percent of day completed. Midnight Local = 0%, Noon Local = 50%
minuteNUM = minute();
hourNUM = int(hour());
// manually adjust hourNUM and minuteNUM for debug
// hourNUM = 5; // valid values are INTERGERS 0 thru 24
// minuteNUM = 49; // valid values are INTEGERS 0 thru 60
minuteNUM = minuteNUM / 60; // covert minutes to 'fraction'
hourNUM = hourNUM - 6; // hourNUM is in UTC. Adjust to local. -6 = CST, -5 = CDT, 0 = UTC
DOWNUM = int(weekday());
// Calculate percentage of the day pasted into 'progress'
int progress = round( ( ((hourNUM + minuteNUM)) / 24 ) * 100 );
if (progress < 0) { // it's past UTC midnight and the timezone offset created a negative %
progress = 100 + progress;
DOWNUM = DOWNUM - 1;
} //end progress < 0
// Convert DOWNUM into printable String. Sunday is 1
if (DOWNUM == 1) { DOW = "SUN"; }
if (DOWNUM == 2) { DOW = "MON"; }
if (DOWNUM == 3) { DOW = "TUE"; }
if (DOWNUM == 4) { DOW = "WED"; }
if (DOWNUM == 5) { DOW = "THU"; }
if (DOWNUM == 6) { DOW = "FRI"; }
if (DOWNUM == 7) { DOW = "SAT"; }
Serial.print(String(int(weekday())) + " is " + DOW);
Serial.println();
Serial.print(String(hourNUM) + " + " + String(minuteNUM) + " = " + String(progress) + "%");
Serial.println();
// clear the display
Heltec.display->clear();
// Draw the Precent of Day pasted onto progress bar
Heltec.display->drawProgressBar(0, 0, 127, 20, progress);
// Label the Day Of Week and percentage as String
Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
Heltec.display->drawString(60, 31, DOW + " " + String(progress) + "%");
// Write buffer to Heltec display
Heltec.display->display();
delay(5000); // wait some. we are in no hurry to update the screen
} // end void loop main program
void printDigits(int digits)
{
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
-----
Friday, January 21, 2022
Wireless Charger Checker
-----
Charging your phone, watch, earbuds, etc. from a wireless charger sometimes doesn't work. Most of the time it is due to poor alignment to the charge point. Of course, it could be that the wireless charger is broken or simply unplugged.
This quick DIY tool helps verify that the charger is plugged in as well as determine the wireless charger's "sweet spot".
-----
The build is about as cheap and simple is it can be. If you have trouble building the rig from the image below you may be following the wrong project site. ;)
-----
-----Saturday, October 2, 2021
Elenco Resistor Substitution Kit
-----
We built the Elenco Capacitor Substitution Kit and because it went together so well decided to build it's partner the Elenco Resistor Substitution Kit. Not much else to say; like the Capacitor Substitution Kit the directions are easy to follow for this very basic kit. We did make the same modification and replace the provided alligator clip connection with banana jacks. The measured values look fine:
-----
-----Sunday, September 5, 2021
Elenco Capacitor Substitution Kit
-----
Not often (okay, almost never) a capacitance substitution box would come in handy on the bench. Finally, the Amazon AI wizard suggested that at $14.99 we couldn't live life any longer without one.
From the pic above you can see that the included wire and alligator clip connector were replaced with banana plug jacks. It gives the rig and the bench a cleaner look.
-----
The directions (which we folded up and placed inside the box for future generations) were straight forward and the values are accurate enough per a NIST traceable capacitance meter.
-----
The same company also makes a resistance substitution box which we also don't need, but will probably build up anyway. Both are a nice, cheap, welcome convenience.
-----
Sunday, May 30, 2021
Remote CW Straight Key [FlexRadio and RemoteHams.Com]
-----
As a remote ham radio operator that enjoys using a manual CW (Morse code) straight key we were presented with two problems:
- RemoteHams.Com remote manual keying
- FlexRadio remote manual keying
Since both require slightly different hardware wiring to the PC running the remote rig, we set out to make something that would be a universal connection regardless of which platform we are using.
-----
The hardware connection is simple and shown below. It does require a USB/RS-232 adapter and using the RS-232 terminal block
break out module shown makes things easier. There are many example of these two items on Amazon.Com, etc.
For both applications software is needed as well.
-----
For our RemoteHams.Com solution go here.
-----
For the FlexRadio solution you will need to install the excellent and easy to use Remote Keyer Interface software. Follow the RKI link for the software and you will also find the very helpful RKI message board that I personally proved is helpful and tolerant of 'stupid' questions. Seriously, the software has great documentation and even though I asked a "RTFM" question I got helpful direction from the forum. After you install and run the RKI software you will see this screen:
Make the remote connection to your FlexRadio via your SmartLink login and select the correct COM Port for your (now) USB straight key. Verify the other options are selected per above screen shot and you should be good to go.
BTW, Remote Keyer Interface software has several other cool features in addition to this simple application.
-----
Summary: My remote CW manual straight keying problems are solved!
-----
Wednesday, May 26, 2021
The Tale of Three USB Chargers
-----
Everyone seems to have a box full of USB chargers. Well, we do anyway and decided to pick two random "no name" chargers and pit them up against an official Apple USB charger.
The test setup was the same for each USB charger. We swept a load that increases from 100mA until the USB charger went into protection (no voltage output). We then checked EMI/RF Noise and 'vampire drain'.
EMI/RF Noise is a big concern. EMI/RF can have a negative effect on WiFi, Bluetooth, ham radio, and pretty much any radio signal in the area. Measuring with a Spectrum Analyzer the Apple was by far the most RF quite. The White "no name" was extremely EMI/RF noisy with the Black "no name" being pretty bad as well.
"Vampire drain" or standby power is how much energy the charger uses when just plugged into the wall doing nothing. The Apple won in this category as well.
My opinion, get the Apple or other well made USB charger and avoid the "no names", The Apple uses less standby power and should not have a negative effect on your or your neighbors wireless equipment. The Apple power output meets spec as well and is more linear until protection kicks in. The Apple is just better and some "no names" are are just unsafe.
-----
-----
-----
-----