Begin work on standalone websocket client

This commit is contained in:
Neale Pickett 2024-10-20 17:34:41 -06:00
parent 533dd49bdc
commit 38cf3f535d
10 changed files with 199 additions and 359 deletions

10
.clangd
View File

@ -1,10 +0,0 @@
CompileFlags:
Add:
- "--include-directory=/opt/arduino/hardware/arduino/avr/cores/arduino"
- "--include-directory=/opt/arduino/hardware/arduino/avr/variants/standard"
- "--include-directory=/opt/arduino/hardware/arduino/avr/libraries/HID/src"
- "--include-directory=/opt/arduino/hardware/tools/avr/avr/include"
- "--include-directory=/opt/arduino/libraries/Keyboard/src"
- "--include-directory=/opt/arduino/libraries/HID/src"
- "--include-directory=/home/dartcatcher/Arduino/libraries/MIDIUSB/src"

View File

@ -1,5 +0,0 @@
{
"clangd.arguments": [
"--enable-config"
]
}

View File

@ -1,132 +0,0 @@
#include <Arduino.h>
#include <Keyboard.h>
#include <MIDIUSB.h>
#include <cstddef>
#include "keyers.h"
#include "adapter.h"
#include "polybuzzer.h"
#define MILLISECOND 1
#define SECOND (1000 * MILLISECOND)
VailAdapter::VailAdapter(unsigned int PiezoPin) {
this->buzzer = new PolyBuzzer(PiezoPin);
}
bool VailAdapter::KeyboardMode() {
return this->keyboardMode;
}
// Send a MIDI Key Event
void VailAdapter::midiKey(uint8_t key, bool down) {
midiEventPacket_t event = {uint8_t(down?9:8), uint8_t(down?0x90:0x80), key, 0x7f};
MidiUSB.sendMIDI(event);
MidiUSB.flush();
}
// Send a keyboard key event
void VailAdapter::keyboardKey(uint8_t key, bool down) {
if (down) {
Keyboard.press(key);
} else {
Keyboard.release(key);
}
}
// Begin transmitting
void VailAdapter::BeginTx() {
this->buzzer->Note(0, this->txNote);
if (this->keyboardMode) {
this->keyboardKey(KEY_LEFT_CTRL, true);
} else {
this->midiKey(0, true);
}
}
// Stop transmitting
void VailAdapter::EndTx() {
this->buzzer->NoTone(0);
if (this->keyboardMode) {
this->keyboardKey(KEY_LEFT_CTRL, false);
} else {
this->midiKey(0, false);
}
}
// Handle a paddle being pressed.
//
// The caller needs to debounce keys and deal with keys wired in parallel.
void VailAdapter::HandlePaddle(Paddle paddle, bool pressed) {
switch (paddle) {
case PADDLE_STRAIGHT:
if (pressed) {
this->BeginTx();
} else {
this->EndTx();
}
return;
case PADDLE_DIT:
if (this->keyer) {
this->keyer->Key(paddle, pressed);
} else if (this->keyboardMode) {
this->keyboardKey(KEY_LEFT_CTRL, pressed);
} else {
this->midiKey(1, pressed);
}
break;
case PADDLE_DAH:
if (this->keyer) {
this->keyer->Key(paddle, pressed);
} else if (this->keyboardMode) {
this->keyboardKey(KEY_RIGHT_CTRL, pressed);
} else {
this->midiKey(2, pressed);
}
break;
}
}
// Handle a MIDI event.
//
// We act as a MIDI
void VailAdapter::HandleMIDI(midiEventPacket_t event) {
uint16_t msg = (event.byte1 << 8) | (event.byte2 << 0);
switch (event.byte1) {
case 0xB0: // Controller Change
switch (event.byte2) {
case 0: // turn keyboard mode on/off
this->keyboardMode = (event.byte3 > 0x3f);
MidiUSB.sendMIDI(event); // Send it back to acknowledge
break;
case 1: // set dit duration (0-254) *2ms
this->ditDuration = event.byte3 * 2 * MILLISECOND;
if (this->keyer) {
this->keyer->SetDitDuration(this->ditDuration);
}
break;
case 2: // set tx note
this->txNote = event.byte3;
break;
}
break;
case 0xC0: // Program Change
if (this->keyer) {
this->keyer->Release();
}
this->keyer = GetKeyerByNumber(event.byte2, this);
this->keyer->SetDitDuration(this->ditDuration);
break;
case 0x80: // Note off
this->buzzer->NoTone(1);
break;
case 0x90: // Note on
this->buzzer->Note(1, event.byte2);
break;
}
}
void VailAdapter::Tick(unsigned millis) {
if (this->keyer) {
this->keyer->Tick(millis);
}
}

View File

@ -1,27 +0,0 @@
#pragma once
#include <MIDIUSB.h>
#include "keyers.h"
#include "polybuzzer.h"
class VailAdapter: public Transmitter {
private:
unsigned int txNote = 69;
unsigned int ditDuration = 100;
bool keyboardMode = true;
Keyer *keyer = NULL;
PolyBuzzer *buzzer = NULL;
void midiKey(uint8_t key, bool down);
void keyboardKey(uint8_t key, bool down);
public:
VailAdapter(unsigned int PiezoPin);
bool KeyboardMode();
void HandlePaddle(Paddle key, bool pressed);
void HandleMIDI(midiEventPacket_t event);
void BeginTx();
void EndTx();
void Tick(unsigned millis);
};

View File

@ -1,15 +0,0 @@
#! /bin/sh
src=$1
dst=$2
info=$dst/INFO_UF2.txt
echo -n "Waiting for $info to appear..."
while ! [ -f $info ]; do
echo -n "."
sleep 1
done
echo "👍"
cp $src $dst
echo "Installed!"

View File

@ -47,31 +47,24 @@ public:
class StraightKeyer: public Keyer { class StraightKeyer: public Keyer {
public: public:
Transmitter *output; bool transmitting = false;
unsigned int ditDuration; unsigned int ditDuration = 100;
bool txRelays[2]; bool txRelays[2];
StraightKeyer() { StraightKeyer() {
this->Reset(); this->Reset();
} }
void SetOutput(Transmitter *output) { char *Name() {
this->output = output; return "straight";
}
void Reset() {
if (this->output) {
this->output->EndTx();
}
this->ditDuration = 100;
} }
void SetDitDuration(unsigned int duration) { void SetDitDuration(unsigned int duration) {
this->ditDuration = duration; this->ditDuration = duration;
} }
void Release() { void Reset() {
this->Reset(); this->transmitting = false;
} }
bool TxClosed() { bool TxClosed() {
@ -90,22 +83,16 @@ public:
void Tx(int relay, bool closed) { void Tx(int relay, bool closed) {
bool wasClosed = this->TxClosed(); bool wasClosed = this->TxClosed();
this->txRelays[relay] = closed; this->txRelays[relay] = closed;
bool nowClosed = this->TxClosed(); this->transmitting = this->TxClosed();
if (wasClosed != nowClosed) {
if (nowClosed) {
this->output->BeginTx();
} else {
this->output->EndTx();
}
}
} }
void Key(Paddle key, bool pressed) { void Key(Paddle key, bool pressed) {
this->Tx(key, pressed); this->Tx(key, pressed);
} }
void Tick(unsigned int millis) {}; bool Tick(unsigned long now) {
return this->transmitting;
}
}; };
class BugKeyer: public StraightKeyer { class BugKeyer: public StraightKeyer {
@ -115,6 +102,10 @@ public:
using StraightKeyer::StraightKeyer; using StraightKeyer::StraightKeyer;
char *Name() {
return "bug";
}
void Reset() { void Reset() {
StraightKeyer::Reset(); StraightKeyer::Reset();
this->nextPulse = 0; this->nextPulse = 0;
@ -131,10 +122,11 @@ public:
} }
} }
void Tick(unsigned int millis) { bool Tick(unsigned long now) {
if (this->nextPulse && (millis >= this->nextPulse)) { if (this->nextPulse && (now >= this->nextPulse)) {
this->pulse(millis); this->pulse(now);
} }
return this->transmitting;
} }
void beginPulsing() { void beginPulsing() {
@ -143,7 +135,7 @@ public:
} }
} }
virtual void pulse(unsigned int millis) { virtual void pulse(unsigned int now) {
if (this->TxClosed(0)) { if (this->TxClosed(0)) {
this->Tx(0, false); this->Tx(0, false);
} else if (this->keyPressed[0]) { } else if (this->keyPressed[0]) {
@ -152,7 +144,7 @@ public:
this->nextPulse = 0; this->nextPulse = 0;
return; return;
} }
this->nextPulse = millis + this->ditDuration; this->nextPulse = now + this->ditDuration;
} }
}; };
@ -162,6 +154,10 @@ public:
using BugKeyer::BugKeyer; using BugKeyer::BugKeyer;
char *Name() {
return "el bug";
}
void Reset() { void Reset() {
BugKeyer::Reset(); BugKeyer::Reset();
this->nextRepeat = -1; this->nextRepeat = -1;
@ -204,7 +200,7 @@ public:
return this->nextRepeat; return this->nextRepeat;
} }
virtual void pulse(unsigned int millis) { virtual void pulse(unsigned int now) {
int nextPulse = 0; int nextPulse = 0;
if (this->TxClosed(0)) { if (this->TxClosed(0)) {
// Pause if we're currently transmitting // Pause if we're currently transmitting
@ -219,7 +215,7 @@ public:
} }
if (nextPulse) { if (nextPulse) {
this->nextPulse = millis + nextPulse; this->nextPulse = now + nextPulse;
} else { } else {
this->nextPulse = 0; this->nextPulse = 0;
} }
@ -232,6 +228,10 @@ public:
using ElBugKeyer::ElBugKeyer; using ElBugKeyer::ElBugKeyer;
char *Name() {
return "ultimatic";
}
void Key(Paddle key, bool pressed) { void Key(Paddle key, bool pressed) {
if (pressed) { if (pressed) {
this->queue.add(key); this->queue.add(key);
@ -253,7 +253,11 @@ public:
QSet queue; QSet queue;
using ElBugKeyer::ElBugKeyer; using ElBugKeyer::ElBugKeyer;
char *Name() {
return "single dot";
}
void Key(Paddle key, bool pressed) { void Key(Paddle key, bool pressed) {
if (pressed && (key == PADDLE_DIT)) { if (pressed && (key == PADDLE_DIT)) {
this->queue.add(key); this->queue.add(key);
@ -274,9 +278,12 @@ public:
class IambicKeyer: public ElBugKeyer { class IambicKeyer: public ElBugKeyer {
public: public:
using ElBugKeyer::ElBugKeyer; using ElBugKeyer::ElBugKeyer;
char *Name() {
return "iambic plain";
}
virtual int nextTx() { virtual int nextTx() {
int next = ElBugKeyer::nextTx(); int next = ElBugKeyer::nextTx();
if (this->keyPressed[PADDLE_DIT] && this->keyPressed[PADDLE_DAH]) { if (this->keyPressed[PADDLE_DIT] && this->keyPressed[PADDLE_DAH]) {
@ -292,6 +299,10 @@ public:
using IambicKeyer::IambicKeyer; using IambicKeyer::IambicKeyer;
char *Name() {
return "iambic a";
}
void Key(Paddle key, bool pressed) { void Key(Paddle key, bool pressed) {
if (pressed && (key == PADDLE_DIT)) { if (pressed && (key == PADDLE_DIT)) {
this->queue.add(key); this->queue.add(key);
@ -312,15 +323,21 @@ public:
class IambicBKeyer: public IambicKeyer { class IambicBKeyer: public IambicKeyer {
public: public:
QSet queue; QSet queue;
int sending;
using IambicKeyer::IambicKeyer; using IambicKeyer::IambicKeyer;
char *Name() {
return "iambic b";
}
void Reset() { void Reset() {
this->sending = -1;
IambicKeyer::Reset(); IambicKeyer::Reset();
} }
void Key(Paddle key, bool pressed) { void Key(Paddle key, bool pressed) {
if (pressed) { if (pressed && (this->sending != key)) {
this->queue.add(key); this->queue.add(key);
} }
IambicKeyer::Key(key, pressed); IambicKeyer::Key(key, pressed);
@ -333,7 +350,8 @@ public:
} }
} }
return this->queue.shift(); this->sending = this->queue.shift();
return this->sending;
} }
}; };
@ -344,6 +362,10 @@ public:
using ElBugKeyer::ElBugKeyer; using ElBugKeyer::ElBugKeyer;
char *Name() {
return "keyahead";
}
void Reset() { void Reset() {
ElBugKeyer::Reset(); ElBugKeyer::Reset();
this->qlen = 0; this->qlen = 0;
@ -381,8 +403,7 @@ IambicAKeyer iambicAKeyer = IambicAKeyer();
IambicBKeyer iambicBKeyer = IambicBKeyer(); IambicBKeyer iambicBKeyer = IambicBKeyer();
KeyaheadKeyer keyaheadKeyer = KeyaheadKeyer(); KeyaheadKeyer keyaheadKeyer = KeyaheadKeyer();
Keyer *keyers[] = { Keyer *AllKeyers[] = {
NULL,
&straightKeyer, &straightKeyer,
&bugKeyer, &bugKeyer,
&elBugKeyer, &elBugKeyer,
@ -394,12 +415,10 @@ Keyer *keyers[] = {
&keyaheadKeyer, &keyaheadKeyer,
}; };
Keyer *GetKeyerByNumber(int n, Transmitter *output) { Keyer *GetKeyerByNumber(int n) {
if (n >= len(keyers)) { if (n >= len(AllKeyers)) {
return NULL; return NULL;
} }
Keyer *k = keyers[n]; return AllKeyers[n];
k->SetOutput(output); }
return k;
}

View File

@ -5,26 +5,18 @@
typedef enum { typedef enum {
PADDLE_DIT = 0, PADDLE_DIT = 0,
PADDLE_DAH = 1, PADDLE_DAH = 1,
PADDLE_STRAIGHT,
} Paddle; } Paddle;
class Transmitter {
public:
virtual void BeginTx();
virtual void EndTx();
};
class Keyer { class Keyer {
public: public:
virtual void SetOutput(Transmitter *output); virtual char *Name() = 0;
virtual void Reset(); virtual void Reset() = 0;
virtual void SetDitDuration(unsigned int d); virtual void SetDitDuration(unsigned int d) = 0;
virtual void Release(); virtual void Key(Paddle key, bool pressed) = 0;
virtual bool TxClosed();
virtual bool TxClosed(int relay); // Tick updates internal state,
virtual void Tx(int relay, bool closed); // and returns whether the keyer is transmitting at time now.
virtual void Key(Paddle key, bool pressed); virtual bool Tick(unsigned long now) = 0;
virtual void Tick(unsigned int millis);
}; };
Keyer *GetKeyerByNumber(int n, Transmitter *output); Keyer *GetKeyerByNumber(int n);

View File

@ -1,11 +0,0 @@
#include "touchbounce.h"
void TouchBounce::attach(int pin) {
this->qt = Adafruit_FreeTouch(pin, OVERSAMPLE_2, RESISTOR_0, FREQ_MODE_SPREAD);
this->qt.begin();
}
bool TouchBounce::readCurrentState() {
int val = this->qt.measure();
return val > QT_THRESHOLD;
}

View File

@ -1,16 +0,0 @@
#pragma once
#include <Adafruit_FreeTouch.h>
#include "bounce2.h"
#define QT_THRESHOLD 450
class TouchBounce: public Bounce {
public:
// attach a touch pin
void attach(int pin);
protected:
bool readCurrentState();
Adafruit_FreeTouch qt;
};

View File

@ -2,119 +2,164 @@
// Distributed under the MIT license // Distributed under the MIT license
// Please see https://github.com/nealey/vail-adapter/ // Please see https://github.com/nealey/vail-adapter/
// MIDIUSB - Version: Latest #include <Arduino.h>
#include <MIDIUSB.h> #include <Adafruit_NeoPixel.h>
#include <Keyboard.h> #include <Adafruit_GFX.h>
#include <Adafruit_FreeTouch.h> #include <Adafruit_SSD1306.h>
#include <Fonts/FreeSans9pt7b.h>
#include "bounce2.h" #include "bounce2.h"
#include "touchbounce.h" #include "keyers.h"
#include "adapter.h" #include "polybuzzer.h"
#define DIT_PIN 2
#define DAH_PIN 1
#define KEY_PIN 0
#define QT_DIT_PIN A6
#define QT_DAH_PIN A7
#define QT_KEY_PIN A8
#define PIEZO 10
#define LED_ON false // Xiao inverts this logic for some reason
#define LED_OFF (!LED_ON)
#define DIT_KEYBOARD_KEY KEY_LEFT_CTRL
#define DAH_KEYBOARD_KEY KEY_RIGHT_CTRL
#define TONE 3000
#define DIT_PIN 1
#define DAH_PIN 3
#define S1_PIN 4
#define S2_PIN 9
#define SPEAKER 7
#define SOUNDER 8
#define MILLISECOND 1 #define MILLISECOND 1
#define SECOND (1 * MILLISECOND) #define SECOND (1 * MILLISECOND)
bool trs = false; // true if a TRS plug is in a TRRS jack
uint16_t iambicDelay = 80 * MILLISECOND; uint16_t iambicDelay = 80 * MILLISECOND;
Bounce dit = Bounce(); Bounce dit = Bounce();
Bounce dah = Bounce(); Bounce dah = Bounce();
Bounce key = Bounce(); Bounce s1 = Bounce();
TouchBounce qt_dit = TouchBounce(); Bounce s2 = Bounce();
TouchBounce qt_dah = TouchBounce(); PolyBuzzer buzzer = PolyBuzzer(SPEAKER);
TouchBounce qt_key = TouchBounce();
VailAdapter adapter = VailAdapter(PIEZO); Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 display(128, 32, &Wire, -1);
// Tones default to a minor third
unsigned int txNote = 72; // C5
unsigned int rxNote = 69; // A4
int keyerCurrentN = 0;
Keyer *keyerCurrent = NULL;
void led(uint32_t color = 0) {
pixels.fill(color);
pixels.show();
}
// A reentrant doodad to blink out the letter V at startup
#define HELLO_BITS 0b0000101010111000
void hello(unsigned long now) {
static bool beeping = false;
int beat = now / iambicDelay;
if (beat < 16) {
bool on = HELLO_BITS & (1 << (15-beat));
if (on != beeping) {
if (on) {
buzzer.Note(0, rxNote);
led(0x220000);
} else {
buzzer.NoTone(0);
led(0);
}
beeping = on;
}
}
}
int loadKeyer(int n) {
keyerCurrent = GetKeyerByNumber(n);
if (NULL == keyerCurrent) {
n = 0;
keyerCurrent = GetKeyerByNumber(n);
}
display.clearDisplay();
display.setCursor(1, 12);
display.println(keyerCurrent->Name());
display.display();
return n;
}
void
void setup() { void setup() {
pinMode(LED_BUILTIN, OUTPUT);
dit.attach(DIT_PIN, INPUT_PULLUP); dit.attach(DIT_PIN, INPUT_PULLUP);
dah.attach(DAH_PIN, INPUT_PULLUP); dah.attach(DAH_PIN, INPUT_PULLUP);
key.attach(KEY_PIN, INPUT_PULLUP); s1.attach(S1_PIN, INPUT_PULLUP);
qt_dit.attach(QT_DIT_PIN); s2.attach(S2_PIN, INPUT_PULLUP);
qt_dah.attach(QT_DAH_PIN); pinMode(SOUNDER, OUTPUT);
qt_key.attach(QT_KEY_PIN);
Keyboard.begin(); Serial.begin(115200);
Serial.println("Vail Client / " __DATE__ " " __TIME__);
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.setBrightness(20); // not so bright
Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC); // This never returns false, in my testing
display.setFont(&FreeSans9pt7b);
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
display.setCursor(1, 12);
display.println(__DATE__);
display.display();
// To auto-sense a straight key in a TRRS jack, // To auto-sense a straight key in a TRRS jack,
// we just check to see if DAH is closed. // check to see if DAH is closed.
// The sleeve on the straight key's TRS plug // The sleeve on the straight key's TRS plug
// will short the second ring to the sleeve. // will short the second ring to the sleeve.
// In this case, set the keyer to straight.
// Since keyers are edge triggered,
// nothing will happen if the key stays closed.
// If it opens, it falls back to being a straight key.
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
hello(millis());
delay(20); delay(20);
dah.update(); dah.update();
} }
if (dah.read() == LOW) { if (dah.read() == LOW) {
trs = true; loadKeyer(0); // Straight
key = dit; } else {
loadKeyer(7); // Iambic B
} }
} }
// A reentrant doodad to blink out the letter V at startup
// After startup, display the status of the keyboard
#define HELLO_BITS 0b0000101010111000
void setLED() {
static bool beepin = false;
int beat = millis() / iambicDelay;
bool on = adapter.KeyboardMode(); // If we're not in intro, display status of keyboard
if (beat < 16) {
on = HELLO_BITS & (1 << (15-beat));
if (on != beepin) {
if (on) {
tone(PIEZO, TONE);
} else {
noTone(PIEZO);
}
beepin = on;
}
}
digitalWrite(LED_BUILTIN, on?LED_ON:LED_OFF);
}
void loop() { void loop() {
unsigned now = millis(); static unsigned long txBegin = 0;
midiEventPacket_t event = MidiUSB.read(); unsigned long now = millis();
setLED(); hello(now);
adapter.Tick(now);
if (event.header) { s1.update();
adapter.HandleMIDI(event); s2.update();
if (s1.fell() || s2.fell()) {
keyerCurrentN = loadKeyer(keyerCurrentN + 1);
// XXX: menu
} }
// Monitor straight key pin if (dit.update()) {
if (key.update() || qt_key.update()) { keyerCurrent->Key(PADDLE_DIT, !dit.read());
bool pressed = !key.read() || qt_key.read();
adapter.HandlePaddle(PADDLE_STRAIGHT, pressed);
}
// If we made dit = dah, we have a straight key on the dit pin,
// so we skip other keys polling.
if (trs) {
return;
}
if (dit.update() || qt_dit.update()) {
bool pressed = !dit.read() || qt_dit.read();
adapter.HandlePaddle(PADDLE_DIT, pressed);
} }
if (dah.update() || qt_dah.update()) { if (dah.update()) {
bool pressed = !dah.read() || qt_dah.read(); keyerCurrent->Key(PADDLE_DAH, !dah.read());
adapter.HandlePaddle(PADDLE_DAH, pressed); }
bool sounding = keyerCurrent->Tick(now);
if (sounding) {
if (!txBegin) {
txBegin = now;
buzzer.Note(0, txNote);
digitalWrite(SOUNDER, HIGH);
led(0xff0000);
}
} else {
if (txBegin) {
Serial.printf("%d %d\n", txBegin, now - txBegin);
txBegin = 0;
buzzer.NoTone(0);
digitalWrite(SOUNDER, LOW);
led();
}
} }
} }