From 39779713415f553833cc20805401087171900b2a Mon Sep 17 00:00:00 2001 From: mattbk Date: Wed, 30 Aug 2023 22:04:32 -0500 Subject: [PATCH] Get basic Morse LED control working with JLed. --- vulpes/platformio.ini | 4 +- vulpes/src/README.md | 17 ++++++ vulpes/src/bitset.h | 53 ++++++++++++++++++ vulpes/src/main.cpp | 67 ++++++++++++++++++++++- vulpes/src/morse.h | 121 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 259 insertions(+), 3 deletions(-) create mode 100755 vulpes/src/README.md create mode 100755 vulpes/src/bitset.h create mode 100755 vulpes/src/morse.h diff --git a/vulpes/platformio.ini b/vulpes/platformio.ini index 0b3a327..e2f760c 100644 --- a/vulpes/platformio.ini +++ b/vulpes/platformio.ini @@ -17,4 +17,6 @@ lib_deps = me-no-dev/AsyncTCP@^1.1.1 me-no-dev/ESP Async WebServer@^1.2.3 contrem/arduino-timer@^3.0.1 - kj7rrv/Telegraph@^1.0.0 + ;kj7rrv/Telegraph@^1.0.0 + ;etherkit/Etherkit Morse@^1.1.2 + jandelgado/JLed@^4.13.0 diff --git a/vulpes/src/README.md b/vulpes/src/README.md new file mode 100755 index 0000000..96998cb --- /dev/null +++ b/vulpes/src/README.md @@ -0,0 +1,17 @@ +# JLed morse example + +This examples demonstrates an efficient method to generate morse code on +an micro controller like the Arduino. + +The morse example uses the morse alphabet encoded in a binary tree to +generate morse code using a JLed user defined brightness class. The text +to be morsed is transformed into morse code and then transformed into a +sequence of `1` and `0` which are written out to a GPIO controlling a LED or +a sound generator. + +![morse example](../../doc/morse.jpg) + +## Author + +Jan Delgado + diff --git a/vulpes/src/bitset.h b/vulpes/src/bitset.h new file mode 100755 index 0000000..5665095 --- /dev/null +++ b/vulpes/src/bitset.h @@ -0,0 +1,53 @@ +// Copyright (c) 2019 Jan Delgado +// https://github.com/jandelgado/jled + +#ifndef EXAMPLES_MORSE_BITSET_H_ +#define EXAMPLES_MORSE_BITSET_H_ + +// a simple bit set with capacity of N bits, just enough for the morse demo +class Bitset { + private: + size_t n_; + uint8_t* bits_; + + protected: + // returns num bytes needed to store n bits. + static constexpr size_t num_bytes(size_t n) { + return n > 0 ? ((n - 1) >> 3) + 1 : 0; + } + + public: + Bitset() : Bitset(0) {} + + Bitset(const Bitset& b) : Bitset() { *this = b; } + + explicit Bitset(size_t n) : n_(n), bits_{new uint8_t[num_bytes(n)]} { + memset(bits_, 0, num_bytes(n_)); + } + + Bitset& operator=(const Bitset& b) { + if (&b == this) return *this; + const auto size_new = num_bytes(b.n_); + if (num_bytes(n_) != size_new) { + delete[] bits_; + bits_ = new uint8_t[size_new]; + n_ = b.n_; + } + memcpy(bits_, b.bits_, size_new); + return *this; + } + + virtual ~Bitset() { + delete[] bits_; + bits_ = nullptr; + } + void set(size_t i, bool val) { + if (val) + bits_[i >> 3] |= (1 << (i & 7)); + else + bits_[i >> 3] &= ~(1 << (i & 7)); + } + bool test(size_t i) const { return (bits_[i >> 3] & (1 << (i & 7))) != 0; } + size_t size() const { return n_; } +}; +#endif // EXAMPLES_MORSE_BITSET_H_ diff --git a/vulpes/src/main.cpp b/vulpes/src/main.cpp index fb619eb..a02afdc 100644 --- a/vulpes/src/main.cpp +++ b/vulpes/src/main.cpp @@ -18,6 +18,11 @@ #include #include #include +//#include //arduino morse +//#include //etherkit morse +#include // jled +#include "morse.h" //jled +//#include "morse_effect.h" // jled // download zip from https://github.com/me-no-dev/ESPAsyncWebServer and install. #include @@ -201,12 +206,41 @@ void k(){ // ^^^^ -Telegraph telegraph(LED_BUILTIN, 10, HIGH); +//telegraph +//Telegraph telegraph(LED_BUILTIN, 10, HIGH); +Telegraph telegraph26(output26, 10, HIGH); +//arduinomorse +//LEDMorseSender sender(LED_BUILTIN); +//Etherkit Morse +//Morse morse(LED_BUILTIN, 15); +//jled from https://github.com/jandelgado/jled/blob/master/examples/morse/morse_effect.h +class MorseEffect : public jled::BrightnessEvaluator { + Morse morse_; + // duration of a single 'dit' in ms + const uint16_t speed_; + public: + explicit MorseEffect(const char* message, uint16_t speed = 200) + : morse_(message), speed_(speed) {} + uint8_t Eval(uint32_t t) const override { + const auto pos = t / speed_; + if (pos >= morse_.size()) return 0; + return morse_.test(pos) ? 255 : 0; + } + + uint16_t Period() const override { return (morse_.size() + 1) * speed_; } +}; + +// Speed is milliseconds per dit, which is 1000 * (60 / (50 * WPM)) +// 60 is 20 wpm, 120 is 10 wpm, 90 is 15 wpm, etc. +// https://morsecode.world/international/timing.html +MorseEffect morseEffect("CQ CQ CQ DE W1CDN", 120); +auto morseLed = + JLed(output26).UserFunc(&morseEffect).DelayAfter(2000).Forever(); //================================================================================ // setup(): stuff that only gets done once, after power up (KB1OIQ's description) @@ -293,7 +327,16 @@ void setup() { server.onNotFound(notFound); server.begin(); - telegraph.send("CQ CQ CQ"); + //telegraph + //telegraph.send("CQ CQ CQ"); + //telegraph26.send("CQ CQ CQ DE W1CDN K"); + + // arduinomorse + // sender.setup(); + // sender.setMessage(String("73 de kb3jcy ")); + // sender.startSending(); + + } @@ -302,11 +345,31 @@ void loop() { time_until_start.tick(); timer.tick(); + //arduinomorse + //sender.continueSending(); String yourInputString = readFile(SPIFFS, "/inputString.txt"); int yourInputInt = readFile(SPIFFS, "/inputInt.txt").toInt(); // float yourInputFloat = readFile(SPIFFS, "/inputFloat.txt").toFloat(); + // if you want to send code, and it's not sending, then start it up + if(yourInputInt != 0 & morseLed.IsRunning() == false){ + //jled + morseLed.Reset().Update(); + //morse.send("CQ CQ CQ DE W1CDN K"); //etherkit morse + //telegraph26.send("CQ CQ CQ DE W1CDN K"); //telegraph + + // if you want to send code, and it is sending, keep sending + } else if(yourInputInt != 0 & morseLed.IsRunning() == true){ + morseLed.Update(); + // if you don't want to send code + } else { + // stop sending and make sure the pin is off + morseLed.Stop(JLed::eStopMode::FULL_OFF).Update(); + } + + + // Blink LED according to seconds entered // if (yourInputInt > 0) { // Serial.println("GPIO 26 on"); diff --git a/vulpes/src/morse.h b/vulpes/src/morse.h new file mode 100755 index 0000000..8d4fe27 --- /dev/null +++ b/vulpes/src/morse.h @@ -0,0 +1,121 @@ +// Copyright (c) 2019 Jan Delgado +// https://github.com/jandelgado/jled +#include +#include +#include +#include "bitset.h" // NOLINT + +#ifndef EXAMPLES_MORSE_MORSE_H_ +#define EXAMPLES_MORSE_MORSE_H_ + +// The Morse class converts a text sequence into a bit sequence representing +// a morse code sequence. +class Morse { + // pre-ordered tree of morse codes. Bit 1 = 'dah', 0 = 'dit'. + // Position in string corresponds to position in binary tree starting w/ 1 + // see https://www.pocketmagic.net/morse-encoder/ for info on encoding + static constexpr auto LATIN = + "*ETIANMSURWDKGOHVF*L*PJBXCYZQ**54*3***2*******16*******7***8*90"; + + static constexpr auto DURATION_DIT = 1; + static constexpr auto DURATION_DAH = 3 * DURATION_DIT; + static constexpr auto DURATION_PAUSE_CHAR = DURATION_DAH; + static constexpr auto DURATION_PAUSE_WORD = 7 * DURATION_DIT; + + protected: + char upper(char c) const { return c >= 'a' && c <= 'z' ? c - 32 : c; } + bool isspace(char c) const { return c == ' '; } + + // returns position of char in morse tree. Count starts with 1, i.e. + // E=2, T=3, etc. + size_t treepos(char c) const { + auto i = 1u; + while (LATIN[i++] != c) { + } + return i; + } + + // returns uint16_t with size of morse sequence (dit's and dah's) in MSB + // and the morse sequence in the LSB + uint16_t pos_to_morse_code(int code) const { + uint8_t res = 0; + uint8_t size = 0; + while (code > 1) { + size++; + res <<= 1; + res |= (code & 1); + code >>= 1; + } + return res | (size << 8); + } + + template + uint16_t iterate_sequence(const char* p, F f) const { + // call f(count,val) num times, incrementing count each time + // and returning num afterwards. + auto set = [](int num, int count, bool val, F f) -> int { + for (auto i = 0; i < num; i++) f(count + i, val); + return num; + }; + + auto bitcount = 0; + while (*p) { + const auto c = upper(*p++); + if (isspace(c)) { // space not part of alphabet, treat separately + bitcount += set(DURATION_PAUSE_WORD, bitcount, false, f); + continue; + } + + const auto morse_code = pos_to_morse_code(treepos(upper(c))); + auto code = morse_code & 0xff; // dits (0) and dahs (1) + auto size = morse_code >> 8; // number of dits and dahs in code + while (size--) { + bitcount += set((code & 1) ? DURATION_DAH : DURATION_DIT, + bitcount, true, f); + + // pause between symbols := 1 dit + if (size) { + bitcount += set(DURATION_DIT, bitcount, false, f); + } + code >>= 1; + } + + if (*p && !isspace(*p)) { + bitcount += set(DURATION_PAUSE_CHAR, bitcount, false, f); + } + } + return bitcount; + } + + public: + // returns ith bit of morse sequence + bool test(uint16_t i) const { return bits_->test(i); } + + // length of complete morse sequence in in bits + size_t size() const { return bits_->size(); } + + Morse() : bits_(new Bitset(0)) {} + + explicit Morse(const char* s) { + const auto length = iterate_sequence(s, [](int, bool) -> void {}); + auto bits = new Bitset(length); + iterate_sequence(s, [bits](int i, bool v) -> void { bits->set(i, v); }); + bits_ = bits; + } + + ~Morse() { delete bits_; } + + // make sure that the following, currently not needed, methods are not used + Morse(const Morse&m) {*this = m;} + Morse& operator=(const Morse&m) { + delete bits_; + bits_ = new Bitset(*m.bits_); + return *this; + } + + private: + // stores morse bit sequence + const Bitset* bits_ = nullptr; +}; + +#endif // EXAMPLES_MORSE_MORSE_H_