Sunday, March 24, 2024

Headphone Jack turns Ham Radio Morse Key

 

-----

We have featured a few of our DIY ham radio Morse Code Keys here before, but this one is less than a dollar, super portable, and actually works surprisingly well.  I may have found my new hiking key!

-----



Thursday, March 21, 2024

HTML Help for Guitar Chord Changes


 -----

We committed to learning the guitar (again).  However, this time we are taking a more methodical approach vs. just getting frustrated that we don't sound like Monte Montgomery.   

The internet is helpful and one of our favorite resources has been Lauren Bateman who encourages "Embace the Suck".   Lauren encourages other things too... like practicing chord changes with a metronome and keeping a progress log.  These two suggestions are huge.  We created some simple HTML markup to help us practice random chord changes and, gotta say, you show more results with practice than with excuses.   

Just copy and save the HTML markup between the "-----"s below on your hard drive as "random_guitar_chords.html" and it should work in any web browser.    NOTE:  There is no spyware, tracking, cookies, disk writes, etc.  Feel free to edit the HTML to meet your needs.

 Good luck and see you on the stage!

-----

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Random Guitar Chords</title>
<style>
    body {
        font-family: Arial, sans-serif;
        text-align: center;
    }
    #chordDisplay {
        font-size: 36px;
        margin-top: 50px;
    }
    .checkbox-container {
        margin-bottom: 5px;
    }
</style>
</head>
<body>

<h1>Select Guitar Chords</h1>

<form id="chordForm">
    <div class="checkbox-container">
        <input type="checkbox" id="A" value="A" checked>
        <label for="A">A</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="C" value="C" checked>
        <label for="C">C</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="D" value="D" checked>
        <label for="D">D</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="E" value="E" checked>
        <label for="E">E</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="Em" value="Em" checked>
        <label for="Em">Em</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="F" value="F" checked>
        <label for="F">F</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="F#m" value="F#m" checked>
        <label for="F#m">F#m</label>
    </div>
    <div class="checkbox-container">
        <input type="checkbox" id="G" value="G" checked>
        <label for="G">G</label>
    </div>
</form>

<p><strong>!!! ALWAYS USE A METRONOME AND LOG YOUR PROGRESS!!!</strong></p>

<label for="initialChordsSelect">How many chords to cycle:</label><br>
<select id="initialChordsSelect">
    <option value="2" selected>2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
    <option value="6">6</option>
    <option value="7">7</option>
    <option value="8">8</option>
</select><br>

<br>

<label for="timeSelect">Seconds until next random chords:</label><br>
<select id="timeSelect">
    <option value="10">10</option>
    <option value="15">15</option>
    <option value="20">20</option>
    <option value="25">25</option>
    <option value="30">30</option>
    <option value="35">35</option>
    <option value="40">40</option>
    <option value="45">45</option>
    <option value="50">50</option>
    <option value="55">55</option>
    <option value="60" selected>60</option>
    <option value="65">65</option>
    <option value="70">70</option>
    <option value="75">75</option>
    <option value="80">80</option>
    <option value="85">85</option>
    <option value="90">90</option>
    <option value="95">95</option>
    <option value="100">100</option>
    <option value="105">105</option>
    <option value="110">110</option>
    <option value="115">115</option>
    <option value="120">120</option>
</select><br>

<br>

<button onclick="startDisplay()">Let's Jam!</button>

<div id="chordDisplay"></div>
<br>
<div id="timer"></div>
<br>
<div id="totalMinutes"></div>

<script>
var startTime;
var timerInterval;

function startDisplay() {
    clearInterval(timerInterval); // Clear previous interval
    document.getElementById("chordDisplay").textContent = ""; // Clear previous chords
    document.getElementById("timer").textContent = ""; // Clear previous timer
    startTime = new Date().getTime();
    displayChords();
    displayTimer();
}

function displayChords() {
    var chords = [];
    var checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
    for (var i = 0; i < checkboxes.length; i++) {
        chords.push(checkboxes[i].value);
    }

    var numInitialChords = parseInt(document.getElementById("initialChordsSelect").value);
    var initialChords = [];
    for (var i = 0; i < numInitialChords; i++) {
        var randomChord;
        do {
            randomChord = chords[Math.floor(Math.random() * chords.length)];
        } while (initialChords.includes(randomChord));
        initialChords.push(randomChord);
    }

    document.getElementById("chordDisplay").textContent = initialChords.join(" ");
}

function displayTimer() {
    var timeInSeconds = parseInt(document.getElementById("timeSelect").value);
    var count = timeInSeconds;
    timerInterval = setInterval(function() {
        count--;
        if (count <= 0) {
            clearInterval(timerInterval);
            document.getElementById("timer").textContent = "";
            displayChords();
            setTimeout(displayTimer, 1000); // Restart the timer after displaying new chords
        } else {
            document.getElementById("timer").textContent = "SPACEBAR for immediately ELSE selecting new chords in: " + count + " seconds";
            updateTimeElapsed();
        }
    }, 1000);

    function updateTimeElapsed() {
        var currentTime = new Date().getTime();
        var elapsedTime = (currentTime - startTime) / (1000 * 60);
        document.getElementById("totalMinutes").textContent = "Total practice time in minutes: " + elapsedTime.toFixed(1);
    }
}

document.body.addEventListener('keydown', function(e) {
    if (e.keyCode === 32) { // Spacebar key code
        clearInterval(timerInterval);
        document.getElementById("chordDisplay").textContent = ""; // Clear previous chords
        document.getElementById("timer").textContent = ""; // Clear previous timer
        displayChords();
        displayTimer();
        e.preventDefault(); // Prevent default spacebar behavior (scrolling)
    }
});
</script>

</body>
</html>

-----



Wednesday, February 28, 2024

USB Jump Battery "Hack" for Meshtastic WisBlock Base Board

-----

Meshtasic is cool.   The quickie description from their website is: "An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices".  It's a way to send SMS type messages without the use of the internet, cell phone infrastructure, etc.  The hardware is cheap and there are Android and iPhone apps to that support it.

We wanted to experiment with Meshtasic and purchased the RAKwireless WisBlock Base Board from Rokland.  One nice thing about the WisBlock Base Board are the many supported 'plug and play' modules that allow for easy expansion.

-----

Enough about that... the problem we had was running the WisBlock base board with battery power.  There is a right way to do this and there is the way we did it.  The right way is to use a rechargeable LiPo battery with the connector already installed.

But... this is what we did mainly because we already had a drawer full of USB portable charging battery bricks.  Like many other USB powered devices, the current draw from the WisBlock Base Board is so low that when powering it from a USB portable charging battery the portable charger will just shut down thinking there is nothing connected to it.   Our fix was to add a 100 ohm load.  The WisBlock makes this super easy from the on board BATTERY socket (see "right way" above).

----

Here's the current draw (~15mA) without the 100 ohm load added.  All of our tested USB portable charging battery bricks would shut down:

-----

We plug in the 100 ohm resistor and now all of our tested USB portable charging battery bricks stay activated with ~53mA current draw: 

 

-----

Does it use more juice?  Of course it does, but even the small lipstick battery shown will last for days and that's good enough for our use.

-----

FWIW, here is what our Meshtasic portable rig looks like:

-----

Tuesday, January 23, 2024

WS2812B LED Fireworks Simulator


-----

WS2812B LED strips are pretty cool.  They are string of individually addressable RGB LEDs.  This allows control of the color and brightness of each LED.

Fireworks are also pretty cool.  On the downside they can be dangerous, scare wildlife, start fires, be expensive, illegal, etc.   So, until we can afford our own fleet of drones we settled on this alternative.  Like many projects, we stand on the shoulders of giants mentioned in the Ardunio source code below.  Our main issue with their code was the effect was never changing so we improved mainly on that aspect; a few other things as well.

----

Result:

-----

The hardware is an ESP8266 and a WS2812B LED-strip with 300 LEDs (16.5 feet).  We wanted to use a Ardunio Nano (because we had one), but due to the amount of memory needed to define the arrays for the 300 LEDs we went with an ESP8266 (because we had one).

-----
You are also going to need a big pole.

 



-----

/*  
 *  LED Fireworks Simulator
 *  WhiskeyTangoHotel.Com
 *  JAN 2024
 *
 *  To vary the effect experiments randomizing variables
 *  within acceptable limits was done.  Otherwise the effect
 *  just looks to same 'shot after shot'.  Other mods as well
  *
 *  Leverage from https://www.Daniel-Westhof.de and
 *  https://www.anirama.com/1000leds/1d-fireworks/
 *
 *  Hardware:  
 *  HiLetgo 1PC ESP8266 NodeMCU CP2102 ESP-12E Development Board and a
 *  WS2812B LED-strip with 300 LEDs (16.5 feet).
 */
 
#include <FastLED.h>
#define NUM_LEDS 300
#define DATA_PIN 5  // Labeled a D1 on the board.
#define LED_PIN 2  // This is the BLUE LED on board
#define NUM_SPARKS NUM_LEDS/2  // OG: NUM_LEDS/2
 
CRGB leds[NUM_LEDS]; // sets up block of memory
 
float sparkPos[NUM_SPARKS];
float sparkVel[NUM_SPARKS];
float sparkCol[NUM_SPARKS];
float flarePos;
float gravity = -.008; // m/s/s
int launch_delay; // we later randomize seconds between launches
 
void setup() {
  Serial.begin(115200);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  pinMode(LED_PIN, OUTPUT);
}
 
void loop() {   
  // Delay untill next launch. Blink BLUE on board LED
  Serial.println("  ");
  launch_delay = int(random(5,30)); // Min>=5.  Randomize secs between BOOMs.  
  //launch_delay = 5;   
  for (int i = (launch_delay - 5); i > 0; i--) {
    Serial.println(String(i + 5) + " seconds to launch...");
    digitalWrite(LED_PIN, LOW);  // ON
    delay(500);
    digitalWrite(LED_PIN, HIGH);  // OFF
    delay(500);    
  }

  // Slower timer done .  Fast blink for ~5 seconds to warn of BOOM
  Serial.print("5 seconds to launch!!!");
  for (int i = 50; i > 0; i--) {
    Serial.print(".");
    digitalWrite(LED_PIN, LOW);  // ON
    delay(50);
    digitalWrite(LED_PIN, HIGH);  // OFF
    delay(50);
  }

  Serial.println(".");
  Serial.print("BOOM...");
  digitalWrite(LED_PIN, LOW); // LED ON
 
  // send up flare
  flare();
  digitalWrite(LED_PIN, HIGH); // LED OFF
 
  // explode
  explodeLoop();
}
 

void flare() {
  flarePos = 0;  // 0
  // flareVel is how hight the BOOM is.  2.2 is max height
  float flareVel = float(random(180, 215)) / 100; // Start: (180, 195)) / 100; trial and error to get reasonable range
  Serial.println(" with flare height of " + String((flareVel*100)/2.2) + "%");  // How high is the BOOM?
  float brightness = 5;   // OG: 1
 
  // initialize launch sparks
  int blast_base = random(5,20);  // number of sparks at blast base.
  for (int i = 0; i < blast_base; i++) {   // OG: int i = 0; i < 5; i++  
    sparkPos[i] = 0; sparkVel[i] = (float(random8()) / 255) * (flareVel / 2); // OG: (float(random8()) / 255) * (flareVel / 5); the / xx); is control value for BURST
    sparkCol[i] = sparkVel[i] * 1000; sparkCol[i] = constrain(sparkCol[i], 0, 255);
    //Serial.println(String(i) + "   " + String(sparkVel[i]) + "  " + String(sparkCol[i]));
  }  

  // launch
  while (flareVel >= -.2) {   // OG: flareVel >= -.2  when to explode after peak BOOM.  Bigger neg val means more fall before sparks
    // sparks
    for (int i = 0; i < blast_base; i++) {   // OG: int i = 0; i < 5; i++  
      sparkPos[i] += sparkVel[i];
      sparkPos[i] = constrain(sparkPos[i], 0, 120);
      sparkVel[i] += gravity;
      sparkCol[i] += -.8;
      sparkCol[i] = constrain(sparkCol[i], 0, 255);
      leds[int(sparkPos[i])] = HeatColor(sparkCol[i]);
      leds[int(sparkPos[i])] %= 50; // reduce brightness to 50/255
    }
   
    // flare
    leds[int(flarePos)] = CHSV(0, 0, int(brightness * 255));
    FastLED.show();
    delay(5);
    FastLED.clear();
    flarePos += flareVel;
    flarePos = constrain(flarePos, 0, NUM_LEDS-1);
    flareVel += gravity;
    brightness *= .99; // OG = .98
  }  // while (flareVel >= -.2)
}  // end void flare
 
void explodeLoop() {
  int nSpark_var = random(2, 10);  // Bigger number is less BOOM sparks
  int nSparks = flarePos / nSpark_var; // OG: nSparks = flarePos / 2
  //Serial.println(String(nSparks));

   
  // initialize sparks
  for (int i = 0; i < nSparks; i++) {
    sparkPos[i] = flarePos; sparkVel[i] = (float(random(0, 20000)) / 10000.0) - 1.0; // from -1 to 1
    sparkCol[i] = abs(sparkVel[i]) * 500; // set colors before scaling velocity to keep them bright
    sparkCol[i] = constrain(sparkCol[i], 0, 255);
    sparkVel[i] *= flarePos / NUM_LEDS; // proportional to height
  }
  sparkCol[0] = 255; // OG: 255  This will be our known spark
  float dying_gravity = gravity;
  float c1 = random(80,130);  // OG: 120
  float c2 = random(1,30);   // OG: 50
  //Serial.println("c1 is: " + String(c1));
  //Serial.println("c2 is: " + String(c2));
    
 
  while(sparkCol[0] > c2/128) { // OG: (sparkCol[0] > c2/128)  As long as our known spark is lit, work with all the sparks
    int decay_rate = (random(0,50));  // Slow to decay the blast sparks.  (0,50) seems right. Bigger is slower.  OG: delay(0);
    delay(decay_rate);
    //Serial.println(String(decay_rate));
    FastLED.clear();
    
    for (int i = 0; i < nSparks; i++) {   
      sparkPos[i] += sparkVel[i];
      sparkPos[i] = constrain(sparkPos[i], 0, NUM_LEDS-1);
      sparkVel[i] += dying_gravity;
      sparkCol[i] *= .975;
      sparkCol[i] = constrain(sparkCol[i], 0, 255); // RED cross dissolve. OG: constrain(sparkCol[i], 0, 255);
      
      if(sparkCol[i] > c1) { // fade white to yellow
        leds[int(sparkPos[i])] = CRGB(random(0,255), random(200,255), (255 * (sparkCol[i] - c1)) / (255 - c1));  // OG: CRGB(255, 255, (255 * (sparkCol[i] - c1)) / (255 - c1));
      }
      else if (sparkCol[i] < c2) { // fade from red to black
        leds[int(sparkPos[i])] = CRGB((random(200,255) * sparkCol[i]) / c2, 0, 0); // OG: CRGB((255 * sparkCol[i]) / c2, 0, 0);
      }
      else { // fade from yellow to red
        leds[int(sparkPos[i])] = CRGB(random(0,255), (random(200,255) * (sparkCol[i] - c2)) / (c1 - c2), 0);  // OG: CRGB(255, (255 * (sparkCol[i] - c2)) / (c1 - c2), 0);
      }      
    }
    dying_gravity *= .99; // OG: dying_gravity *= .99;  As sparks burn out they fall slower
    FastLED.show();    
  }  // end while(sparkCol)
 
  delay(5);
  FastLED.clear();
  delay(5);  
  FastLED.show();
  Serial.println("Effect Complete!!!");
} // end void explodeLoop() 

-----