diff --git a/vulpes/.gitignore b/vulpes/.gitignore index 3d6ed9d..0183291 100644 --- a/vulpes/.gitignore +++ b/vulpes/.gitignore @@ -3,4 +3,5 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +.vscode/* */config.h \ No newline at end of file diff --git a/vulpes/platformio.ini b/vulpes/platformio.ini index 1a2242f..722236a 100644 --- a/vulpes/platformio.ini +++ b/vulpes/platformio.ini @@ -10,20 +10,17 @@ [env:esp32doit-devkit-v1] platform = espressif32 -;build_flags = -; -std=c++11 -; -std=gnu++11 board = esp32doit-devkit-v1 framework = arduino upload_speed = 921600 monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +build_type = debug 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.0 - ;adafruit/RTClib@^2.1.1 - https://github.com/adafruit/RTClib.git ; >=2.1.2 + jandelgado/JLed@^4.13.1 + 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 09210a8..4b27559 100644 --- a/vulpes/src/main.cpp +++ b/vulpes/src/main.cpp @@ -16,23 +16,22 @@ #include #include #include -#include -// #include -//#include //arduino morse -//#include //etherkit morse -#include // jled -#include "jled/morse.h" //jled +#include "morse.h" #include // for DS3231 #include // for DS3231 -//#include //for DS3231 -//#include +#include // download zip from https://github.com/me-no-dev/ESPAsyncWebServer and install. #include 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 // Read from config.h const char* ssid = WIFI_SSID; @@ -44,6 +43,11 @@ const char* PARAM_WPM = "inputWPM"; const char* PARAM_MSG = "inputMsg"; const char* PARAM_FLOAT = "inputFloat"; const char* PARAM_TIME = "inputTimeUnix"; +const char* PARAM_START = "inputStartTimeUnix"; +const char* PARAM_RUNNING = "programRunning"; +const char* PARAM_STEPLENGTH = "inputStepLength"; +const char* PARAM_CYCLEID = "inputCycleID"; +const char* PARAM_NTRANS = "inputNtransmitters"; // Global variables String yourInputString; @@ -53,28 +57,80 @@ int yourInputMsg; int yourInputMsg_old; // to save previous state and check changes float yourInputFloat; uint32_t yourInputTime; //to keep time +uint32_t yourInputStartTimeUnix; +bool startProgram; +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 +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( + ESP Input Form - -
- inputString (current value %inputString%):
+ +

Vulpes Radio Orienteering Controller

+

Local time:

+ + +

General Settings

+

Sending program:
- Message (current value %inputMsg%): + Message:
+

+ +

Cycle Settings

+

Only applies when Sending Program is set to "2 - Cycle". You cannot set a cycle start date more than a month in advance.

+

Cycle start time
+ Current value: - WPM (current value %inputWPM%): (doesn't work yet)
+ +

+

+ Step length: milliseconds
+ Cycle ID:
+ Number of transmitters:
+

+ + + - Current time (UTC): %inputTimeUnix% -
+ + + + - inputFloat (current value %inputFloat%):
- + + )rawliteral"; -// Auxiliary variables to store the current output state -//String output26State = "off"; -//String output27State = "off"; - -// 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; -auto time_until_start = timer_create_default(); - -// Example from https://github.com/contrem/arduino-timer#examples -bool toggle_led(void *) { - digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED - return true; // keep timer active? true -} - -// Toggle GPIO pin (LED or relay) -// bool toggle_gpio_26(void *) { -// if(output26State == "off"){ -// output26State = "on"; -// digitalWrite(output26, HIGH); -// } else { -// output26State = "off"; -// digitalWrite(output26, LOW); -// } -// return true; // keep timer active? true -// } void notFound(AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); @@ -177,98 +223,32 @@ String processor(const String& var){ else if(var == "inputMsg"){ return readFile(SPIFFS, "/inputMsg.txt"); } + else if(var == "inputStepLength"){ + return readFile(SPIFFS, "/inputStepLength.txt"); + } + else if(var == "inputCycleID"){ + return readFile(SPIFFS, "/inputCycleID.txt"); + } + else if(var == "inputNtransmitters"){ + return readFile(SPIFFS, "/inputNtransmitters.txt"); + } else if(var == "inputFloat"){ return readFile(SPIFFS, "/inputFloat.txt"); - } else if(var == "inputTimeUnix"){ - return rtc.now().timestamp(); - } + } else if(var == "inputStartTimeUnix"){ + // Webform breaks if this value is empty. + String temp = readFile(SPIFFS, "/inputStartTimeUnix.txt"); + if(temp == ""){ + temp = "0"; + } + return temp; + } return String(); } -// // vvvvv Modify some functions from KB1OIQ's controller. -// // This section hasn't been tested on the hardware. -// //int dit_len = 60 ; //milliseconds; https://morsecode.world/international/timing.html - -// //================================================================================ -// // stop_26(): set GPIO 26 to LOW. Used for dot(), dash(). -// //================================================================================ -// bool stop_26(void *){ -// output26State = "off"; -// digitalWrite(output26, LOW); -// return false; // keep timer active? true -// } - -// //================================================================================ -// // dit(): transmit a single dit -// //================================================================================ -// void dit(int dit_len = 1000) { -// output26State = "on"; -// digitalWrite(output26, HIGH); -// timer.in(dit_len, stop_26); -// } - -// //================================================================================ -// // dah(): transmit a single dah -// //================================================================================ -// void dah(int dit_len = 1000) { -// output26State = "on"; -// digitalWrite(output26, HIGH); -// timer.in(dit_len * 3, stop_26); -// } - -// //================================================================================ -// // char_space()): transmit a character space -// //================================================================================ -// // A function that does nothing except (hopefully) block the timer. -// bool empty(void *) { -// return false; -// } - -// void char_space(int dit_len = 1000) { - -// timer.in(dit_len, empty); -// } - -// void k(){ -// Serial.println("K"); -// dah(); -// char_space(); -// dit(); -// char_space(); -// dah(); -// } - -// // ^^^^ - -// //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_; } -}; +// 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. @@ -276,55 +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. -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 - - -// format and print a time_t value -// void printTime(time_t t) -// { -// char buf[25]; -// char m[4]; // temporary storage for month string (DateStrings.cpp uses shared buffer) -// strcpy(m, monthShortStr(month(t))); -// sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d", -// hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t)); -// Serial.println(buf); -// } //================================================================================ // setup(): stuff that only gets done once, after power up (KB1OIQ's description) @@ -332,13 +263,10 @@ auto morseToSend_blink = morseTEST_blink; // set this up to overwrite later void setup() { Serial.begin(115200); - // https://github.com/JChristensen/DS3232RTC/blob/master/examples/TimeRTC/TimeRTC.ino - // rtc.begin(); - // setSyncProvider(rtc.get); // the function to get the time from the RTC - // if(timeStatus() != timeSet) - // Serial.println("Unable to sync with the RTC"); - // else - // Serial.println("RTC has set the system time"); + // Get arduinomorse ready to go + sender.setup(); + + pinMode(alarmPin, INPUT_PULLUP); // Set alarm pin as pullup if (! rtc.begin()) { Serial.println("Couldn't find RTC"); @@ -356,51 +284,34 @@ void setup() { //rtc.adjust(DateTime(2023, 9, 2, 17, 32, 0)); } - // Timer example, blink main LED - pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT - // call the toggle_led function every 10000 millis (10 second) - //timer.every(10000, toggle_led); - // call the toggle_gpio_26 function - //timer.every(1000, toggle_gpio_26); + // Report the RTC time + Serial.print("RTC time on startup: "); + Serial.println(rtc.now().unixtime()); + Serial.println(rtc.now().timestamp()); + + // Are there any RTC alarms set? + DateTime alarm_one = rtc.getAlarm1(); // Get the current time + char buff[] = "Alarm 1 set for at hh:mm:ss DDD, DD MMM YYYY"; + Serial.print(alarm_one.toString(buff)); + Serial.println(" (only HH:MM:SS day-of-month are accurate)"); // 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); - //#ifdef ESP32 if(!SPIFFS.begin(true)){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } - //#else if(!SPIFFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } - //#endif - - // Make sure files exist, maybe with defaults here - // if(SPIFFS.exists("/inputString.txt") == 0){ - // writeFile(SPIFFS, "/inputString.txt", "CQ"); - // } - // if(SPIFFS.exists("/inputSend.txt") == 0){ - // writeFile(SPIFFS, "/inputSend.txt", "0"); - // } - // if(SPIFFS.exists("/inputWPM.txt") == 0){ - // writeFile(SPIFFS, "/inputWPM.txt", "10"); - // } - // if(SPIFFS.exists("/inputMsg.txt") == 0){ - // writeFile(SPIFFS, "/inputMsg.txt", "0"); - // } - // if(SPIFFS.exists("/inputFloat.txt") == 0){ - // writeFile(SPIFFS, "/inputFloat.txt", "1.1"); - // } - // Read in existing data yourInputString = readFile(SPIFFS, "/inputString.txt"); @@ -408,27 +319,25 @@ void setup() { yourInputWPM = readFile(SPIFFS, "/inputWPM.txt").toInt(); yourInputMsg = readFile(SPIFFS, "/inputMsg.txt").toInt(); yourInputFloat = readFile(SPIFFS, "/inputFloat.txt").toFloat(); + yourInputStartTimeUnix = readFile(SPIFFS, "/inputStartTimeUnix.txt").toInt(); + yourInputStepLength = readFile(SPIFFS, "/inputStepLength.txt").toInt(); + yourInputCycleID = readFile(SPIFFS, "/inputCycleID.txt").toInt(); + yourInputNtransmitters = readFile(SPIFFS, "/inputNtransmitters.txt").toInt(); // 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); @@ -460,6 +369,12 @@ void setup() { inputMessage = request->getParam(PARAM_SEND)->value(); writeFile(SPIFFS, "/inputSend.txt", inputMessage.c_str()); yourInputSend = inputMessage.toInt(); + // if not running a program, set the program running off + //if(yourInputSend != 2){ + // Cease all programs on new input + startProgram = false; + programRunning = false; + //} } // GET inputWPM value on /get?inputWPM= if (request->hasParam(PARAM_WPM)) { @@ -474,18 +389,60 @@ 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)) { + inputMessage = request->getParam(PARAM_STEPLENGTH)->value(); + writeFile(SPIFFS, "/inputStepLength.txt", inputMessage.c_str()); + yourInputStepLength = inputMessage.toInt(); + } + // GET inputCycleID value on /get?inputCycleID= + if (request->hasParam(PARAM_CYCLEID)) { + inputMessage = request->getParam(PARAM_CYCLEID)->value(); + writeFile(SPIFFS, "/inputCycleID.txt", inputMessage.c_str()); + yourInputCycleID = inputMessage.toInt(); + } + // GET inputNtransmitters value on /get?inputNtransmitters= + if (request->hasParam(PARAM_NTRANS)) { + inputMessage = request->getParam(PARAM_NTRANS)->value(); + writeFile(SPIFFS, "/inputNtransmitters.txt", inputMessage.c_str()); + yourInputNtransmitters = inputMessage.toInt(); } // GET inputTimeUnix value on /get?inputTimeUnix= if (request->hasParam(PARAM_TIME)) { inputMessage = request->getParam(PARAM_TIME)->value(); - Serial.println(inputMessage); //https://stackoverflow.com/a/22733127/2152245 yourInputTime = atol(inputMessage.c_str()); + Serial.print("yourInputTime: "); Serial.println(yourInputTime); // update the RTC time rtc.adjust(DateTime(yourInputTime)); -; DateTime now = rtc.now(); + // Might work to fix random errors? If date is far in the future, + // try to update again. + // replace if with while if you want it to try a bunch... + if(now.year() > 2040){ + Serial.print("Year is "); + Serial.println(now.year()); + Serial.println("RTC can't set time. Trying again."); + rtc.adjust(DateTime(yourInputTime)); + } + Serial.print("UTC time from browser: "); Serial.print(now.year(), DEC); Serial.print('/'); @@ -501,6 +458,9 @@ void setup() { Serial.print(':'); Serial.print(now.second(), DEC); Serial.println(); + + Serial.print("rtc.now().unixtime(): "); + Serial.println(rtc.now().unixtime()); } // GET inputFloat value on /get?inputFloat= if (request->hasParam(PARAM_FLOAT)) { @@ -508,127 +468,118 @@ void setup() { writeFile(SPIFFS, "/inputFloat.txt", inputMessage.c_str()); yourInputFloat = inputMessage.toFloat(); } - // else { - // inputMessage = "No message sent"; - // } - request->send(200, "text/plain", inputMessage); - + // GET inputStartTimeUnix value on /get?inputStartTimeUnix= + if (request->hasParam(PARAM_START)) { + inputMessage = request->getParam(PARAM_START)->value(); + Serial.println(inputMessage); + // if a start time isn't entered, don't overwrite the old one + //if(!(inputMessage != NULL && inputMessage[0] == '\0')){ + writeFile(SPIFFS, "/inputStartTimeUnix.txt", inputMessage.c_str()); + yourInputStartTimeUnix = atol(inputMessage.c_str()); + //} + Serial.println(yourInputStartTimeUnix); + + // Use alarm built into RTC + rtc.setAlarm1(DateTime(yourInputStartTimeUnix), DS3231_A1_Date); + //rtc.setAlarm1(DateTime(2020, 6, 25, 15, 34, 0), DS3231_A2_Date); + DateTime alarm_one = rtc.getAlarm1(); // Get the current alarm time + char buff[] = "Alarm 1 set for at hh:mm:ss DDD, DD MMM YYYY"; + Serial.print(alarm_one.toString(buff)); + Serial.println(" (only HH:MM:SS day-of-month are accurate)"); + } + // https://techtutorialsx.com/2018/01/14/esp32-arduino-http-server-external-and-internal-redirects/ + request->redirect("/"); }); server.onNotFound(notFound); server.begin(); - //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(); - - - } void loop() { - // Timers - time_until_start.tick(); - timer.tick(); - - // DateTime now = rtc.now(); - // Serial.print(now.year(), DEC); - // Serial.print('/'); - // Serial.print(now.month(), DEC); - // Serial.print('/'); - // Serial.print(now.day(), DEC); - // Serial.print(" ("); - // Serial.print(now.dayOfTheWeek()); - // Serial.print(") "); - // Serial.print(now.hour(), DEC); - // Serial.print(':'); - // Serial.print(now.minute(), DEC); - // Serial.print(':'); - // Serial.print(now.second(), DEC); - // Serial.println(); - - - //arduinomorse - //sender.continueSending(); // 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 from locking up - yourInputMsg_old = yourInputMsg; - } - - - // 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(); - //morse.send("CQ CQ CQ DE W1CDN K"); //etherkit morse - //telegraph26.send("CQ CQ CQ DE W1CDN K"); //telegraph - - // 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) & (morseToSend.IsRunning() == true)){ - morseToSend.Update(); - morseToSend_blink.Update(); - - // if you want to send cycle code and it's not sending, then start it up - } else if((yourInputSend == 2) & (morseToSend.IsRunning() == true)){ - morseToSend.Reset().Update(); - morseToSend_blink.Reset().Update(); - - // 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(); - } - //morseToSend.Update(); - - - - // Blink LED according to seconds entered - // if (yourInputInt > 0) { - // Serial.println("GPIO 26 on"); - // output26State = "on"; - // digitalWrite(output26, HIGH); - // delay(yourInputInt * 1000); - // Serial.println(yourInputInt); - // Serial.println("GPIO 26 off"); - // output26State = "off"; - // digitalWrite(output26, LOW); - // delay(yourInputInt * 1000); - // } else { - // output26State = "off"; + // 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 + if((yourInputSend == 2) & (digitalRead(alarmPin) == LOW)) { + // Print current time and date + DateTime now = rtc.now(); // Get the current time + char buff[] = "Alarm triggered at hh:mm:ss DDD, DD MMM YYYY"; + Serial.println(now.toString(buff)); + startProgram = true; + + // Disable and clear alarm + rtc.clearAlarm(1); + rtc.clearAlarm(2); // clear the other one just in case + } + + // Once alarm has started the program, set things up to run + if(startProgram == true){ + //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)){ + 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)){ + 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)){ + // do we need something here? + // if you don't want to send code + } 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); + } + } \ 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); +}; +