diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 5f11eeff..4d2a827f 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -122,7 +122,6 @@ set(CSRC ${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c ${HACKRF_PATH}/firmware/common/usb_request.c ${HACKRF_PATH}/firmware/common/usb_standard_request.c - ${CHIBIOS}/os/various/shell.c ${CHIBIOS}/os/various/chprintf.c ) @@ -130,6 +129,7 @@ set(CSRC # setting. set(CPPSRC main.cpp + shell.cpp ${COMMON}/acars_packet.cpp ${COMMON}/adsb.cpp ${COMMON}/adsb_frame.cpp diff --git a/firmware/application/apps/ui_settings.cpp b/firmware/application/apps/ui_settings.cpp index 66a89d63..5b445c7a 100644 --- a/firmware/application/apps/ui_settings.cpp +++ b/firmware/application/apps/ui_settings.cpp @@ -149,23 +149,10 @@ SetRadioView::SetRadioView( }); } - std::string source_name("---"); - switch (reference.source) { - case ClockManager::ReferenceSource::Xtal: - source_name = "HackRF"; - break; - case ClockManager::ReferenceSource::PortaPack: - source_name = "PortaPack"; - break; - case ClockManager::ReferenceSource::External: - source_name = "External"; - break; - } + std::string source_name = clock_manager.get_source(); value_source.set(source_name); - value_source_frequency.set( - to_string_dec_uint(reference.frequency / 1000000, 2) + "." + - to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz"); + value_source_frequency.set(clock_manager.get_freq()); // Make these Text controls look like Labels. label_source.set_style(&Styles::light_grey); diff --git a/firmware/application/clock_manager.cpp b/firmware/application/clock_manager.cpp index c8924781..a9e6fa96 100644 --- a/firmware/application/clock_manager.cpp +++ b/firmware/application/clock_manager.cpp @@ -217,6 +217,27 @@ ClockManager::Reference ClockManager::get_reference() const { return reference; } +std::string ClockManager::get_source() { + std::string source_name("---"); + switch (reference.source) { + case ClockManager::ReferenceSource::Xtal: + source_name = "HackRF"; + break; + case ClockManager::ReferenceSource::PortaPack: + source_name = "PortaPack"; + break; + case ClockManager::ReferenceSource::External: + source_name = "External"; + break; + } + return source_name; +} + +std::string ClockManager::get_freq() { + return to_string_dec_uint(reference.frequency / 1000000, 2) + "." + + to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz"; +} + static void portapack_tcxo_enable() { portapack::io.reference_oscillator(true); diff --git a/firmware/application/clock_manager.hpp b/firmware/application/clock_manager.hpp index 94cdad8d..58d9944b 100644 --- a/firmware/application/clock_manager.hpp +++ b/firmware/application/clock_manager.hpp @@ -22,6 +22,7 @@ #ifndef __CLOCK_MANAGER_H__ #define __CLOCK_MANAGER_H__ +#include "string_format.hpp" #include "ch.h" #include "hal.h" @@ -74,6 +75,8 @@ class ClockManager { uint32_t get_frequency_monitor_measurement_in_hertz(); Reference get_reference() const; + std::string get_source(); + std::string get_freq(); void enable_clock_output(bool enable); diff --git a/firmware/application/shell.cpp b/firmware/application/shell.cpp new file mode 100644 index 00000000..96779307 --- /dev/null +++ b/firmware/application/shell.cpp @@ -0,0 +1,305 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file shell.c + * @brief Simple CLI shell code. + * + * @addtogroup SHELL + * @{ + */ + +#include +#include +#include + +#include "ch.h" +#include "hal.h" +#include "shell.hpp" +#include "chprintf.h" +#include "portapack.hpp" + +/** + * @brief Shell termination event source. + */ +EventSource shell_terminated; + +static char* _strtok(char* str, const char* delim, char** saveptr) { + char* token; + if (str) + *saveptr = str; + token = *saveptr; + + if (!token) + return NULL; + + token += strspn(token, delim); + *saveptr = strpbrk(token, delim); + if (*saveptr) + *(*saveptr)++ = '\0'; + + return *token ? token : NULL; +} + +static void usage(BaseSequentialStream* chp, char* p) { + chprintf(chp, "Usage: %s\r\n", p); +} + +static void list_commands(BaseSequentialStream* chp, const ShellCommand* scp) { + while (scp->sc_name != NULL) { + chprintf(chp, "%s ", scp->sc_name); + scp++; + } +} + +static void cmd_info(BaseSequentialStream* chp, int argc, char* argv[]) { + (void)argv; + if (argc > 0) { + usage(chp, const_cast("info")); + return; + } + + chprintf(chp, "Kernel: %s\r\n", CH_KERNEL_VERSION); +#ifdef CH_COMPILER_NAME + chprintf(chp, "Compiler: %s\r\n", CH_COMPILER_NAME); +#endif + chprintf(chp, "Architecture: %s\r\n", CH_ARCHITECTURE_NAME); +#ifdef CH_CORE_VARIANT_NAME + chprintf(chp, "Core Variant: %s\r\n", CH_CORE_VARIANT_NAME); +#endif +#ifdef CH_PORT_INFO + chprintf(chp, "Port Info: %s\r\n", CH_PORT_INFO); +#endif +#ifdef PLATFORM_NAME + chprintf(chp, "Platform: %s\r\n", PLATFORM_NAME); +#endif +#ifdef BOARD_NAME + chprintf(chp, "Board: %s\r\n", BOARD_NAME); +#endif +#ifdef VERSION_STRING + chprintf(chp, "Mayhem Version: %s\r\n", VERSION_STRING); +#endif + chprintf(chp, "HackRF Board Rev: %s\r\n", hackrf_r9 ? "R9" : "R1-R8"); + chprintf(chp, "Reference Source: %s\r\n", portapack::clock_manager.get_source().c_str()); + chprintf(chp, "Reference Freq: %s\r\n", portapack::clock_manager.get_freq().c_str()); +#ifdef __DATE__ +#ifdef __TIME__ + chprintf(chp, "Build time: %s%s%s\r\n", __DATE__, " - ", __TIME__); +#endif +#endif +} + +static void cmd_systime(BaseSequentialStream* chp, int argc, char* argv[]) { + (void)argv; + if (argc > 0) { + usage(chp, const_cast("systime")); + return; + } + chprintf(chp, "%lu\r\n", (unsigned long)chTimeNow()); +} + +/** + * @brief Array of the default commands. + */ +static ShellCommand local_commands[] = { + {"info", cmd_info}, + {"systime", cmd_systime}, + {NULL, NULL}}; + +static bool_t cmdexec(const ShellCommand* scp, BaseSequentialStream* chp, char* name, int argc, char* argv[]) { + while (scp->sc_name != NULL) { + if (strcmp(scp->sc_name, name) == 0) { + scp->sc_function(chp, argc, argv); + return FALSE; + } + scp++; + } + return TRUE; +} + +/** + * @brief Shell thread function. + * + * @param[in] p pointer to a @p BaseSequentialStream object + * @return Termination reason. + * @retval RDY_OK terminated by command. + * @retval RDY_RESET terminated by reset condition on the I/O channel. + * + * @notapi + */ +static msg_t shell_thread(void* p) { + int n; + BaseSequentialStream* chp = ((ShellConfig*)p)->sc_channel; + const ShellCommand* scp = ((ShellConfig*)p)->sc_commands; + char *lp, *cmd, *tokp, line[SHELL_MAX_LINE_LENGTH]; + char* args[SHELL_MAX_ARGUMENTS + 1]; + + chRegSetThreadName("shell"); + chprintf(chp, "\r\nChibiOS/RT Shell\r\n"); + while (TRUE) { + chprintf(chp, "ch> "); + if (shellGetLine(chp, line, sizeof(line))) { + chprintf(chp, "\r\nlogout"); + break; + } + lp = _strtok(line, " \t", &tokp); + cmd = lp; + n = 0; + while ((lp = _strtok(NULL, " \t", &tokp)) != NULL) { + if (n >= SHELL_MAX_ARGUMENTS) { + chprintf(chp, "too many arguments\r\n"); + cmd = NULL; + break; + } + args[n++] = lp; + } + args[n] = NULL; + if (cmd != NULL) { + if (strcmp(cmd, "exit") == 0) { + if (n > 0) { + usage(chp, const_cast("exit")); + continue; + } + break; + } else if (strcmp(cmd, "help") == 0) { + if (n > 0) { + usage(chp, const_cast("help")); + continue; + } + chprintf(chp, "Commands: help exit "); + list_commands(chp, local_commands); + if (scp != NULL) + list_commands(chp, scp); + chprintf(chp, "\r\n"); + } else if (cmdexec(local_commands, chp, cmd, n, args) && + ((scp == NULL) || cmdexec(scp, chp, cmd, n, args))) { + chprintf(chp, "%s", cmd); + chprintf(chp, " ?\r\n"); + } + } + } + shellExit(RDY_OK); + /* Never executed, silencing a warning.*/ + return 0; +} + +/** + * @brief Shell manager initialization. + * + * @api + */ +void shellInit(void) { + chEvtInit(&shell_terminated); +} + +/** + * @brief Terminates the shell. + * @note Must be invoked from the command handlers. + * @note Does not return. + * + * @param[in] msg shell exit code + * + * @api + */ +void shellExit(msg_t msg) { + /* Atomically broadcasting the event source and terminating the thread, + there is not a chSysUnlock() because the thread terminates upon return.*/ + chSysLock(); + chEvtBroadcastI(&shell_terminated); + chThdExitS(msg); +} + +/** + * @brief Spawns a new shell. + * @pre @p CH_USE_HEAP and @p CH_USE_DYNAMIC must be enabled. + * + * @param[in] scp pointer to a @p ShellConfig object + * @param[in] size size of the shell working area to be allocated + * @param[in] prio priority level for the new shell + * @return A pointer to the shell thread. + * @retval NULL thread creation failed because memory allocation. + * + * @api + */ +#if CH_USE_HEAP && CH_USE_DYNAMIC +Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio) { + return chThdCreateFromHeap(NULL, size, prio, shell_thread, (void*)scp); +} +#endif + +/** + * @brief Create statically allocated shell thread. + * + * @param[in] scp pointer to a @p ShellConfig object + * @param[in] wsp pointer to a working area dedicated to the shell thread stack + * @param[in] size size of the shell working area + * @param[in] prio priority level for the new shell + * @return A pointer to the shell thread. + * + * @api + */ +Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio) { + return chThdCreateStatic(wsp, size, prio, shell_thread, (void*)scp); +} + +/** + * @brief Reads a whole line from the input channel. + * + * @param[in] chp pointer to a @p BaseSequentialStream object + * @param[in] line pointer to the line buffer + * @param[in] size buffer maximum length + * @return The operation status. + * @retval TRUE the channel was reset or CTRL-D pressed. + * @retval FALSE operation successful. + * + * @api + */ +bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size) { + char* p = line; + + while (TRUE) { + char c; + + if (chSequentialStreamRead(chp, (uint8_t*)&c, 1) == 0) + return TRUE; + if (c == 4) { + chprintf(chp, "^D"); + return TRUE; + } + if ((c == 8) || (c == 127)) { + if (p != line) { + chSequentialStreamPut(chp, c); + chSequentialStreamPut(chp, 0x20); + chSequentialStreamPut(chp, c); + p--; + } + continue; + } + if (c == '\r') { + chprintf(chp, "\r\n"); + *p = 0; + return FALSE; + } + if (c < 0x20) + continue; + if (p < line + size - 1) { + chSequentialStreamPut(chp, c); + *p++ = (char)c; + } + } +} + +/** @} */ diff --git a/firmware/application/shell.hpp b/firmware/application/shell.hpp new file mode 100644 index 00000000..1d672eda --- /dev/null +++ b/firmware/application/shell.hpp @@ -0,0 +1,83 @@ +/* + ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file shell.h + * @brief Simple CLI shell header. + * + * @addtogroup SHELL + * @{ + */ + +#ifndef _SHELL_H_ +#define _SHELL_H_ + +/** + * @brief Shell maximum input line length. + */ +#if !defined(SHELL_MAX_LINE_LENGTH) || defined(__DOXYGEN__) +#define SHELL_MAX_LINE_LENGTH 64 +#endif + +/** + * @brief Shell maximum arguments per command. + */ +#if !defined(SHELL_MAX_ARGUMENTS) || defined(__DOXYGEN__) +#define SHELL_MAX_ARGUMENTS 4 +#endif + +/** + * @brief Command handler function type. + */ +typedef void (*shellcmd_t)(BaseSequentialStream* chp, int argc, char* argv[]); + +/** + * @brief Custom command entry type. + */ +typedef struct { + const char* sc_name; /**< @brief Command name. */ + shellcmd_t sc_function; /**< @brief Command function. */ +} ShellCommand; + +/** + * @brief Shell descriptor type. + */ +typedef struct { + BaseSequentialStream* sc_channel; /**< @brief I/O channel associated + to the shell. */ + const ShellCommand* sc_commands; /**< @brief Shell extra commands + table. */ +} ShellConfig; + +#if !defined(__DOXYGEN__) +extern EventSource shell_terminated; +#endif + +#ifdef __cplusplus +extern "C" { +#endif +void shellInit(void); +void shellExit(msg_t msg); +Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio); +Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio); +bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size); +#ifdef __cplusplus +} +#endif + +#endif /* _SHELL_H_ */ + +/** @} */