From e7cfde10926c2ee2275d7b0b78233a4a9e8d4eae Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 20 Oct 2024 17:51:51 -0600 Subject: [PATCH] Revert "Begin work on standalone websocket client" This reverts commit 38cf3f535d26e55e8258adbbc53c8ee800fe757a. --- .clangd | 10 ++ .vscode/settings.json | 5 + adapter.cpp | 132 ++++++++++++++++++++++++++ adapter.h | 27 ++++++ install.sh | 15 +++ keyers.cpp | 107 +++++++++------------ keyers.h | 26 ++++-- touchbounce.cpp | 11 +++ touchbounce.h | 16 ++++ vail-adapter.ino | 209 +++++++++++++++++------------------------- 10 files changed, 359 insertions(+), 199 deletions(-) create mode 100644 .clangd create mode 100644 .vscode/settings.json create mode 100644 adapter.cpp create mode 100644 adapter.h create mode 100755 install.sh create mode 100644 touchbounce.cpp create mode 100644 touchbounce.h diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..882b8cd --- /dev/null +++ b/.clangd @@ -0,0 +1,10 @@ +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" + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84a82d3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "clangd.arguments": [ + "--enable-config" + ] +} \ No newline at end of file diff --git a/adapter.cpp b/adapter.cpp new file mode 100644 index 0000000..a53946f --- /dev/null +++ b/adapter.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#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); + } +} \ No newline at end of file diff --git a/adapter.h b/adapter.h new file mode 100644 index 0000000..97cdc9c --- /dev/null +++ b/adapter.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#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); +}; diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..3ca4683 --- /dev/null +++ b/install.sh @@ -0,0 +1,15 @@ +#! /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!" diff --git a/keyers.cpp b/keyers.cpp index b955925..857de1c 100644 --- a/keyers.cpp +++ b/keyers.cpp @@ -47,24 +47,31 @@ public: class StraightKeyer: public Keyer { public: - bool transmitting = false; - unsigned int ditDuration = 100; + Transmitter *output; + unsigned int ditDuration; bool txRelays[2]; StraightKeyer() { this->Reset(); } - char *Name() { - return "straight"; + void SetOutput(Transmitter *output) { + this->output = output; + } + + void Reset() { + if (this->output) { + this->output->EndTx(); + } + this->ditDuration = 100; } void SetDitDuration(unsigned int duration) { this->ditDuration = duration; } - void Reset() { - this->transmitting = false; + void Release() { + this->Reset(); } bool TxClosed() { @@ -83,16 +90,22 @@ public: void Tx(int relay, bool closed) { bool wasClosed = this->TxClosed(); this->txRelays[relay] = closed; - this->transmitting = this->TxClosed(); + bool nowClosed = this->TxClosed(); + + if (wasClosed != nowClosed) { + if (nowClosed) { + this->output->BeginTx(); + } else { + this->output->EndTx(); + } + } } void Key(Paddle key, bool pressed) { this->Tx(key, pressed); } - bool Tick(unsigned long now) { - return this->transmitting; - } + void Tick(unsigned int millis) {}; }; class BugKeyer: public StraightKeyer { @@ -102,10 +115,6 @@ public: using StraightKeyer::StraightKeyer; - char *Name() { - return "bug"; - } - void Reset() { StraightKeyer::Reset(); this->nextPulse = 0; @@ -122,11 +131,10 @@ public: } } - bool Tick(unsigned long now) { - if (this->nextPulse && (now >= this->nextPulse)) { - this->pulse(now); + void Tick(unsigned int millis) { + if (this->nextPulse && (millis >= this->nextPulse)) { + this->pulse(millis); } - return this->transmitting; } void beginPulsing() { @@ -135,7 +143,7 @@ public: } } - virtual void pulse(unsigned int now) { + virtual void pulse(unsigned int millis) { if (this->TxClosed(0)) { this->Tx(0, false); } else if (this->keyPressed[0]) { @@ -144,7 +152,7 @@ public: this->nextPulse = 0; return; } - this->nextPulse = now + this->ditDuration; + this->nextPulse = millis + this->ditDuration; } }; @@ -154,10 +162,6 @@ public: using BugKeyer::BugKeyer; - char *Name() { - return "el bug"; - } - void Reset() { BugKeyer::Reset(); this->nextRepeat = -1; @@ -200,7 +204,7 @@ public: return this->nextRepeat; } - virtual void pulse(unsigned int now) { + virtual void pulse(unsigned int millis) { int nextPulse = 0; if (this->TxClosed(0)) { // Pause if we're currently transmitting @@ -215,7 +219,7 @@ public: } if (nextPulse) { - this->nextPulse = now + nextPulse; + this->nextPulse = millis + nextPulse; } else { this->nextPulse = 0; } @@ -228,10 +232,6 @@ public: using ElBugKeyer::ElBugKeyer; - char *Name() { - return "ultimatic"; - } - void Key(Paddle key, bool pressed) { if (pressed) { this->queue.add(key); @@ -253,11 +253,7 @@ public: QSet queue; using ElBugKeyer::ElBugKeyer; - - char *Name() { - return "single dot"; - } - + void Key(Paddle key, bool pressed) { if (pressed && (key == PADDLE_DIT)) { this->queue.add(key); @@ -278,11 +274,8 @@ public: class IambicKeyer: public ElBugKeyer { public: - using ElBugKeyer::ElBugKeyer; - char *Name() { - return "iambic plain"; - } + using ElBugKeyer::ElBugKeyer; virtual int nextTx() { int next = ElBugKeyer::nextTx(); @@ -299,10 +292,6 @@ public: using IambicKeyer::IambicKeyer; - char *Name() { - return "iambic a"; - } - void Key(Paddle key, bool pressed) { if (pressed && (key == PADDLE_DIT)) { this->queue.add(key); @@ -323,21 +312,15 @@ public: class IambicBKeyer: public IambicKeyer { public: QSet queue; - int sending; using IambicKeyer::IambicKeyer; - char *Name() { - return "iambic b"; - } - void Reset() { - this->sending = -1; IambicKeyer::Reset(); } void Key(Paddle key, bool pressed) { - if (pressed && (this->sending != key)) { + if (pressed) { this->queue.add(key); } IambicKeyer::Key(key, pressed); @@ -350,8 +333,7 @@ public: } } - this->sending = this->queue.shift(); - return this->sending; + return this->queue.shift(); } }; @@ -362,10 +344,6 @@ public: using ElBugKeyer::ElBugKeyer; - char *Name() { - return "keyahead"; - } - void Reset() { ElBugKeyer::Reset(); this->qlen = 0; @@ -403,7 +381,8 @@ IambicAKeyer iambicAKeyer = IambicAKeyer(); IambicBKeyer iambicBKeyer = IambicBKeyer(); KeyaheadKeyer keyaheadKeyer = KeyaheadKeyer(); -Keyer *AllKeyers[] = { +Keyer *keyers[] = { + NULL, &straightKeyer, &bugKeyer, &elBugKeyer, @@ -415,10 +394,12 @@ Keyer *AllKeyers[] = { &keyaheadKeyer, }; -Keyer *GetKeyerByNumber(int n) { - if (n >= len(AllKeyers)) { - return NULL; - } +Keyer *GetKeyerByNumber(int n, Transmitter *output) { + if (n >= len(keyers)) { + return NULL; + } - return AllKeyers[n]; -} \ No newline at end of file + Keyer *k = keyers[n]; + k->SetOutput(output); + return k; +} diff --git a/keyers.h b/keyers.h index b9f398a..956049f 100644 --- a/keyers.h +++ b/keyers.h @@ -5,18 +5,26 @@ typedef enum { PADDLE_DIT = 0, PADDLE_DAH = 1, + PADDLE_STRAIGHT, } Paddle; +class Transmitter { +public: + virtual void BeginTx(); + virtual void EndTx(); +}; + class Keyer { public: - virtual char *Name() = 0; - virtual void Reset() = 0; - virtual void SetDitDuration(unsigned int d) = 0; - virtual void Key(Paddle key, bool pressed) = 0; - - // Tick updates internal state, - // and returns whether the keyer is transmitting at time now. - virtual bool Tick(unsigned long now) = 0; + virtual void SetOutput(Transmitter *output); + virtual void Reset(); + virtual void SetDitDuration(unsigned int d); + virtual void Release(); + virtual bool TxClosed(); + virtual bool TxClosed(int relay); + virtual void Tx(int relay, bool closed); + virtual void Key(Paddle key, bool pressed); + virtual void Tick(unsigned int millis); }; -Keyer *GetKeyerByNumber(int n); \ No newline at end of file +Keyer *GetKeyerByNumber(int n, Transmitter *output); diff --git a/touchbounce.cpp b/touchbounce.cpp new file mode 100644 index 0000000..1baee42 --- /dev/null +++ b/touchbounce.cpp @@ -0,0 +1,11 @@ +#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; +} diff --git a/touchbounce.h b/touchbounce.h new file mode 100644 index 0000000..e4c1d1e --- /dev/null +++ b/touchbounce.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#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; +}; diff --git a/vail-adapter.ino b/vail-adapter.ino index 643a927..9cd6581 100644 --- a/vail-adapter.ino +++ b/vail-adapter.ino @@ -2,164 +2,119 @@ // Distributed under the MIT license // Please see https://github.com/nealey/vail-adapter/ -#include -#include -#include -#include -#include +// MIDIUSB - Version: Latest +#include +#include +#include #include "bounce2.h" -#include "keyers.h" -#include "polybuzzer.h" +#include "touchbounce.h" +#include "adapter.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 SECOND (1 * MILLISECOND) +bool trs = false; // true if a TRS plug is in a TRRS jack uint16_t iambicDelay = 80 * MILLISECOND; Bounce dit = Bounce(); Bounce dah = Bounce(); -Bounce s1 = Bounce(); -Bounce s2 = Bounce(); -PolyBuzzer buzzer = PolyBuzzer(SPEAKER); - -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 +Bounce key = Bounce(); +TouchBounce qt_dit = TouchBounce(); +TouchBounce qt_dah = TouchBounce(); +TouchBounce qt_key = TouchBounce(); +VailAdapter adapter = VailAdapter(PIEZO); void setup() { + pinMode(LED_BUILTIN, OUTPUT); dit.attach(DIT_PIN, INPUT_PULLUP); dah.attach(DAH_PIN, INPUT_PULLUP); - s1.attach(S1_PIN, INPUT_PULLUP); - s2.attach(S2_PIN, INPUT_PULLUP); - pinMode(SOUNDER, OUTPUT); + key.attach(KEY_PIN, INPUT_PULLUP); + qt_dit.attach(QT_DIT_PIN); + qt_dah.attach(QT_DAH_PIN); + qt_key.attach(QT_KEY_PIN); - 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(); + Keyboard.begin(); // To auto-sense a straight key in a TRRS jack, - // check to see if DAH is closed. + // we just check to see if DAH is closed. // The sleeve on the straight key's TRS plug // 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++) { - hello(millis()); delay(20); dah.update(); } if (dah.read() == LOW) { - loadKeyer(0); // Straight - } else { - loadKeyer(7); // Iambic B + trs = true; + key = dit; } } +// 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() { - static unsigned long txBegin = 0; - unsigned long now = millis(); + unsigned now = millis(); + midiEventPacket_t event = MidiUSB.read(); - hello(now); + setLED(); + adapter.Tick(now); - s1.update(); - s2.update(); - if (s1.fell() || s2.fell()) { - keyerCurrentN = loadKeyer(keyerCurrentN + 1); - // XXX: menu + if (event.header) { + adapter.HandleMIDI(event); } - if (dit.update()) { - keyerCurrent->Key(PADDLE_DIT, !dit.read()); + // Monitor straight key pin + if (key.update() || qt_key.update()) { + 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()) { - keyerCurrent->Key(PADDLE_DAH, !dah.read()); - } - - 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(); - } + if (dah.update() || qt_dah.update()) { + bool pressed = !dah.read() || qt_dah.read(); + adapter.HandlePaddle(PADDLE_DAH, pressed); } }