diff --git a/vulpes/platformio.ini b/vulpes/platformio.ini index a1de28d..722236a 100644 --- a/vulpes/platformio.ini +++ b/vulpes/platformio.ini @@ -10,9 +10,6 @@ [env:esp32doit-devkit-v1] platform = espressif32 -;build_flags = -; -std=c++11 -; -std=gnu++11 board = esp32doit-devkit-v1 framework = arduino upload_speed = 921600 @@ -23,10 +20,7 @@ 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 jandelgado/JLed@^4.13.1 - ;adafruit/RTClib@^2.1.1 - https://github.com/adafruit/RTClib.git ; >=2.1.2 + https://github.com/adafruit/RTClib.git adafruit/Adafruit BusIO@^1.14.3 - ;jchristensen/DS3232RTC@^2.0.1 - + erropix/ESP32 AnalogWrite@^0.2 diff --git a/vulpes/src/jled/README.md b/vulpes/src/jled/README.md deleted file mode 100755 index 96998cb..0000000 --- a/vulpes/src/jled/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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/jled/bitset.h b/vulpes/src/jled/bitset.h deleted file mode 100755 index 5665095..0000000 --- a/vulpes/src/jled/bitset.h +++ /dev/null @@ -1,53 +0,0 @@ -// 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/jled/morse.h b/vulpes/src/jled/morse.h deleted file mode 100755 index 8d4fe27..0000000 --- a/vulpes/src/jled/morse.h +++ /dev/null @@ -1,121 +0,0 @@ -// 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_ diff --git a/vulpes/src/main.cpp b/vulpes/src/main.cpp index 18538d5..4b27559 100644 --- a/vulpes/src/main.cpp +++ b/vulpes/src/main.cpp @@ -16,9 +16,7 @@ #include #include #include -#include -#include // jled -#include "jled/morse.h" //jled +#include "morse.h" #include // for DS3231 #include // for DS3231 #include @@ -28,6 +26,10 @@ AsyncWebServer server(80); +// Assign output variables to GPIO pins +const int keyer = 32; //LED_BUILTIN for on-board (dev);//26 for LED; //32 for transmitter keyer +const int blinker = LED_BUILTIN; + RTC_DS3231 rtc; // set up RTC const int alarmPin = 4; // pin to monitor for RTC alarms @@ -61,9 +63,12 @@ bool programRunning; int yourInputStepLength; int yourInputCycleID; int yourInputNtransmitters; -int step_length = 10000; // 10 secs -int cycle_id = 1; // number of this transmitter in cycle -int n_transmitters = 2; //number of transmitters total +//int step_length = 10000; // 10 secs +//int cycle_id = 1; // number of this transmitter in cycle +//int n_transmitters = 2; //number of transmitters total +long start_millis = 0; +long stop_millis = 0; +long pause_until_millis = 0; // HTML web page to handle 3 input fields (inputString, inputSend, inputFloat) const char index_html[] PROGMEM = R"rawliteral( @@ -166,21 +171,6 @@ const char index_html[] PROGMEM = R"rawliteral( )rawliteral"; -// Assign output variables to GPIO pins -const int keyer = 32; //LED_BUILTIN for on-board (dev);//26 for LED; //32 for transmitter keyer -const int blinker = LED_BUILTIN; - -// Timers -//auto timer = timer_create_default(); -Timer<1> timer; - -// Example from https://github.com/contrem/arduino-timer#examples -bool toggle_led(void *) { - //Serial.print("Timer time: "); - //Serial.println(rtc.now().timestamp()); - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // keep timer active? true -} void notFound(AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); @@ -255,24 +245,10 @@ String processor(const String& var){ return String(); } -//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_; } -}; +// Set up arduinomorse pin and default WPM +LEDMorseSender sender(blinker, 10.0f) ; // the 'f' makes sure this is a float +// TODO also for keyer once blinker works // Speed is milliseconds per dit, which is 1000 * (60 / (50 * WPM)) // 60 is 20 wpm, 120 is 10 wpm, 90 is 15 wpm, etc. @@ -280,97 +256,6 @@ class MorseEffect : public jled::BrightnessEvaluator { float wpm = 10; float ms_per_dit = 1000 * (60 / (50 * wpm)); int word_space_ms = ms_per_dit * 7; -// Hardcoding messages and WPM for now, will come back and make it more flexible. -// Extra space at the end to get around https://github.com/jandelgado/jled/issues/122 on cycle mode -MorseEffect morseEffectTEST("TEST TEST TEST DE W1CDN", ms_per_dit); -MorseEffect morseEffectMOE("MOE", ms_per_dit); -MorseEffect morseEffectMOI("MOI", ms_per_dit); -MorseEffect morseEffectMOS("MOS", ms_per_dit); -MorseEffect morseEffectMOH("MOH", ms_per_dit); -MorseEffect morseEffectMO5("MO5", ms_per_dit); - -// CW for keyer -auto morseTEST = - JLed(keyer).UserFunc(&morseEffectTEST).DelayAfter(word_space_ms).Forever(); -auto morseMOE = - JLed(keyer).UserFunc(&morseEffectMOE).DelayAfter(word_space_ms).Forever(); -auto morseMOI = - JLed(keyer).UserFunc(&morseEffectMOI).DelayAfter(word_space_ms).Forever(); -auto morseMOS = - JLed(keyer).UserFunc(&morseEffectMOS).DelayAfter(word_space_ms).Forever(); -auto morseMOH = - JLed(keyer).UserFunc(&morseEffectMOH).DelayAfter(word_space_ms).Forever(); -auto morseMO5 = - JLed(keyer).UserFunc(&morseEffectMO5).DelayAfter(word_space_ms).Forever(); -auto morseToSend = morseTEST; // set this up to overwrite later - -// CW for blinker -auto morseTEST_blink = - JLed(blinker).UserFunc(&morseEffectTEST).DelayAfter(word_space_ms).Forever(); -auto morseMOE_blink = - JLed(blinker).UserFunc(&morseEffectMOE).DelayAfter(word_space_ms).Forever(); -auto morseMOI_blink = - JLed(blinker).UserFunc(&morseEffectMOI).DelayAfter(word_space_ms).Forever(); -auto morseMOS_blink = - JLed(blinker).UserFunc(&morseEffectMOS).DelayAfter(word_space_ms).Forever(); -auto morseMOH_blink = - JLed(blinker).UserFunc(&morseEffectMOH).DelayAfter(word_space_ms).Forever(); -auto morseMO5_blink = - JLed(blinker).UserFunc(&morseEffectMO5).DelayAfter(word_space_ms).Forever(); -auto morseToSend_blink = morseTEST_blink; // set this up to overwrite later - -// Cycle stuff -auto morse_cycle = morseEffectMOS; -int period = morse_cycle.Period() + word_space_ms; -int repeats = step_length / period; -int remainder_wait = step_length - (period * repeats); -int total_wait = ((step_length * (n_transmitters - 1) + remainder_wait)); -auto blinker_continuous = JLed(blinker).UserFunc(&morse_cycle).Repeat(repeats).DelayAfter(word_space_ms); -auto blinker_continuous_wait = JLed(blinker).Off(total_wait); -JLed morses_blink[] = { - blinker_continuous, - blinker_continuous_wait -}; -auto morses_sequence_blink = JLedSequence(JLedSequence::eMode::SEQUENCE, morses_blink); - - - - -JLedSequence* sequence = NULL; -JLedSequence* make_sequence(JLedSequence* seq, const char* message, int wpm, int step_length, int n_transmitters){ - int ms_per_dit = 60;//1000 * (60 / (50 * wpm)); - int word_space_ms = ms_per_dit * 7; - MorseEffect morse_effect(message, ms_per_dit); - int period = morse_effect.Period(); - int repeats = 2;//step_length / period; - int remainder_wait = step_length - (period * repeats); - int total_wait = ((step_length * (n_transmitters - 1) + remainder_wait)); - Serial.print("total_wait: "); Serial.println(total_wait); - JLed morses_blink[] = { - JLed(blinker).UserFunc(&morse_effect).Repeat(repeats).DelayAfter(word_space_ms), - JLed(blinker).Off(total_wait) - }; - if (seq){ - delete seq; - //seq = new JLedSequence(JLedSequence::eMode::SEQUENCE, leds); - seq = new JLedSequence(JLedSequence::eMode::SEQUENCE, morses_blink); - } - return seq; -} -// Initial definition of the sequence -JLedSequence* morses_sequence_blink_test = make_sequence(sequence, "MOE", 10, 10000, 2); - - - - -//================================================================================ -// start_program(): a function to start the planned program at the planned time -//================================================================================ -// bool start_program(){ -// Serial.println("The scheduled program has started."); -// startProgram = true; -// return false; -// } //================================================================================ // setup(): stuff that only gets done once, after power up (KB1OIQ's description) @@ -378,7 +263,8 @@ JLedSequence* morses_sequence_blink_test = make_sequence(sequence, "MOE", 10, 10 void setup() { Serial.begin(115200); - + // Get arduinomorse ready to go + sender.setup(); pinMode(alarmPin, INPUT_PULLUP); // Set alarm pin as pullup @@ -411,10 +297,10 @@ void setup() { // Initialize the output variables as outputs pinMode(keyer, OUTPUT); - pinMode(blinker, OUTPUT); + //pinMode(blinker, OUTPUT); // Set outputs to LOW digitalWrite(keyer, LOW); - digitalWrite(blinker, LOW); + //digitalWrite(blinker, LOW); // Initialize SPIFFS SPIFFS.begin(true); @@ -441,23 +327,17 @@ void setup() { // On restart, keep doing what you were doing before yourInputMsg_old = yourInputMsg; if(yourInputMsg == 0){ - morseToSend = morseTEST; - morseToSend_blink = morseTEST_blink; + sender.setMessage(String("test test test de w1cdn ")); } else if(yourInputMsg == 1){ - morseToSend = morseMOE; - morseToSend_blink = morseMOE_blink; + sender.setMessage(String("moe ")); } else if(yourInputMsg == 2){ - morseToSend = morseMOI; - morseToSend_blink = morseMOI_blink; + sender.setMessage(String("moi ")); } else if(yourInputMsg == 3){ - morseToSend = morseMOS; - morseToSend_blink = morseMOS_blink; + sender.setMessage(String("mos ")); } else if(yourInputMsg == 4){ - morseToSend = morseMOH; - morseToSend_blink = morseMOH_blink; + sender.setMessage(String("moh ")); } else if(yourInputMsg == 5){ - morseToSend = morseMO5; - morseToSend_blink = morseMO5_blink; + sender.setMessage(String("mo5 ")); } WiFi.mode(WIFI_STA); @@ -490,10 +370,11 @@ void setup() { writeFile(SPIFFS, "/inputSend.txt", inputMessage.c_str()); yourInputSend = inputMessage.toInt(); // if not running a program, set the program running off - if(yourInputSend != 2){ + //if(yourInputSend != 2){ + // Cease all programs on new input startProgram = false; programRunning = false; - } + //} } // GET inputWPM value on /get?inputWPM= if (request->hasParam(PARAM_WPM)) { @@ -508,6 +389,21 @@ void setup() { // save previous state yourInputMsg_old = yourInputMsg; yourInputMsg = inputMessage.toInt(); + + // Check the message every time the form is submitted. + if(yourInputMsg == 0){ + sender.setMessage(String("test test test de w1cdn ")); + } else if(yourInputMsg == 1){ + sender.setMessage(String("moe ")); + } else if(yourInputMsg == 2){ + sender.setMessage(String("moi ")); + } else if(yourInputMsg == 3){ + sender.setMessage(String("mos ")); + } else if(yourInputMsg == 4){ + sender.setMessage(String("moh ")); + } else if(yourInputMsg == 5){ + sender.setMessage(String("mo5 ")); + } } // GET inputStepLength value on /get?inputStepLength= if (request->hasParam(PARAM_STEPLENGTH)) { @@ -582,13 +478,6 @@ void setup() { yourInputStartTimeUnix = atol(inputMessage.c_str()); //} Serial.println(yourInputStartTimeUnix); - - // We can't use arduino-timer for starting a program because - // it relies on millis(), which reset on power cycle. - // timer.at(millis() + 10000, toggle_led); - // Serial.println(millis()); - // auto active_tasks = timer.size(); - // Serial.println(active_tasks); // Use alarm built into RTC rtc.setAlarm1(DateTime(yourInputStartTimeUnix), DS3231_A1_Date); @@ -607,37 +496,26 @@ void setup() { } void loop() { - // Timers - timer.tick(); - morses_sequence_blink_test->Forever().Update(); - -// See which message we are sending + // See which message we are sending // Only do this when the message has been updated. - if(yourInputMsg != yourInputMsg_old){ - //morseToSend.Stop(JLed::eStopMode::FULL_OFF).Update(); - if(yourInputMsg == 0){ - morseToSend = morseTEST; - morseToSend_blink = morseTEST_blink; - } else if(yourInputMsg == 1){ - morseToSend = morseMOE; - morseToSend_blink = morseMOE_blink; - } else if(yourInputMsg == 2){ - morseToSend = morseMOI; - morseToSend_blink = morseMOI_blink; - } else if(yourInputMsg == 3){ - morseToSend = morseMOS; - morseToSend_blink = morseMOS_blink; - } else if(yourInputMsg == 4){ - morseToSend = morseMOH; - morseToSend_blink = morseMOH_blink; - } else if(yourInputMsg == 5){ - morseToSend = morseMO5; - morseToSend_blink = morseMO5_blink; - } - // Keeps the key/led from locking up - yourInputMsg_old = yourInputMsg; - } + // if(yourInputMsg != yourInputMsg_old){ + // if(yourInputMsg == 0){ + // sender.setMessage(String("test test test de w1cdn ")); + // } else if(yourInputMsg == 1){ + // sender.setMessage(String("moe ")); + // } else if(yourInputMsg == 2){ + // sender.setMessage(String("moi ")); + // } else if(yourInputMsg == 3){ + // sender.setMessage(String("mos ")); + // } else if(yourInputMsg == 4){ + // sender.setMessage(String("moh ")); + // } else if(yourInputMsg == 5){ + // sender.setMessage(String("mo5 ")); + // } + // // Keeps the key/led from locking up + // yourInputMsg_old = yourInputMsg; + // } // This statement from https://github.com/garrysblog/DS3231-Alarm-With-Adafruit-RTClib-Library/blob/master/DS3231-RTClib-Adafruit-Alarm-Poll-alarmFired/DS3231-RTClib-Adafruit-Alarm-Poll-alarmFired.ino // Check if alarm by polling SQW alarm pin @@ -655,58 +533,53 @@ void loop() { // Once alarm has started the program, set things up to run if(startProgram == true){ - //auto morse_cycle = morseEffectMOS; - //int period = morse_cycle.Period() + word_space_ms; - //int repeats = step_length / period; - //int remainder_wait = step_length - (period * repeats); - //int total_wait = ((step_length * (n_transmitters - 1) + remainder_wait)); - - // Nothing makes it out of this scope... - // blinker_continuous = JLed(blinker).UserFunc(&morse_cycle).Repeat(repeats).DelayAfter(word_space_ms); - // blinker_continuous_wait = JLed(blinker).Off(total_wait); - // JLed morses_blink[] = { - // blinker_continuous, - // blinker_continuous_wait - // }; - // auto morses_sequence_blink = JLedSequence(JLedSequence::eMode::SEQUENCE, morses_blink); - - morses_sequence_blink.Forever().Update(); + //Serial.println("Start sending"); + start_millis = millis() + ((yourInputCycleID - 1) * yourInputStepLength); + stop_millis = start_millis + yourInputStepLength; + if(yourInputCycleID == 1){ + pause_until_millis = stop_millis + (yourInputStepLength * (yourInputNtransmitters - 1)); + } else { + // Subtract 2 rather than 1 here to account for start_millis duration at beginning of repeat. + pause_until_millis = stop_millis + (yourInputStepLength * (yourInputNtransmitters - 2)); + } + //sender.startSending(); programRunning = true; startProgram = false; } // if you want to send continuous code, and it's not sending, then start it up - if((yourInputSend == 1) & (morseToSend.IsRunning() == false)){ - //jled - morseToSend.Reset().Update(); - morseToSend_blink.Reset().Update(); - - // if you want to send continuous code, and it is sending, keep sending - } else if((yourInputSend == 1) & (morseToSend.IsRunning() == true)){ - morseToSend.Update(); - morseToSend_blink.Update(); - - // if you want to send cycle code and it is sending, keep sending - } else if((yourInputSend == 2) & (programRunning == true) &(morses_sequence_blink.Update() == true)){ - morseToSend.Update(); - //morseToSend_blink.Update(); - morses_sequence_blink.Update(); - + if((yourInputSend == 1)){ + if (!sender.continueSending()){ + // Set the internal counters to the message's beginning. + // Here, this results in repeating the message indefinitely. + sender.startSending(); + } // if you want to send cycle code and it's not sending, then start it up - } else if((yourInputSend == 2) & (programRunning == true) & (morses_sequence_blink.Update() == false)){ - morseToSend.Reset().Update(); - //morseToSend_blink.Reset().Update(); - morses_sequence_blink.Reset(); + } else if((yourInputSend == 2) & (programRunning == true)){ + if((millis() < start_millis)){ + // Shut the pin off manually + digitalWrite(blinker, LOW); + } else if((millis() >= start_millis) & (millis() <= stop_millis)){ + if (!sender.continueSending()){ + // Set the internal counters to the message's beginning. + // Here, this results in repeating the message indefinitely. + sender.startSending(); + } + } else if((millis() >= stop_millis) & (millis() <= pause_until_millis)){ + // do nothing in this case -- in between cycles + // Shut the pin off manually + digitalWrite(blinker, LOW); + } else if((millis() >= pause_until_millis)){ + startProgram = true; + } // if the cycle program is not running } else if((yourInputSend == 2) & (programRunning == false)){ - morses_sequence_blink.Stop(); + // do we need something here? // if you don't want to send code - } else { - // stop sending and make sure the pin is off - morseToSend.Stop(JLed::eStopMode::FULL_OFF).Update(); - morseToSend_blink.Stop(JLed::eStopMode::FULL_OFF).Update(); - morses_sequence_blink.Stop(); + } else if(yourInputSend == 0){ + //sender.setMessage(String("")) ; // Not sure this is the right way to stop things. + // Shut the pin off manually + digitalWrite(blinker, LOW); } - //morseToSend.Update(); } \ No newline at end of file diff --git a/vulpes/src/morse.cpp b/vulpes/src/morse.cpp new file mode 100755 index 0000000..66f58ee --- /dev/null +++ b/vulpes/src/morse.cpp @@ -0,0 +1,232 @@ +// Morse Code sending library + +#include + +// MorseSender +int MorseSender::copyTimings( + morseTiming_t *rawOut, + morseBitmask_t definition) +{ + int t = 0; + boolean foundSentinel = false; + for(morseBitmask_t mask = MORSE_BITMASK_HIGH_BIT; + mask > 0; mask = mask >> 1) + { + boolean isDah = (mask & definition) > 0; + if(!foundSentinel) + { + if (isDah) { foundSentinel = true; } + continue; + } + rawOut[2*t] = isDah ? DAH : DIT; + rawOut[2*t + 1] = DIT; + t++; + } + return t; +} +unsigned int MorseSender::fillTimings(char c) +{ + int t = 0; + unsigned int start = 0; + if (c >= 'a' && c <= 'z') + { + t = copyTimings(timingBuffer, MORSE_LETTERS[c-'a']); + } + else if (c >= '0' && c <= '9') + { + int n = c - '0'; + boolean ditsFirst = (n <= 5); + if (!ditsFirst) + { + n -= 5; + } + while(t < 5) + { + timingBuffer[2*t] = ((t < n) == ditsFirst) ? DIT : DAH; + timingBuffer[2*t + 1] = DIT; + t++; + } + } + else + { + int s = 0; + while(MORSE_PUNCT_ETC[s].c != END) + { + if(MORSE_PUNCT_ETC[s].c == c) + { + t = copyTimings(timingBuffer, + MORSE_PUNCT_ETC[s].timing); + break; + } + s++; + } + if (MORSE_PUNCT_ETC[s].c == END) + { + start = t = 1; // start on a space + } + } + + timingBuffer[2*t - 1] = DAH; + timingBuffer[2*t] = END; + + /* + Serial.print("Refilled timing buffer for '"); + Serial.print(c); + Serial.print("': "); + int i = start; + while(timingBuffer[i] != END) + { + Serial.print((int)timingBuffer[i]); + Serial.print(", "); + i++; + } + Serial.println("END"); + */ + + return start; +} + +// see note in header about pure-virtual-ness +void MorseSender::setOn() {}; +void MorseSender::setOff() {}; + +// noop defaults +void MorseSender::setReady() {}; +void MorseSender::setComplete() {}; + +MorseSender::MorseSender(unsigned int outputPin, float wpm) : + pin(outputPin) +{ + setWPM(wpm); +} + +void MorseSender::setup() { pinMode(pin, OUTPUT); } + +void MorseSender::setWPM(float wpm) +{ + setSpeed((morseTiming_t)(1000.0*60.0/(max(1.0f, wpm)*DITS_PER_WORD))); +} + +void MorseSender::setSpeed(morseTiming_t duration) +{ + DIT = max(duration, (morseTiming_t) 1); + DAH = 3*DIT; +} + +void MorseSender::setMessage(const String newMessage) +{ + message = newMessage; + + // Force startSending() before continueSending(). + messageIndex = message.length(); + + // If a different message was in progress, make sure it stops cleanly. + if (timingIndex % 2 == 0) { + setOff(); + } +} + +void MorseSender::sendBlocking() +{ + //Serial.println("Sending blocking: "); + //Serial.println(message); + startSending(); + while(continueSending()); +} + +void MorseSender::startSending() +{ + messageIndex = 0; + if (message.length() == 0) { return; } + timingIndex = fillTimings(message[0]); + setReady(); + if (timingIndex % 2 == 0) { + setOn(); + //Serial.print("Starting with on, duration="); + } else { + //Serial.print("Starting with off, duration="); + } + lastChangedMillis = millis(); + //Serial.println((int)timingBuffer[timingIndex]); +} + +boolean MorseSender::continueSending() +{ + if(messageIndex >= message.length()) { return false; } + + unsigned long elapsedMillis = millis() - lastChangedMillis; + if (elapsedMillis < timingBuffer[timingIndex]) { return true; } + + timingIndex++; + if (timingBuffer[timingIndex] == END) + { + messageIndex++; + if(messageIndex >= message.length()) { + setOff(); + setComplete(); + return false; + } + timingIndex = fillTimings(message[messageIndex]); + } + + lastChangedMillis += elapsedMillis; + //Serial.print("Next is "); + if (timingIndex % 2 == 0) { + //Serial.print("(on) "); + setOn(); + } else { + //Serial.print("(off) "); + setOff(); + } + //Serial.println((int)timingBuffer[timingIndex]); + + return true; +} + +void *MorseSender::operator new(size_t size) { return malloc(size); } +void MorseSender::operator delete(void* ptr) { if (ptr) free(ptr); } + + +// SpeakerMorseSender + +// void SpeakerMorseSender::setOn() { tone(pin, frequency); } +// void SpeakerMorseSender::setOff() { +// if (carrFrequency == CARRIER_FREQUENCY_NONE) { +// noTone(pin); +// } else { +// tone(pin, carrFrequency); +// } +// } +// void SpeakerMorseSender::setReady() { setOff(); } +// void SpeakerMorseSender::setComplete() { noTone(pin); } +// SpeakerMorseSender::SpeakerMorseSender( +// int outputPin, +// unsigned int toneFrequency, +// unsigned int carrierFrequency, +// float wpm) +// : MorseSender(outputPin, wpm), +// frequency(toneFrequency), +// carrFrequency(carrierFrequency) {}; + + +// LEDMorseSender + +void LEDMorseSender::setOn() { digitalWrite(pin, activeLow ? LOW : HIGH); } +void LEDMorseSender::setOff() { digitalWrite(pin, activeLow ? HIGH : LOW); } +LEDMorseSender::LEDMorseSender(int outputPin, bool activeLow, float wpm) + : MorseSender(outputPin, wpm), activeLow(activeLow) {}; +LEDMorseSender::LEDMorseSender(int outputPin, float wpm) + : MorseSender(outputPin, wpm), activeLow(false) {}; + +// PWMMorseSender + +void PWMMorseSender::setOn() { analogWrite(pin, brightness); } +void PWMMorseSender::setOff() { analogWrite(pin, 0); } +void PWMMorseSender::setBrightness(byte bright) { + brightness = bright; +} +PWMMorseSender::PWMMorseSender( + int outputPin, + float wpm, + byte bright) + : MorseSender(outputPin, wpm), brightness(bright) {}; diff --git a/vulpes/src/morse.h b/vulpes/src/morse.h new file mode 100755 index 0000000..18e6cf3 --- /dev/null +++ b/vulpes/src/morse.h @@ -0,0 +1,276 @@ +#pragma once +/** + * Generate and send Morse Code on an LED or a speaker. Allow sending + * in a non-blocking manner (by calling a 'continue sending' method + * every so often to turn an LED on/off, or to call tone/noTone appropriately). + * + * All input should be lowercase. Prosigns (SK, KN, etc) have special + * character values #defined. + * + * See also: + * Morse decoder (using binary tree): + * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1289074596/15 + * Generator (on playground): + * http://www.arduino.cc/playground/Code/Morse + */ + +// for malloc and free, for the new/delete operators +#include +#include + +// Arduino language types +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#define WPM_DEFAULT 12.0 +// PARIS WPM measurement: 50; CODEX WPM measurement: 60 (Wikipedia:Morse_code) +#define DITS_PER_WORD 50 +// Pass to SpeakerMorseSender as carrierFrequency to suppress the carrier. +#define CARRIER_FREQUENCY_NONE 0 + +// Bitmasks are 1 for dah and 0 for dit, in left-to-right order; +// the sequence proper begins after the first 1 (a sentinel). +// Credit for this scheme to Mark VandeWettering K6HX ( brainwagon.org ). +typedef unsigned int morseTiming_t; +typedef unsigned char morseBitmask_t; // see also MAX_TIMINGS +#define MORSE_BITMASK_HIGH_BIT B10000000 + +// sentinel +#define END 0 + +// the most timing numbers any unit will need; ex: k = on,off,on,off,on,end = 5 +#define MAX_TIMINGS 15 + +// Punctuation and Prosigns +#define PROSIGN_SK 'S' +#define PROSIGN_KN 'K' +#define PROSIGN_BT 'B' +typedef struct { + char c; + morseBitmask_t timing; +} specialTiming; +const specialTiming MORSE_PUNCT_ETC[] = { + {'.', B1010101}, + {'?', B1001100}, + {'/', B110010}, + {PROSIGN_SK, B1000101}, + {PROSIGN_KN, B110110}, + {PROSIGN_BT, B110001}, + {END, B1}, +}; + +// Morse Code (explicit declaration of letter timings) +const morseBitmask_t MORSE_LETTERS[26] = { + /* a */ B101, + /* b */ B11000, + /* c */ B11010, + /* d */ B1100, + /* e */ B10, + /* f */ B10010, + /* g */ B1110, + /* h */ B10000, + /* i */ B100, + /* j */ B10111, + /* k */ B1101, + /* l */ B10100, + /* m */ B111, + /* n */ B110, + /* o */ B1111, + /* p */ B10110, + /* q */ B11101, + /* r */ B1010, + /* s */ B1000, + /* t */ B11, + /* u */ B1001, + /* v */ B10001, + /* w */ B1011, + /* x */ B11001, + /* y */ B11011, + /* z */ B11100, +}; + + +/** + * Define the logic of converting characters to on/off timing, + * and encapsulate the state of one sending-in-progress Morse message. + * + * Subclasses define setOn and setOff for (for example) LED and speaker output. + */ +class MorseSender { +protected: + const unsigned int pin; + // The setOn and setOff methods would be pure virtual, + // but that has compiler issues. + // See: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1167672075 . + + /** + * Called to set put the output in 'on' state, during a dit or dah. + */ + virtual void setOn(); + virtual void setOff(); + + /** + * Called before sending a message. Used for example to enable a + * carrier. (Noop in the base class.) + */ + virtual void setReady(); + virtual void setComplete(); + +private: + morseTiming_t DIT, DAH; + String message; + + // on,off,...,wait,0 list, millis + morseTiming_t timingBuffer[MAX_TIMINGS+1]; + + // index of the character currently being sent + unsigned int messageIndex; + // timing unit currently being sent + unsigned int timingIndex; + + // when this timing unit was started + unsigned long lastChangedMillis; + + /** + * Copy definition timings (on only) to raw timings (on/off). + * @return the number of 'on' timings copied + */ + int copyTimings(morseTiming_t *rawOut, + morseBitmask_t definition); + + /** + * Fill a buffer with on,off,..,END timings (millis) + * @return the index at which to start within the new timing sequence + */ + unsigned int fillTimings(char c); + +public: + /** + * Create a sender which will output to the given pin. + */ + MorseSender(unsigned int outputPin, float wpm=WPM_DEFAULT); + + /** + * To be called during the Arduino setup(); set the pin as OUTPUT. + */ + void setup(); + + /** + * Set the words per minute (based on PARIS timing). + */ + void setWPM(float wpm); + + /** + * Set the duration, in milliseconds, of a DIT. + */ + void setSpeed(morseTiming_t duration); + + /** + * Set the message to be sent. + * This halts any sending in progress. + */ + void setMessage(const String newMessage); + + /** + * Send the entirety of the current message before returning. See the "simple" + * example, which uses sendBlocking to send one message. + */ + void sendBlocking(); + + /** + * Prepare to send and begin sending the current message. After calling this, + * call continueSending repeatedly until it returns false to finish sending + * the message. See the "speeds" example, which calls startSending and + * continueSending on two different senders. + */ + void startSending(); + + /** + * Switch outputs on and off (and refill the internal timing buffer) + * as necessary to continue with the sending of the current message. + * This should be called every few milliseconds (at a significantly + * smaller interval than a DIT) to produce a legible fist. + * + * @see startSending, which must be called first + * @return false if sending is complete, otherwise true (keep sending) + */ + boolean continueSending(); + + void *operator new(size_t size); + void operator delete(void* ptr); +}; + + +/** + * Adapt Morse sending to use the Arduino language tone() and noTone() + * functions, for use with a speaker. + * + * If a carrierFrequency is given, instead of calling noTone, call tone + * with a low frequency. This is useful ex. for maintaining radio links. + */ +class SpeakerMorseSender: public MorseSender { + private: + unsigned int frequency; + unsigned int carrFrequency; + protected: + virtual void setOn(); + virtual void setOff(); + virtual void setReady(); + virtual void setComplete(); + public: + // concert A = 440 + // middle C = 261.626; higher octaves = 523.251, 1046.502 + SpeakerMorseSender( + int outputPin, + unsigned int toneFrequency=1046, + unsigned int carrierFrequency=CARRIER_FREQUENCY_NONE, + float wpm=WPM_DEFAULT); +}; + + +/** + * Sends Morse on a digital output pin. + */ +class LEDMorseSender: public MorseSender { + private: + bool activeLow; + protected: + virtual void setOn(); + virtual void setOff(); + public: + /** + * Creates a LED Morse code sender with the given GPIO pin. The optional + * boolean activeLow indicates LED is ON with digital LOW value. + * @param outputPin GPIO pin number + * @param activeLow set to true to indicate the LED ON with digital LOW value. default: false + * @param wpm words per minute, default: WPM_DEFAULT + */ + LEDMorseSender(int outputPin, bool activeLow = false, float wpm=WPM_DEFAULT); + + /** + * Creates a LED Morse code sender with the given GPIO pin. This constructor is for backward compability. + * @param outputPin GPIO pin number + * @param wpm words per minute + */ + LEDMorseSender(int outputPin, float wpm); +}; + + +/** + * Sends Morse on an analog output pin (using PWM). The brightness value is + * between 0 and 255 and is passed directly to analogWrite. + */ +class PWMMorseSender: public MorseSender { + private: + byte brightness; + protected: + virtual void setOn(); + virtual void setOff(); + public: + PWMMorseSender(int outputPin, float wpm=WPM_DEFAULT, byte brightness=255); + void setBrightness(byte brightness); +}; +