From 413fcad68fc516e8f76dac6e1f5988a446af1aa0 Mon Sep 17 00:00:00 2001 From: Chris Vig Date: Sun, 31 Aug 2025 11:14:53 -0500 Subject: [PATCH] Add additional commands to Python interface. - Add commands to get buzzer enablement and frequency. - Add commands to get and set WPM. - Add commands to get and set WPM element scales. - Add commands to get and set paddle mode and invert settings. - Add command to get device softare version. - Add command to enable / disable LEDs. - Add commands to set type and polarity of I/O pins. - Reduce boilerplate. --- scripts/interactive.py | 3 +- scripts/readme.md | 2 + scripts/superkey/interface.py | 313 ++++++++++++---- scripts/superkey/types.py | 103 +++++- src/main/application/config.h | 2 +- src/main/application/intf_port.c | 584 +++++++++++++++++++++++++++--- src/main/application/intf_types.h | 22 +- 7 files changed, 910 insertions(+), 119 deletions(-) diff --git a/scripts/interactive.py b/scripts/interactive.py index f4ccade..16d1aa2 100644 --- a/scripts/interactive.py +++ b/scripts/interactive.py @@ -13,7 +13,8 @@ # ------------------------------------------------------ IMPORTS ------------------------------------------------------- -from superkey import Interface, InteractiveInterface +from superkey.interface import Interface, InteractiveInterface +from superkey.types import * # ----------------------------------------------------- PROCEDURES ----------------------------------------------------- diff --git a/scripts/readme.md b/scripts/readme.md index c41473e..d2bebb4 100644 --- a/scripts/readme.md +++ b/scripts/readme.md @@ -35,3 +35,5 @@ function declared on the `Interface` class. No instance is required. >>> set_buzzer_frequency(800) >>> autokey('cq cq de n0vig n0vig k') ``` + +The `dir()` function may be used to get a list of available functions. diff --git a/scripts/superkey/interface.py b/scripts/superkey/interface.py index dc3b2e7..37c662f 100644 --- a/scripts/superkey/interface.py +++ b/scripts/superkey/interface.py @@ -70,6 +70,13 @@ def __init__(self): super().__init__("Reply: Invalid payload.") +class InvalidValueError(InterfaceError): + """Exception indicating that a `REPLY_INVALID_VALUE` reply was received.""" + def __init__(self): + """Initializes a new instance.""" + super().__init__('Reply: Invalid value.') + + class Interface: """ Class encapsulating the serial interface provided by the SuperKey hardware. @@ -103,79 +110,177 @@ def autokey(self, string: str): """ Sends the `REQUEST_AUTOKEY` command. Queues the specified string to be automatically keyed. """ - # Assemble packet - payload = bytes(string, encoding='ascii') + b'\x00' # null char is not added by default - size = len(payload) - crc = self.__class__.__crc16(payload) - header = self.__class__.__pack_header(MessageID.REQUEST_AUTOKEY, size, crc) + self.__send_packet(MessageID.REQUEST_AUTOKEY, bytes(string, encoding='ascii') + b'\x00') + self.__check_reply_empty() - # Send packet - self.__send(header) - self.__send(payload) - self.__check_empty_reply() + def get_buzzer_enabled(self) -> bool: + """ + Sends the `REQUEST_GET_BUZZER_ENABLED` command. Returns whether the buzzer is enabled or not. + """ + self.__send_packet(MessageID.REQUEST_GET_BUZZER_ENABLED) + return self.__check_reply(' int: + """ + Sends the `REQUEST_GET_BUZZER_FREQUENCY` command. Returns the current buzzer frequency, in Hz. + """ + self.__send_packet(MessageID.REQUEST_GET_BUZZER_FREQUENCY) + return self.__check_reply(' bool: + """ + Sends the `REQUEST_GET_INVERT_PADDLES` command. Returns whether or not the paddles are inverted. + """ + self.__send_packet(MessageID.REQUEST_GET_INVERT_PADDLES) + return self.__check_reply(' IOPolarity: + """ + Sends the `REQUEST_GET_IO_POLARITY` command. Returns the polarity of the specified I/O pin. + """ + self.__send_packet(MessageID.REQUEST_GET_IO_POLARITY, struct.pack(' IOPin: + """ + Sends the `REQUEST_GET_IO_STATE` command. Returns `true` if the specified input / output pin is active. + """ + self.__send_packet(MessageID.REQUEST_GET_IO_STATE, struct.pack(' IOType: + """ + Sends the `REQUEST_GET_IO_STATE_FOR_TYPE` command. Returns `true` if any I/O pin with the specified type is on. + """ + self.__send_packet(MessageID.REQUEST_GET_IO_STATE_FOR_TYPE, struct.pack(' IOType: + """ + Sends the `REQUEST_GET_IO_TYPE` command. Returns the type of the specified I/O pin. + """ + self.__send_packet(MessageID.REQUEST_GET_IO_TYPE, struct.pack(' bool: + """ + Sends the `REQUEST_GET_LED_ENABLED` command. Returns whether or not the specified LED is enabled. + """ + self.__send_packet(MessageID.REQUEST_GET_LED_ENABLED, struct.pack(' PaddleMode: + """ + Sends the `REQUEST_GET_PADDLE_MODE` command. Returns the currently selected paddle mode. + """ + self.__send_packet(MessageID.REQUEST_GET_PADDLE_MODE) + return PaddleMode(self.__check_reply(' float: + """ + Sends the `REQUEST_GET_WPM` command. Returns the current WPM setting. + """ + self.__send_packet(MessageID.REQUEST_GET_WPM) + return self.__check_reply(' float: + """ + Sends the `REQUEST_GET_WPM_SCALE` command. Returns the current WPM scale for the specified code element. + """ + self.__send_packet(MessageID.REQUEST_GET_WPM_SCALE, struct.pack(' str: + """ + Sends the `REQUEST_VERSION` command. Returns the device's version information. + """ + header = self.__class__.__pack_header(MessageID.REQUEST_VERSION) self.__send(header) - self.__send(payload) - self.__check_empty_reply() + return self.__check_reply_str() def __validate_serial(self): """ @@ -184,9 +289,40 @@ def __validate_serial(self): if self.serial is None: raise InterfaceError("The serial port is not open.") - def __check_empty_reply(self) -> bool: + def __send(self, buffer: bytes): """ - Attempts to receive a generic empty reply from the device. + Transmits the specified buffer. + """ + self.__validate_serial() + self.serial.write(buffer) + + def __send_packet(self, message: MessageID, payload: Optional[bytes] = None): + """ + Sends a packet with the specified message ID and payload. + """ + # Get header + size = 0 + crc = 0 + if payload is not None: + size = len(payload) + crc = self.__class__.__crc16(payload) + header = self.__class__.__pack_header(message, size, crc) + + # Send data + self.__send(header) + if payload is not None: + self.__send(payload) + + def __receive(self, size: int): + """ + Receives the specified number of bytes. + """ + self.__validate_serial() + return self.serial.read(size=size) + + def __receive_header(self): + """ + Attempts to receive a header from the serial port. """ # Receive reply and verify we got enough data reply = self.__receive(HEADER_STRUCT_SIZE) @@ -194,11 +330,76 @@ def __check_empty_reply(self) -> bool: raise InterfaceError('No reply received.') # Unpack the header and verify the size and CRC are correct - message, size, crc = self.__class__.__unpack_header(reply) + return self.__class__.__unpack_header(reply) + + def __check_reply(self, format: str) -> Tuple[any, ...]: + """ + Attempts to receive a reply with a payload from the device. + """ + # Unpack header + message, size, crc = self.__receive_header() + + # Receive payload + if size != 0: + payload = self.__receive(size) + if len(payload) != size: + raise InterfaceError('No payload received.') + else: + payload = None + + # Check the message ID + self.__check_reply_message_id(message) + + # Check CRC + if payload is not None and self.__class__.__crc16(payload) != crc: + raise InterfaceError('Reply: Invalid CRC?') + + # Check payload length + if len(payload) != struct.calcsize(format): + raise InterfaceError('Reply: Invalid payload?') + + return struct.unpack(format, payload) + + def __check_reply_empty(self): + """ + Attempts to receive a generic empty reply from the device. + """ + # Unpack message + message, size, crc = self.__receive_header() if size != 0 or crc != 0: raise InterfaceError('Reply: Invalid size / CRC?') # Check the message ID + self.__check_reply_message_id(message) + + def __check_reply_str(self) -> Optional[str]: + """ + Attempts to receive a reply with a string payload from the device. + """ + # Unpack header + message, size, crc = self.__receive_header() + + # Receive payload + if size != 0: + payload = self.__receive(size) + if len(payload) != size: + raise InterfaceError('No payload received.') + else: + return None + + # Check the message ID + self.__check_reply_message_id(message) + + # Check CRC + if self.__class__.__crc16(payload) != crc: + raise InterfaceError('Reply: Invalid CRC?') + + return str(payload, encoding='ascii') + + def __check_reply_message_id(self, message: MessageID): + """ + Throws an exception if the specified message ID represent a failure. + """ if message == MessageID.REPLY_SUCCESS: return # no error elif message == MessageID.REPLY_INVALID_MESSAGE: @@ -209,23 +410,11 @@ def __check_empty_reply(self) -> bool: raise InvalidCRCError() elif message == MessageID.REPLY_INVALID_PAYLOAD: raise InvalidPayloadError() + elif message == MessageID.REPLY_INVALID_VALUE: + raise InvalidValueError() else: raise InterfaceError('Reply: Unknown reply?') - def __send(self, buffer: bytes): - """ - Transmits the specified buffer. - """ - self.__validate_serial() - self.serial.write(buffer) - - def __receive(self, size: int): - """ - Receives the specified number of bytes. - """ - self.__validate_serial() - return self.serial.read(size=size) - @staticmethod def __crc16(buffer: bytes, seed: int = 0xFFFF): """ diff --git a/scripts/superkey/types.py b/scripts/superkey/types.py index e462ca7..0a53c2c 100644 --- a/scripts/superkey/types.py +++ b/scripts/superkey/types.py @@ -14,27 +14,128 @@ # ------------------------------------------------------ EXPORTS ------------------------------------------------------- __all__ = [ + 'CodeElement', + 'IOPin', + 'IOPolarity', + 'IOType', + 'LED', 'MessageID', + 'PaddleMode', ] # ------------------------------------------------------- TYPES -------------------------------------------------------- +# Morse code elements (corresponds to wpm_element_t) +CodeElement = IntEnum( + 'CodeElement', + [ + 'DOT', + 'DASH', + 'ELEMENT_SPACE', + 'LETTER_SPACE', + 'WORD_SPACE' + ], + start = 0 +) + +# I/O pins (correspinds to io_pin_t) +IOPin = IntEnum( + 'IOPin', + [ + 'TRS_0_TIP', + 'TRS_0_RING', + 'TRS_1_TIP', + 'TRS_1_RING', + 'TRS_2_TIP', + 'TRS_2_RING', + 'TRS_3_TIP', + 'TRS_3_RING', + ], + start = 0 +) + +# I/O polarities (corresponds to io_polarity_t) +IOPolarity = IntEnum( + 'IOPolarity', + [ + 'ACTIVE_LOW', + 'ACTIVE_HIGH', + ], + start = 0 +) + +# I/O types (corresponds to io_type_t) +IOType = IntEnum( + 'IOType', + [ + # Inputs + 'INPUT_STRAIGHT_KEY', + 'INPUT_PADDLE_LEFT', + 'INPUT_PADDLE_RIGHT', + # Outputs + 'OUTPUT_KEYER', + ], + start = 0 +) + +# LED IDs (corresponds to led_t) +LED = IntEnum( + 'LED', + [ + 'STATUS', + 'KEY', + ], + start = 0 +) + # Message IDs (corresponds to intf_message_t) MessageID = IntEnum( 'MessageID', [ # Requests 'REQUEST_AUTOKEY', + 'REQUEST_GET_BUZZER_ENABLED', + 'REQUEST_GET_BUZZER_FREQUENCY', + 'REQUEST_GET_INVERT_PADDLES', + 'REQUEST_GET_IO_POLARITY', + 'REQUEST_GET_IO_STATE', + 'REQUEST_GET_IO_STATE_FOR_TYPE', + 'REQUEST_GET_IO_TYPE', + 'REQUEST_GET_LED_ENABLED', + 'REQUEST_GET_PADDLE_MODE', + 'REQUEST_GET_WPM', + 'REQUEST_GET_WPM_SCALE', 'REQUEST_PANIC', 'REQUEST_PING', 'REQUEST_RESTORE_DEFAULT_CONFIG', 'REQUEST_SET_BUZZER_ENABLED', 'REQUEST_SET_BUZZER_FREQUENCY', + 'REQUEST_SET_INVERT_PADDLES', + 'REQUEST_SET_IO_POLARITY', + 'REQUEST_SET_IO_TYPE', + 'REQUEST_SET_LED_ENABLED', + 'REQUEST_SET_PADDLE_MODE', + 'REQUEST_SET_WPM', + 'REQUEST_SET_WPM_SCALE', + 'REQUEST_VERSION', # Replies 'REPLY_SUCCESS', 'REPLY_INVALID_MESSAGE', 'REPLY_INVALID_SIZE', 'REPLY_INVALID_CRC', 'REPLY_INVALID_PAYLOAD', + 'REPLY_INVALID_VALUE', + ], + start = 0 +) + +# Paddle modes (corresponds to keyer_paddle_mode_t) +PaddleMode = IntEnum( + 'PaddleMode', + [ + 'IAMBIC', + 'ULTIMATIC', + 'ULTIMATIC_ALTERNATE', ], - start = 0) + start = 0 +) diff --git a/src/main/application/config.h b/src/main/application/config.h index 3d40c44..46b9f71 100644 --- a/src/main/application/config.h +++ b/src/main/application/config.h @@ -56,7 +56,7 @@ typedef struct /** If set to `true`, the keyer will emit dashes from the left paddle and dots from the right paddle. */ bool keyer_paddle_invert; -} config_t; +} __attribute__((packed)) config_t; /** * @typedef config_version_t diff --git a/src/main/application/intf_port.c b/src/main/application/intf_port.c index 3c5698a..28dea7a 100644 --- a/src/main/application/intf_port.c +++ b/src/main/application/intf_port.c @@ -9,15 +9,22 @@ /* ---------------------------------------------------- INCLUDES ---------------------------------------------------- */ +#include +#include + #include "application/buzzer.h" #include "application/config.h" #include "application/intf_port.h" #include "application/intf_types.h" #include "application/keyer.h" +#include "application/led.h" +#include "application/wpm.h" #include "core/sys.h" +#include "core/version.h" #include "drivers/usart.h" #include "utility/constants.h" #include "utility/crc.h" +#include "utility/debug.h" #include "utility/types.h" /* --------------------------------------------------- CONSTANTS ---------------------------------------------------- */ @@ -49,6 +56,39 @@ static tick_t s_rx_tick = 0; /**< Tick of most recent byte. #define rx_buf_avail() \ ( RX_BUF_SIZE - s_rx_count ) +/** + * @def VALIDATE_ENUM_OR_BAIL( _value, _count ) + * @brief Sends an error packet and returns if the specified enum value is invalid. + */ +#define VALIDATE_ENUM_OR_BAIL( _value, _count ) \ + if( ( _value ) >= ( _count ) ) \ + { \ + send_empty_packet( INTF_MESSAGE_REPLY_INVALID_VALUE ); \ + return; \ + } + +/** + * @def VALIDATE_PAYLOAD_SIZE_OR_BAIL( _size ) + * @brief Sends an error packet and returns if the payload does not have the specified size. + */ +#define VALIDATE_PAYLOAD_SIZE_OR_BAIL( _size ) \ + if( header->size != ( _size ) ) \ + { \ + send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); \ + return; \ + } + +/** + * @def VALIDATE_RANGE_OR_BAIL( _value, _min, _max ) + * @brief Sends an error packet and returns if the specifed value is outside of the specified minimum / maximum. + */ +#define VALIDATE_RANGE_OR_BAIL( _value, _min, _max ) \ + if( ( _value ) < ( _min ) || ( _value ) > ( _max ) ) \ + { \ + send_empty_packet( INTF_MESSAGE_REPLY_INVALID_VALUE ); \ + return; \ + } + /* ---------------------------------------------- PROCEDURE PROTOTYPES ---------------------------------------------- */ /** @@ -69,6 +109,72 @@ static void process_message( intf_header_t const * header, void const * payload */ static void process_message_request_autokey( intf_header_t const * header, void const * payload ); +/** + * @fn process_message_request_get_buzzer_enabled( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED` message ID. + */ +static void process_message_request_get_buzzer_enabled( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_buzzer_frequency( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_BUZZER_FREQUENCY` message ID. + */ +static void process_message_request_get_buzzer_frequency( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_invert_paddles( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_INVERT_PADDLES` message ID. + */ +static void process_message_request_get_invert_paddles( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_io_polarity( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_IO_POLARITY` message ID. + */ +static void process_message_request_get_io_polarity( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_io_state( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_IO_STATE` message ID. + */ +static void process_message_request_get_io_state( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_io_state_for_type( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_IO_STATE_FOR_TYPE` message ID. + */ +static void process_message_request_get_io_state_for_type( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_io_type( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_IO_TYPE` message ID. + */ +static void process_message_request_get_io_type( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_led_enabled( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_LED_ENABLED` message ID. + */ +static void process_message_request_get_led_enabled( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_paddle_mode( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_PADDLE_MODE` message ID. + */ +static void process_message_request_get_paddle_mode( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_wpm( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_WPM` message ID. + */ +static void process_message_request_get_wpm( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_get_wpm_scale( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_GET_WPM_SCALE` message ID. + */ +static void process_message_request_get_wpm_scale( intf_header_t const * header, void const * payload ); + /** * @fn process_message_request_panic( intf_header_t const *, void const * ) * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_PANIC` message ID. @@ -99,12 +205,66 @@ static void process_message_request_set_buzzer_enabled( intf_header_t const * he */ static void process_message_request_set_buzzer_frequency( intf_header_t const * header, void const * payload ); +/** + * @fn process_message_request_set_invert_paddles( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_INVERT_PADDLES` message ID. + */ +static void process_message_request_set_invert_paddles( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_io_polarity( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_IO_POLARITY` message ID. + */ +static void process_message_request_set_io_polarity( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_io_type( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_IO_TYPE` message ID. + */ +static void process_message_request_set_io_type( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_led_enabled( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_LED_ENABLED` message ID. + */ +static void process_message_request_set_led_enabled( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_paddle_mode( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_PADDLE_MODE` message ID. + */ +static void process_message_request_set_paddle_mode( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_wpm( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_sET_WPM` message ID. + */ +static void process_message_request_set_wpm( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_set_wpm_scale( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_SET_WPM_SCALE` message ID. + */ +static void process_message_request_set_wpm_scale( intf_header_t const * header, void const * payload ); + +/** + * @fn process_message_request_version( intf_header_t const *, void const * ) + * @brief Processes the specified interface message with the `INTF_MESSAGE_REQUEST_VERSION` message ID. + */ +static void process_message_request_version( intf_header_t const * header, void const * payload ); + /** * @fn send_empty_packet( intf_message_t ) * @brief Sends a packet with the specified message ID and no payload. */ static void send_empty_packet( intf_message_t message ); +/** + * @fn send_packet( intf_message_t message, void *, size_t ) + * @brief Sends a packet with the specified message ID and payload. + */ +static void send_packet( intf_message_t message, void * payload, size_t size ); + /* --------------------------------------------------- PROCEDURES --------------------------------------------------- */ void intf_port_init( void ) @@ -214,6 +374,50 @@ static void process_message( intf_header_t const * header, void const * payload process_message_request_autokey( header, payload ); break; + case INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED: + process_message_request_get_buzzer_enabled( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_BUZZER_FREQUENCY: + process_message_request_get_buzzer_frequency( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_INVERT_PADDLES: + process_message_request_get_invert_paddles( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_IO_POLARITY: + process_message_request_get_io_polarity( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_IO_STATE: + process_message_request_get_io_state( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_IO_STATE_FOR_TYPE: + process_message_request_get_io_state_for_type( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_IO_TYPE: + process_message_request_get_io_type( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_LED_ENABLED: + process_message_request_get_led_enabled( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_PADDLE_MODE: + process_message_request_get_paddle_mode( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_WPM: + process_message_request_get_wpm( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_GET_WPM_SCALE: + process_message_request_get_wpm_scale( header, payload ); + break; + case INTF_MESSAGE_REQUEST_PANIC: process_message_request_panic( header, payload ); break; @@ -234,6 +438,38 @@ static void process_message( intf_header_t const * header, void const * payload process_message_request_set_buzzer_frequency( header, payload ); break; + case INTF_MESSAGE_REQUEST_SET_INVERT_PADDLES: + process_message_request_set_invert_paddles( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_IO_POLARITY: + process_message_request_set_io_polarity( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_IO_TYPE: + process_message_request_set_io_type( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_LED_ENABLED: + process_message_request_set_led_enabled( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_PADDLE_MODE: + process_message_request_set_paddle_mode( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_WPM: + process_message_request_set_wpm( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_SET_WPM_SCALE: + process_message_request_set_wpm_scale( header, payload ); + break; + + case INTF_MESSAGE_REQUEST_VERSION: + process_message_request_version( header, payload ); + break; + default: // Unknown message? send_empty_packet( INTF_MESSAGE_REPLY_INVALID_MESSAGE ); @@ -262,21 +498,147 @@ static void process_message_request_autokey( intf_header_t const * header, void } /* process_message_request_autokey() */ -static void process_message_request_panic( intf_header_t const * header, void const * payload ) +static void process_message_request_get_buzzer_enabled( intf_header_t const * header, void const * payload ) { ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); - // Validate size - if( header->size != 0 ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } + bool enabled = buzzer_get_enabled(); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & enabled, sizeof( enabled ) ); - // Stop the keyer - keyer_panic(); +} /* process_message_request_get_buzzer_enabled() */ - // Send reply + +static void process_message_request_get_buzzer_frequency( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + buzzer_freq_t freq = buzzer_get_frequency(); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & freq, sizeof( freq ) ); + +} /* process_message_request_get_buzzer_frequency() */ + + +static void process_message_request_get_invert_paddles( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + bool inverted = keyer_get_paddle_invert(); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & inverted, sizeof( inverted ) ); + +} /* process_message_request_get_invert_paddles() */ + + +static void process_message_request_get_paddle_mode( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + keyer_paddle_mode_t mode = keyer_get_paddle_mode(); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & mode, sizeof( mode ) ); + +} /* process_message_request_get_paddle_mode() */ + + +static void process_message_request_get_io_polarity( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( io_pin_t ) ); + + io_pin_t pin = *( ( io_pin_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( pin, IO_PIN_COUNT ); + + io_polarity_t polarity = io_get_polarity( pin ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & polarity, sizeof( polarity ) ); + +} /* process_message_request_get_io_polarity() */ + + +static void process_message_request_get_io_state( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( io_pin_t ) ); + + io_pin_t pin = *( ( io_pin_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( pin, IO_PIN_COUNT ); + + io_state_t state = io_get_state( pin ); + bool state_is_on = ( state == IO_STATE_ON ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & state_is_on, sizeof( state_is_on ) ); + +} /* process_message_request_get_io_state() */ + + +static void process_message_request_get_io_state_for_type( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( io_type_t ) ); + + io_type_t type = *( ( io_type_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( type, IO_TYPE_COUNT ); + + io_state_t state = io_get_state_type( type ); + bool state_is_on = ( state == IO_STATE_ON ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & state_is_on, sizeof( state_is_on ) ); + +} /* process_message_request_get_io_state_for_type() */ + + +static void process_message_request_get_io_type( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( io_pin_t ) ); + + io_pin_t pin = *( ( io_pin_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( pin, IO_PIN_COUNT ); + + io_type_t type = io_get_type( pin ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & type, sizeof( type ) ); + +} /* process_message_request_get_io_type() */ + + +static void process_message_request_get_led_enabled( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( led_t ) ); + + led_t led = *( ( led_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( led, LED_COUNT ); + + bool enabled = led_get_enabled( led ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & enabled, sizeof( enabled ) ); + +} /* process_message_request_get_paddle_mode() */ + + +static void process_message_request_get_wpm( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + wpm_t wpm = wpm_get(); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & wpm, sizeof( wpm ) ); + +} /* process_message_request_get_wpm() */ + + +static void process_message_request_get_wpm_scale( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( wpm_element_t ) ); + + wpm_element_t el = *( ( wpm_element_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( el, WPM_ELEMENT_COUNT ); + + wpm_element_scale_t scale = wpm_get_element_scale( el ); + send_packet( INTF_MESSAGE_REPLY_SUCCESS, & scale, sizeof( wpm_element_scale_t ) ); + +} /* process_message_request_get_wpm_scale() */ + + +static void process_message_request_panic( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + keyer_panic(); send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); } /* process_message_request_panic() */ @@ -285,18 +647,9 @@ static void process_message_request_panic( intf_header_t const * header, void co static void process_message_request_ping( intf_header_t const * header, void const * payload ) { ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); - // Validate size - if( header->size != 0 ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } - - // Send a friendly greeting to the user keyer_autokey_str( "73ee" ); - - // Send reply send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); } /* process_message_request_ping() */ @@ -305,20 +658,11 @@ static void process_message_request_ping( intf_header_t const * header, void con static void process_message_request_restore_default_config( intf_header_t const * header, void const * payload ) { ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); - // Validate size - if( header->size != 0 ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } - - // Restore default configuration config_t config; config_default( & config ); config_set( & config ); - - // Send reply send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); } /* process_message_request_restore_default_config() */ @@ -326,18 +670,10 @@ static void process_message_request_restore_default_config( intf_header_t const static void process_message_request_set_buzzer_enabled( intf_header_t const * header, void const * payload ) { - // Validate size - if( header->size != sizeof( bool ) ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( bool ) ); - // Set buzzer enablement bool enabled = *( ( bool const * )payload ); buzzer_set_enabled( enabled ); - - // Send reply send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); } /* process_message_request_set_buzzer_enabled() */ @@ -345,28 +681,157 @@ static void process_message_request_set_buzzer_enabled( intf_header_t const * he static void process_message_request_set_buzzer_frequency( intf_header_t const * header, void const * payload ) { - // Validate size - if( header->size != sizeof( buzzer_freq_t ) ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( buzzer_freq_t ) ); - // Set buzzer frequency buzzer_freq_t freq = *( ( buzzer_freq_t const * )payload ); - if( freq < BUZZER_MINIMUM_FREQUENCY || freq > BUZZER_MAXIMUM_FREQUENCY ) - { - send_empty_packet( INTF_MESSAGE_REPLY_INVALID_PAYLOAD ); - return; - } - buzzer_set_frequency( freq ); + VALIDATE_RANGE_OR_BAIL( freq, BUZZER_MINIMUM_FREQUENCY, BUZZER_MAXIMUM_FREQUENCY ); - // Send reply + buzzer_set_frequency( freq ); send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); } /* process_message_request_set_buzzer_frequency() */ +static void process_message_request_set_invert_paddles( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( bool ) ); + + bool inverted = *( ( bool const * )payload ); + keyer_set_paddle_invert( inverted ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_invert_paddles() */ + + +static void process_message_request_set_io_polarity( intf_header_t const * header, void const * payload ) +{ + typedef struct + { + io_pin_t pin; + io_polarity_t polarity; + } format_t; + + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( format_t ) ); + + format_t const * pkt = ( format_t const * )payload; + VALIDATE_ENUM_OR_BAIL( pkt->pin, IO_PIN_COUNT ); + VALIDATE_ENUM_OR_BAIL( pkt->polarity, IO_POLARITY_COUNT ); + + io_set_polarity( pkt->pin, pkt->polarity ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_io_polarity() */ + + +static void process_message_request_set_io_type( intf_header_t const * header, void const * payload ) +{ + typedef struct + { + io_pin_t pin; + io_type_t type; + } format_t; + + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( format_t ) ); + + format_t const * pkt = ( format_t const * )payload; + VALIDATE_ENUM_OR_BAIL( pkt->pin, IO_PIN_COUNT ); + VALIDATE_ENUM_OR_BAIL( pkt->type, IO_TYPE_COUNT ); + + io_set_type( pkt->pin, pkt->type ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_io_type() */ + + +static void process_message_request_set_led_enabled( intf_header_t const * header, void const * payload ) +{ + typedef struct + { + led_t led; + bool enabled; + } format_t; + + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( format_t ) ); + + format_t const * pkt = ( format_t const * )payload; + VALIDATE_ENUM_OR_BAIL( pkt->led, LED_COUNT ); + + led_set_enabled( pkt->led, pkt->enabled ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_paddle_mode() */ + + +static void process_message_request_set_paddle_mode( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( keyer_paddle_mode_t ) ); + + keyer_paddle_mode_t mode = *( ( keyer_paddle_mode_t const * )payload ); + VALIDATE_ENUM_OR_BAIL( mode, KEYER_PADDLE_MODE_COUNT ); + + keyer_set_paddle_mode( mode ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_paddle_mode() */ + + +static void process_message_request_set_wpm( intf_header_t const * header, void const * payload ) +{ + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( wpm_t ) ); + + wpm_t wpm = *( ( wpm_t const * )payload ); + VALIDATE_RANGE_OR_BAIL( wpm, WPM_MINIMUM, WPM_MAXIMUM ); + + wpm_set( wpm ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_wpm() */ + + +static void process_message_request_set_wpm_scale( intf_header_t const * header, void const * payload ) +{ + typedef struct + { + wpm_element_t el; + wpm_element_scale_t scale; + } format_t; + + VALIDATE_PAYLOAD_SIZE_OR_BAIL( sizeof( format_t ) ); + + format_t const * pkt = ( format_t const * )payload; + VALIDATE_ENUM_OR_BAIL( pkt->el, WPM_ELEMENT_COUNT ); + VALIDATE_RANGE_OR_BAIL( pkt->scale, WPM_ELEMENT_SCALE_MINIMUM, WPM_ELEMENT_SCALE_MAXIMUM ); + + wpm_set_element_scale( pkt->el, pkt->scale ); + send_empty_packet( INTF_MESSAGE_REPLY_SUCCESS ); + +} /* process_message_request_set_wpm_scale() */ + + +static void process_message_request_version( intf_header_t const * header, void const * payload ) +{ + ( void )payload; + VALIDATE_PAYLOAD_SIZE_OR_BAIL( 0 ); + + version_t version; + version_get( & version ); + #define BUF_SZ 128 + char buf[ BUF_SZ ]; + size_t count = snprintf( buf, BUF_SZ, "%s v%s (%s [%s] %s %s)", + version.product_name, + version.version, + version.git_branch, + version.git_hash_short, + version.build_date, + version.build_time ); + assert_always( count <= BUF_SZ ); + #undef BUF_SZ + + send_packet( INTF_MESSAGE_REPLY_SUCCESS, buf, count ); + +} /* process_message_request_version() */ + + static void send_empty_packet( intf_message_t message ) { intf_header_t header; @@ -377,3 +842,16 @@ static void send_empty_packet( intf_message_t message ) usart_tx( INTF_PORT_USART, ( byte_t * ) & header, sizeof( intf_header_t ), USART_WAIT_MODE_NORMAL ); } /* send_empty_packet() */ + + +static void send_packet( intf_message_t message, void * payload, size_t size ) +{ + intf_header_t header; + header.message = message; + header.size = size; + header.crc = ( uint16_t )crc_calc_crc16( payload, size ); + + usart_tx( INTF_PORT_USART, ( byte_t * ) & header, sizeof( intf_header_t ), USART_WAIT_MODE_NORMAL ); + usart_tx( INTF_PORT_USART, ( byte_t * ) payload, size, USART_WAIT_MODE_NORMAL ); + +} /* send_packet() */ diff --git a/src/main/application/intf_types.h b/src/main/application/intf_types.h index 783e45a..b808795 100644 --- a/src/main/application/intf_types.h +++ b/src/main/application/intf_types.h @@ -39,17 +39,37 @@ typedef uint16_t intf_message_t; enum { INTF_MESSAGE_REQUEST_AUTOKEY, /**< Queues a string to be autokeyed. */ + INTF_MESSAGE_REQUEST_GET_BUZZER_ENABLED,/**< Get buzzer enablement. */ + INTF_MESSAGE_REQUEST_GET_BUZZER_FREQUENCY,/**< Get buzzer frequency. */ + INTF_MESSAGE_REQUEST_GET_INVERT_PADDLES,/**< Gets paddle inversion setting. */ + INTF_MESSAGE_REQUEST_GET_IO_POLARITY, /**< Gets I/O pin polarity. */ + INTF_MESSAGE_REQUEST_GET_IO_STATE, /**< Gets I/O pin state. */ + INTF_MESSAGE_REQUEST_GET_IO_STATE_FOR_TYPE,/**< Gets I/O type state. */ + INTF_MESSAGE_REQUEST_GET_IO_TYPE, /**< Gets I/O pin type. */ + INTF_MESSAGE_REQUEST_GET_LED_ENABLED, /**< Gets enablement for LED. */ + INTF_MESSAGE_REQUEST_GET_PADDLE_MODE, /**< Gets the paddle mode. */ + INTF_MESSAGE_REQUEST_GET_WPM, /**< Get WPM setting. */ + INTF_MESSAGE_REQUEST_GET_WPM_SCALE, /**< Get element WPM scale. */ INTF_MESSAGE_REQUEST_PANIC, /**< Immediately stop the keyer. */ INTF_MESSAGE_REQUEST_PING, /**< Check if device is alive. */ INTF_MESSAGE_REQUEST_RESTORE_DEFAULT_CONFIG,/**< Restores default configuration. */ INTF_MESSAGE_REQUEST_SET_BUZZER_ENABLED,/**< Enable or disable buzzer. */ INTF_MESSAGE_REQUEST_SET_BUZZER_FREQUENCY,/**< Set buzzer frequency. */ + INTF_MESSAGE_REQUEST_SET_INVERT_PADDLES,/**< Sets paddle inversion setting. */ + INTF_MESSAGE_REQUEST_SET_IO_POLARITY, /**< Sets I/O pin polarity. */ + INTF_MESSAGE_REQUEST_SET_IO_TYPE, /**< Sets I/O pin type. */ + INTF_MESSAGE_REQUEST_SET_LED_ENABLED, /**< Sets enablement for LED. */ + INTF_MESSAGE_REQUEST_SET_PADDLE_MODE, /**< Sets the paddle mode. */ + INTF_MESSAGE_REQUEST_SET_WPM, /**< Set WPM. */ + INTF_MESSAGE_REQUEST_SET_WPM_SCALE, /**< Set WPM scale. */ + INTF_MESSAGE_REQUEST_VERSION, /**< Get application version information. */ INTF_MESSAGE_REPLY_SUCCESS, /**< Command successful / acknowledged. */ INTF_MESSAGE_REPLY_INVALID_MESSAGE, /**< Request was invalid. */ INTF_MESSAGE_REPLY_INVALID_SIZE, /**< Size was invalid. */ INTF_MESSAGE_REPLY_INVALID_CRC, /**< CRC was invalid. */ INTF_MESSAGE_REPLY_INVALID_PAYLOAD, /**< Payload was invalid. */ + INTF_MESSAGE_REPLY_INVALID_VALUE, /**< A specified value was invalid. */ INTF_MESSAGE_COUNT, /**< Number of valid message IDs. */ }; @@ -65,6 +85,6 @@ typedef struct uint16_t size; /**< Total size of message payload. */ uint16_t crc; /**< 16-bit CRC of message payload. */ -} intf_header_t; +} __attribute__((packed)) intf_header_t; #endif /* !defined( APPLICATION_INTF_TYPES_H ) */