diff --git a/firmware/webtools/manifest.json b/firmware/webtools/manifest.json index 1e7d9d0..006d50f 100644 --- a/firmware/webtools/manifest.json +++ b/firmware/webtools/manifest.json @@ -1,6 +1,6 @@ { "name": "SOTACAT for Elecraft KX2 and KX3", - "version": "2024-06-28_14:51-Debug", + "version": "2024-06-29_15:15-Debug", "builds": [ { "chipFamily": "ESP32-C3", diff --git a/include/build_info.h b/include/build_info.h index 91894df..08109e6 100644 --- a/include/build_info.h +++ b/include/build_info.h @@ -1 +1 @@ -#define BUILD_DATE_TIME "240628:1451" +#define BUILD_DATE_TIME "240629:1515" diff --git a/platformio.ini b/platformio.ini index a86d55f..82f3214 100644 --- a/platformio.ini +++ b/platformio.ini @@ -38,6 +38,8 @@ board_build.embed_txtfiles = src/web/pota.js src/web/settings.html src/web/settings.js + src/web/cat.html + src/web/cat.js src/web/about.html ; ------------------------------------------------------------------------------------------ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7159f39..9ec7cc6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,8 @@ idf_component_register( "web/sota.js" "web/pota.html" "web/pota.js" + "web/cat.html" + "web/cat.js" "web/settings.html" "web/settings.js" "web/about.html" diff --git a/src/handler_cat.cpp b/src/handler_cat.cpp new file mode 100644 index 0000000..d8c8b92 --- /dev/null +++ b/src/handler_cat.cpp @@ -0,0 +1,106 @@ +#include +#include "globals.h" +#include "kx_radio.h" +#include "webserver.h" + +#include +static const char * TAG8 = "sc:hdl_cat "; + +/** + * Handles an HTTP PUT request to play a pre-recorded message + * + * @param req Pointer to the HTTP request structure. The "bank" query parameter + * is expected to hold either "1" or "2". + */ +esp_err_t handler_msg_put (httpd_req_t * req) { + showActivity(); + + ESP_LOGV (TAG8, "trace: %s()", __func__); + + STANDARD_DECODE_SOLE_PARAMETER (req, "bank", param_value); + ESP_LOGI (TAG8, "playing message bank '%s'", param_value); + + long bank = atoi (param_value); // Convert the parameter to an integer + const char * command = bank == 1 ? "SWT11;SWT19;" : "SWT11;SWT27;"; + + { + const std::lock_guard lock (kxRadio); + kxRadio.put_to_kx_command_string (command, 1); + } + + REPLY_WITH_SUCCESS(); +} + +esp_err_t handler_power_get (httpd_req_t * req) { + showActivity(); + + ESP_LOGV (TAG8, "trace: %s()", __func__); + + long power; + { + const std::lock_guard lock (kxRadio); + power = kxRadio.get_from_kx ("PC", SC_KX_COMMUNICATION_RETRIES, 3); + } + + char power_string[8]; + snprintf (power_string, sizeof (power_string), "%ld", power); + ESP_LOGI (TAG8, "returning power: %s", power_string); + httpd_resp_send (req, power_string, HTTPD_RESP_USE_STRLEN); + + return ESP_OK; +} + +esp_err_t handler_power_put (httpd_req_t * req) { + showActivity(); + + ESP_LOGV (TAG8, "trace: %s()", __func__); + + STANDARD_DECODE_SOLE_PARAMETER (req, "power", param_value); + ESP_LOGI (TAG8, "setting power to '%s'", param_value); + + { + const std::lock_guard lock (kxRadio); + if (!kxRadio.put_to_kx ("PC", 3, atoi (param_value), SC_KX_COMMUNICATION_RETRIES)) + REPLY_WITH_FAILURE (req, 404, "unable to set power"); + } + + REPLY_WITH_SUCCESS(); +} + +esp_err_t handler_keyer_put (httpd_req_t * req) { + showActivity(); + + ESP_LOGV (TAG8, "trace: %s()", __func__); + + STANDARD_DECODE_SOLE_PARAMETER (req, "message", param_value); + + url_decode_in_place (param_value); + ESP_LOGI (TAG8, "keying message '%s'", param_value); + + char command[256]; + snprintf (command, sizeof (command), "KYW%s;", param_value); + { + const std::lock_guard lock (kxRadio); + radio_mode_t mode; + + mode = (radio_mode_t)kxRadio.get_from_kx ("MD", SC_KX_COMMUNICATION_RETRIES, 1); + if (mode != MODE_CW) + kxRadio.put_to_kx ("MD", 1, MODE_CW, SC_KX_COMMUNICATION_RETRIES); + kxRadio.put_to_kx_command_string (command, 1); + /** + * TODO: wait for keying to complete before changing mode back This can + * be a combination of a smart delay computed by taking WPM, message + * size, and standard dot length into account As well, it can finish by + * querying the radio for characters remaining to transmit, and + * repeating that polling using shorter cycle informed by the queue + * length. See "TBX" command. As it stands, the mode switch will be + * queued, and won't disrupt transmission, but since the command returns + * immediately and says we're still in CW mode, it looks like a failure + * in the logs. + */ + if (mode != MODE_CW) + kxRadio.put_to_kx ("MD", 1, mode, SC_KX_COMMUNICATION_RETRIES); + } + + REPLY_WITH_SUCCESS(); +} diff --git a/src/web/cat.html b/src/web/cat.html new file mode 100644 index 0000000..312481c --- /dev/null +++ b/src/web/cat.html @@ -0,0 +1,52 @@ + +
+
+

Play Stored Msg

+ + +
+
+
+

Send Text as CW

+
+ + +
+
+ + +
+
+ + +
+
+
+
+

Adjust Power

+ + +
+
+
diff --git a/src/web/cat.js b/src/web/cat.js new file mode 100644 index 0000000..3f8b6dd --- /dev/null +++ b/src/web/cat.js @@ -0,0 +1,27 @@ +function playMsg(slot) { + // Create the PUT request using Fetch API + const url = "/api/v1/msg?bank=" + slot; + fetch(url, { method: "PUT" }).catch((error) => + console.error("Fetch error:", error), + ); +} + +function setPowerMinMax(maximum) { + const url = "/api/v1/power?power=" + (maximum ? "10" : "0"); + fetch(url, { method: "PUT" }).catch((error) => + console.error("Fetch error:", error), + ); +} + +function sendKeys(message) { + if (message.length < 1 || message.length > 24) + alert("Text length must be [1..24] characters."); + else { + const url = "/api/v1/keyer?message=" + message; + fetch(url, { method: "PUT" }).catch((error) => + console.error("Fetch error:", error), + ); + } +} + +function settingsOnAppearing() {} diff --git a/src/web/index.html b/src/web/index.html index 0a02e46..96b81ca 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -42,6 +42,7 @@

+
diff --git a/src/web/style.css b/src/web/style.css index 1ae1ebf..43ef425 100644 --- a/src/web/style.css +++ b/src/web/style.css @@ -235,6 +235,35 @@ table.table-sortable th span[data-sort-dir="desc"]::after { text-align: right; /* Right-align the KHz column */ } +/* ---------------------------- CAT Page */ + +/* Increase button size and space between them */ +button.bigthumb { + font-size: 20px; /* Increase font size */ + padding: 15px 30px; /* Increase padding */ + margin: 10px; /* Add margin to increase space between buttons */ + border-radius: 10px; /* Optional: add border-radius for better aesthetics */ + min-width: 100px; /* Set a minimum width */ + min-height: 50px; /* Set a minimum height */ +} + +/* Increase size of buttons and input boxes */ +.input-group { + display: flex; + align-items: center; + margin-bottom: 15px; +} +.input-group input { + font-size: 20px; /* Increase font size */ + padding: 10px; /* Increase padding */ + margin-right: 10px; /* Space between input and button */ + flex-grow: 1; /* Make the input take up available space */ +} +.input-group button { + font-size: 20px; /* Increase font size */ + padding: 10px 20px; /* Increase padding */ + border-radius: 5px; /* Optional: add border-radius for better aesthetics */ +} /* ---------------------------- Settings Page */ diff --git a/src/webserver.cpp b/src/webserver.cpp index f1c5de8..1aeb1fd 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -10,6 +10,8 @@ static const char *TAG8 = "sc:webserve"; extern const uint8_t asset##_srt[] asm("_binary_" #asset "_start"); DECLARE_ASSET(about_html) +DECLARE_ASSET(cat_html) +DECLARE_ASSET(cat_js) DECLARE_ASSET(favicon_ico) DECLARE_ASSET(index_html) DECLARE_ASSET(main_js) @@ -25,7 +27,8 @@ DECLARE_ASSET(style_css) /** * Structure to map web URI to embedded binary asset locations. * - */typedef struct + */ +typedef struct { const char *uri; const void *asset_start; @@ -51,7 +54,9 @@ static const asset_entry_t asset_map[] = { {"/pota.html", pota_html_srt, pota_html_end, "text/html", 60}, {"/pota.js", pota_js_srt, pota_js_end, "text/javascript", 60}, {"/settings.html", settings_html_srt, settings_html_end, "text/html", 60}, - {"/settings.js", settings_js_srt, settings_js_end, "text/html", 60}, + {"/settings.js", settings_js_srt, settings_js_end, "text/javascript", 60}, + {"/cat.html", cat_html_srt, cat_html_end, "text/html", 60}, + {"/cat.js", cat_js_srt, cat_js_end, "text/javascript", 60}, {"/about.html", about_html_srt, about_html_end, "text/html", 60}, {NULL, NULL, NULL, NULL, 0} // Sentinel to mark end of array }; @@ -78,11 +83,15 @@ static const api_handler_t api_handlers[] = { {HTTP_GET, "connectionStatus", handler_connectionStatus_get, true}, {HTTP_GET, "frequency", handler_frequency_get, true}, {HTTP_GET, "mode", handler_mode_get, true}, + {HTTP_GET, "power", handler_power_get, true}, {HTTP_GET, "rxBandwidth", handler_rxBandwidth_get, true}, {HTTP_GET, "settings", handler_settings_get, false}, {HTTP_GET, "version", handler_version_get, false}, {HTTP_PUT, "frequency", handler_frequency_put, true}, + {HTTP_PUT, "keyer", handler_keyer_put, true}, {HTTP_PUT, "mode", handler_mode_put, true}, + {HTTP_PUT, "msg", handler_msg_put, true}, + {HTTP_PUT, "power", handler_power_put, true}, {HTTP_PUT, "rxBandwidth", handler_rxBandwidth_put, true}, {HTTP_PUT, "time", handler_time_put, true}, {HTTP_POST, "prepareft8", handler_prepareft8_post, true},