From e24de0350a0864aaaf2686909c0dd13edef8d62d Mon Sep 17 00:00:00 2001 From: Adrian Del Grosso <10929341+ad3154@users.noreply.github.com> Date: Sat, 11 May 2024 17:45:25 -0600 Subject: [PATCH] [VT]: Added a base class for VT object pools Added a base class for VT object pools so that common information can be shared between VT servers and a VT designer application without duplicating our IOP parser. Added missing doxygen. --- isobus/CMakeLists.txt | 2 + .../isobus/isobus_virtual_terminal_base.hpp | 2 + .../isobus/isobus_virtual_terminal_server.hpp | 106 +- ...al_terminal_server_managed_working_set.hpp | 68 +- ...obus_virtual_terminal_working_set_base.hpp | 101 + ...al_terminal_server_managed_working_set.cpp | 3209 ---------------- ...obus_virtual_terminal_working_set_base.cpp | 3227 +++++++++++++++++ 7 files changed, 3436 insertions(+), 3279 deletions(-) create mode 100644 isobus/include/isobus/isobus/isobus_virtual_terminal_working_set_base.hpp create mode 100644 isobus/src/isobus_virtual_terminal_working_set_base.cpp diff --git a/isobus/CMakeLists.txt b/isobus/CMakeLists.txt index 8903d8d98..fba6d27b1 100644 --- a/isobus/CMakeLists.txt +++ b/isobus/CMakeLists.txt @@ -50,6 +50,7 @@ set(ISOBUS_SRC "isobus_device_descriptor_object_pool_helpers.cpp" "can_message_data.cpp" "isobus_virtual_terminal_server.cpp" + "isobus_virtual_terminal_working_set_base.cpp" "isobus_virtual_terminal_server_managed_working_set.cpp") # Prepend the source directory path to all the source files @@ -103,6 +104,7 @@ set(ISOBUS_INCLUDE "can_message_data.hpp" "isobus_virtual_terminal_base.hpp" "isobus_virtual_terminal_server.hpp" + "isobus_virtual_terminal_working_set_base.hpp" "isobus_virtual_terminal_server_managed_working_set.hpp") # Prepend the include directory path to all the include files diff --git a/isobus/include/isobus/isobus/isobus_virtual_terminal_base.hpp b/isobus/include/isobus/isobus/isobus_virtual_terminal_base.hpp index 60551b7c6..2e7e194c8 100644 --- a/isobus/include/isobus/isobus/isobus_virtual_terminal_base.hpp +++ b/isobus/include/isobus/isobus/isobus_virtual_terminal_base.hpp @@ -13,6 +13,7 @@ namespace isobus { + /// @brief A base class for the VT client and VT server that stores common definitions class VirtualTerminalBase { public: @@ -267,6 +268,7 @@ namespace isobus /// @brief Allows easy comparison of two `AssignedAuxiliaryFunction` objects /// @param[in] other the object to compare against + /// @returns true if the two objects are equal, otherwise false bool operator==(const AssignedAuxiliaryFunction &other) const; std::uint16_t functionObjectID; ///< The object ID of the function present in our object pool diff --git a/isobus/include/isobus/isobus/isobus_virtual_terminal_server.hpp b/isobus/include/isobus/isobus/isobus_virtual_terminal_server.hpp index 0be9b2e9e..d3e0963a7 100644 --- a/isobus/include/isobus/isobus/isobus_virtual_terminal_server.hpp +++ b/isobus/include/isobus/isobus/isobus_virtual_terminal_server.hpp @@ -118,23 +118,69 @@ namespace isobus void process_macro(std::shared_ptr object, isobus::EventID macroEvent, isobus::VirtualTerminalObjectType targetObjectType, std::shared_ptr workingset); // ----------- Mandatory Functions you must implement ----------------------- + + /// @brief This function is called when the client wants to know if the server has enough memory to store the object pool. + /// You should return true if the server has enough memory to store the object pool, otherwise false. + /// @param[in] requestedMemory The amount of memory requested by the client + /// @returns True if the server has enough memory to store the object pool, otherwise false virtual bool get_is_enough_memory(std::uint32_t requestedMemory) const = 0; + + /// @brief This function is called when the client wants to know the version of the VT. + /// @returns The version of the VT virtual VTVersion get_version() const = 0; + + /// @brief This function is called when the interface wants to know the number of navigation soft keys. + /// @returns The number of navigation soft keys virtual std::uint8_t get_number_of_navigation_soft_keys() const = 0; + + /// @brief This function is called when the interface needs to know the number of x pixels (width) of your soft keys + /// @returns The number of x pixels (width) of your soft keys virtual std::uint8_t get_soft_key_descriptor_x_pixel_width() const = 0; + + /// @brief This function is called when the interface needs to know the number of y pixels (height) of your soft keys + /// @returns The number of y pixels (height) of your soft keys virtual std::uint8_t get_soft_key_descriptor_y_pixel_width() const = 0; + + /// @brief This function is called when the interface needs to know the number of possible virtual soft keys in your soft key mask render area + /// @returns The number of possible virtual soft keys in your soft key mask render area virtual std::uint8_t get_number_of_possible_virtual_soft_keys_in_soft_key_mask() const = 0; + + /// @brief This function is called when the interface needs to know the number of physical soft keys + /// @returns The number of physical soft keys virtual std::uint8_t get_number_of_physical_soft_keys() const = 0; + + /// @brief This function is called when the interface needs to know the number of x pixels (width) of your data key mask render area + /// @returns The number of x pixels (width) of your soft key mask render area virtual std::uint16_t get_data_mask_area_size_x_pixels() const = 0; + + /// @brief This function is called when the interface needs to know the number of y pixels (height) of your data key mask render area + /// @returns The number of y pixels (height) of your data key mask render area virtual std::uint16_t get_data_mask_area_size_y_pixels() const = 0; + + /// @brief The interface calls this function when it wants you to discontinue/suspend a working set + /// @param[in] workingSetWithError The working set to suspend virtual void suspend_working_set(std::shared_ptr workingSetWithError) = 0; + + /// @brief This function is called when the interface needs to know the wide chars you support + /// @param[in] codePlane The code plane to inquire about + /// @param[in] firstWideCharInInquiryRange The first wide char in the inquiry range + /// @param[in] lastWideCharInInquiryRange The last wide char in the inquiry range + /// @param[out] numberOfRanges The number of wide char ranges supported + /// @param[out] wideCharRangeArray The wide char range array + /// @returns The error code for the supported wide chars inquiry virtual SupportedWideCharsErrorCode get_supported_wide_chars(std::uint8_t codePlane, std::uint16_t firstWideCharInInquiryRange, std::uint16_t lastWideCharInInquiryRange, std::uint8_t &numberOfRanges, std::vector &wideCharRangeArray) = 0; + /// @brief This function is called when the interface needs to know what versions of object pools are available for a client. + /// @param[in] clientNAME The client requesting the object pool versions + /// @returns A vector of object pool versions available for the client virtual std::vector> get_versions(NAME clientNAME) = 0; + + /// @brief This function is called when the interface needs to know what objects are supported by the server. + /// @returns A vector of supported objects virtual std::vector get_supported_objects() const = 0; /// @brief This function is called when the client wants the server to load a previously stored object pool. @@ -182,14 +228,38 @@ namespace isobus virtual bool delete_object_pool(NAME clientNAME) = 0; //------------ Optional functions you can override -------------------- + + /// @brief If you want to override the graphics mode from its default 256 color mode, you can override this function. + /// Though, that would be unusual. + /// @returns The graphic mode of the VT to report to clients virtual VirtualTerminalBase::GraphicMode get_graphic_mode() const; + + /// @brief If you want to override the amount of time the VT reports it takes to power up, you can override this function. + /// @returns The amount of time the VT reports it takes to power up, or 255 if it is not known virtual std::uint8_t get_powerup_time() const; + + /// @brief By default, the VT server will report that it supports all small and large fonts. + /// If you want to override this, you can override this function. + /// @returns The bitfield of supported small fonts virtual std::uint8_t get_supported_small_fonts_bitfield() const; + + /// @brief By default, the VT server will report that it supports all small and large fonts. + /// If you want to override this, you can override this function. + /// @returns The bitfield of supported large fonts virtual std::uint8_t get_supported_large_fonts_bitfield() const; //-------------- Callbacks/Event driven interface --------------------- + + /// @brief Returns the event dispatcher for repaint events + /// @returns The event dispatcher for repaint events EventDispatcher> &get_on_repaint_event_dispatcher(); + + /// @brief Returns the event dispatcher for change active mask events + /// @returns The event dispatcher for change active mask events EventDispatcher, std::uint16_t, std::uint16_t> &get_on_change_active_mask_event_dispatcher(); + + /// @brief Returns the event dispatcher for when an object is focused + /// @returns The event dispatcher for when an object is focused EventDispatcher, std::uint16_t, bool> &get_on_focus_object_event_dispatcher(); //----------------- Other Server Settings ----------------------------- @@ -363,6 +433,7 @@ namespace isobus AnyOtherError = 2 }; + /// @brief Enumerates the bit indices of the error fields that can be set in a delete object pool response enum class DeleteObjectPoolErrorBit : std::uint8_t { DeletionError = 0, @@ -371,6 +442,8 @@ namespace isobus /// @brief Checks to see if the message should be listened to based on /// what the message is, and if the client has sent the proper working set master message + /// @param[in] message The CAN message to check + /// @returns true if the source of the message is in a valid, managed state by our server, otherwise false bool check_if_source_is_managed(const CANMessage &message); /// @brief Processes a macro's execution synchronously as if it were a CAN message. @@ -397,7 +470,7 @@ namespace isobus /// @brief Processes a CAN message from any VT client /// @param[in] message The CAN message being received - /// @param[in] parentPointer A context variable to find the relevant VT server class + /// @param[in] parent A context variable to find the relevant VT server class static void process_rx_message(const CANMessage &message, void *parent); /// @brief Sends a message using the acknowledgement PGN @@ -449,7 +522,6 @@ namespace isobus /// @brief Sends a response to a change fill attributes command /// @param[in] objectID The object ID for the object to change - /// @param[in] newObjectID The object ID for the object to place at the specified list index, or NULL_OBJECT_ID (0xFFFF) /// @param[in] errorBitfield An error bitfield /// @param[in] destination The control function to send the message to /// @returns true if the message was sent, otherwise false @@ -464,6 +536,7 @@ namespace isobus /// @brief Sends a response to a change list item command /// @param[in] objectID The object ID for the object to change + /// @param[in] newObjectID The object ID for the object to place at the specified list index, or NULL_OBJECT_ID (0xFFFF) /// @param[in] errorBitfield An error bitfield /// @param[in] listIndex The list index to change, numbered 0 to n /// @param[in] destination The control function to send the message to @@ -537,6 +610,7 @@ namespace isobus /// @param[in] parentIDOfFaultingObject The parent object ID for the faulty object, or NULL_OBJECT_ID /// @param[in] faultingObjectID The faulty object's ID or the NULL_OBJECT_ID /// @param[in] errorCodes A bitfield of error codes that describe the issues with the pool + /// @param[in] destination The control function to send the message to /// @returns true if the message was sent, otherwise false bool send_end_of_object_pool_response(bool success, std::uint16_t parentIDOfFaultingObject, @@ -593,20 +667,20 @@ namespace isobus static constexpr std::uint8_t VERSION_LABEL_LENGTH = 7; ///< The length of a standard object pool version label - EventDispatcher> onRepaintEventDispatcher; - EventDispatcher, std::uint16_t, std::uint16_t> onChangeActiveMaskEventDispatcher; - EventDispatcher, std::uint16_t, bool> onFocusObjectEventDispatcher; - LanguageCommandInterface languageCommandInterface; - std::shared_ptr serverInternalControlFunction; - std::vector> managedWorkingSetList; - std::shared_ptr activeWorkingSet; - std::uint32_t statusMessageTimestamp_ms = 0; - std::uint16_t activeWorkingSetDataMaskObjectID = NULL_OBJECT_ID; - std::uint16_t activeWorkingSetSoftkeyMaskObjectID = NULL_OBJECT_ID; - std::uint8_t activeWorkingSetMasterAddress = NULL_CAN_ADDRESS; - std::uint8_t busyCodesBitfield = 0; - std::uint8_t currentCommandFunctionCode = 0; - bool initialized = false; + EventDispatcher> onRepaintEventDispatcher; ///< Event dispatcher for repaint events + EventDispatcher, std::uint16_t, std::uint16_t> onChangeActiveMaskEventDispatcher; ///< Event dispatcher for active mask change events + EventDispatcher, std::uint16_t, bool> onFocusObjectEventDispatcher; ///< Event dispatcher for focus object events + LanguageCommandInterface languageCommandInterface; ///< The language command interface for the server + std::shared_ptr serverInternalControlFunction; ///< The internal control function for the server + std::vector> managedWorkingSetList; ///< The list of managed working sets + std::shared_ptr activeWorkingSet; ///< The active working set + std::uint32_t statusMessageTimestamp_ms = 0; ///< The timestamp of the last status message sent + std::uint16_t activeWorkingSetDataMaskObjectID = NULL_OBJECT_ID; ///< The object ID of the active working set's data mask + std::uint16_t activeWorkingSetSoftkeyMaskObjectID = NULL_OBJECT_ID; ///< The object ID of the active working set's soft key mask + std::uint8_t activeWorkingSetMasterAddress = NULL_CAN_ADDRESS; ///< The address of the active working set's master + std::uint8_t busyCodesBitfield = 0; ///< The busy codes bitfield + std::uint8_t currentCommandFunctionCode = 0; ///< The current command function code being processed + bool initialized = false; ///< True if the server has been initialized, otherwise false }; } // namespace isobus #endif //ISOBUS_VIRTUAL_TERMINAL_SERVER_HPP diff --git a/isobus/include/isobus/isobus/isobus_virtual_terminal_server_managed_working_set.hpp b/isobus/include/isobus/isobus/isobus_virtual_terminal_server_managed_working_set.hpp index 64b9b1c38..e7f77f776 100644 --- a/isobus/include/isobus/isobus/isobus_virtual_terminal_server_managed_working_set.hpp +++ b/isobus/include/isobus/isobus/isobus_virtual_terminal_server_managed_working_set.hpp @@ -17,6 +17,7 @@ #include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_control_function.hpp" #include "isobus/isobus/isobus_virtual_terminal_objects.hpp" +#include "isobus/isobus/isobus_virtual_terminal_working_set_base.hpp" #include "isobus/utility/event_dispatcher.hpp" namespace isobus @@ -26,7 +27,7 @@ namespace isobus /// @brief Defines a managed working set. /// @details This class is meant to be used as the basis for a VT server. /// It keeps track of one active object pool. - class VirtualTerminalServerManagedWorkingSet + class VirtualTerminalServerManagedWorkingSet : public VirtualTerminalWorkingSetBase { public: /// @brief Enumerates the states of the processing thread for the object pool @@ -39,8 +40,14 @@ namespace isobus Joined ///< We have sent our response to the working set master and are done parsing }; + /// @brief Default constructor VirtualTerminalServerManagedWorkingSet(); + + /// @brief Constructor that takes a control function to associate with this working set + /// @param[in] associatedControlFunction The control function to associate with this working set VirtualTerminalServerManagedWorkingSet(std::shared_ptr associatedControlFunction); + + /// @brief Destructor ~VirtualTerminalServerManagedWorkingSet(); /// @brief Starts a thread to parse the received object pool files @@ -49,21 +56,14 @@ namespace isobus /// @brief Joins the parsing thread void join_parsing_thread(); - bool parse_iop_into_objects(std::uint8_t *iopData, std::uint32_t iopLength); - /// @brief Returns if any object pools are being managed for this working set master /// @returns true if at least 1 object pool has been received for this working set master, otherwise false bool get_any_object_pools() const; - std::shared_ptr get_object_by_id(std::uint16_t objectID); - std::shared_ptr get_working_set_object(); - + /// @brief Returns the state of object pool processing, useful when parsing the object pool + /// on its own thread. + /// @returns The state of object pool processing ObjectPoolProcessingThreadState get_object_pool_processing_state(); - std::uint16_t get_object_pool_faulting_object_id(); - - void add_iop_raw_data(std::vector &dataToAdd); - std::size_t get_number_iop_files() const; - std::vector &get_iop_raw_data(std::size_t index); /// @brief Returns the control function that is the working set master /// @returns The control function that is the working set master @@ -79,27 +79,19 @@ namespace isobus /// @brief Saves an event callback handle for the lifetime of this object /// which is useful for keeping track of callback lifetimes in a VT server + /// @param[in] callbackHandle The event callback handle to save void save_callback_handle(isobus::EventCallbackHandle callbackHandle); /// @brief Clears all event callback handles for the this working set /// which is useful if you want to stop drawing this working set void clear_callback_handles(); - /// @brief Returns a colour from this working set's current colour table, by index - /// @param[in] colourIndex The index into the VT's colour table to retrieve - /// @returns A colour from this working set's current colour table, by index - VTColourVector get_colour(std::uint8_t colourIndex) const; - - /// @brief Returns the working set's object tree - /// @returns The working set's object tree - const std::map> &get_object_tree() const; - /// @brief Tells the server where this pool originated from. /// @returns True if this pool was loaded via a Load Version Command, otherwise false (transferred normally) bool get_was_object_pool_loaded_from_non_volatile_memory() const; /// @brief Tells the server where this pool originated from. - /// @param[in] True if this pool was loaded via a Load Version Command, otherwise false (transferred normally) + /// @param[in] value True if this pool was loaded via a Load Version Command, otherwise false (transferred normally) void set_was_object_pool_loaded_from_non_volatile_memory(bool value, CANLibBadge); /// @brief Sets the object ID of the currently focused object @@ -130,54 +122,22 @@ namespace isobus bool is_deletion_requested() const; private: - /// @brief Adds an object to the object tree, and replaces an object of the same type - /// if there's already one in the tree with the same ID. - /// @param[in] objectToAdd The object to add to the object tree - /// @returns true if the object was added or replaced, otherwise false - bool add_or_replace_object(std::shared_ptr objectToAdd); - - /// @brief Parses one object in the remaining object pool data - /// @param[in,out] iopData A pointer to some object pool data - /// @param[in,out] iopLength The number of bytes remaining in the object pool - /// @returns true if an object was parsed - bool parse_next_object(std::uint8_t *&iopData, std::uint32_t &iopLength); - - /// @brief Checks if the object pool contains an object with the supplied object ID - /// @returns true if an object with the specified ID exists in the object pool - bool get_object_id_exists(std::uint16_t objectID); - /// @brief Sets the object pool processing state to a new value /// @param[in] value The new state of processing the object pool void set_object_pool_processing_state(ObjectPoolProcessingThreadState value); - /// @brief Sets the object ID associated with a faulting object during pool parsing - /// @param[in] value The object ID to set as the faulting object - void set_object_pool_faulting_object_id(std::uint16_t value); - - /// @brief Returns the event ID from a byte. Does validation to ensure that the byte is valid. - /// If the proprietary range or reserved range is used, it will be considered invalid and event 0 will be returned. - /// @param[in] eventByte The byte to convert to an event ID - /// @returns The event ID from a byte, or event 0 if the byte is invalid - static EventID get_event_from_byte(std::uint8_t eventByte); - /// @brief The object pool processing thread will execute this function when it runs void worker_thread_function(); - VTColourTable workingSetColourTable; ///< This working set's colour table - std::map> vtObjectTree; ///< The C++ object representation (deserialized) of the object pool being managed - std::vector> iopFilesRawData; ///< Raw IOP File data from the client std::unique_ptr objectPoolProcessingThread = nullptr; ///< A thread to process the object pool with, since that can be fairly time consuming. - std::mutex managedWorkingSetMutex; ///< A mutex to protect the interface of the managed working set std::shared_ptr workingSetControlFunction = nullptr; ///< Stores the control function associated with this working set std::vector callbackHandles; ///< A convenient way to associate callback handles to a working set ObjectPoolProcessingThreadState processingState = ObjectPoolProcessingThreadState::None; ///< Stores the state of processing the object pool std::uint32_t workingSetMaintenanceMessageTimestamp_ms = 0; ///< A timestamp (in ms) to track sending of the maintenance message std::uint32_t auxiliaryInputMaintenanceMessageTimestamp_ms = 0; ///< A timestamp (in ms) to track if/when the working set sent an auxiliary input maintenance message - std::uint16_t workingSetID = NULL_OBJECT_ID; ///< Stores the object ID of the working set object itself - std::uint16_t faultingObjectID = NULL_OBJECT_ID; ///< Stores the faulting object ID to send to a client when parsing the pool fails std::uint16_t focusedObject = NULL_OBJECT_ID; ///< Stores the object ID of the currently focused object bool wasLoadedFromNonVolatileMemory = false; ///< Used to tell the server how this object pool was obtained - bool workingSetDeletionRequested = false; + bool workingSetDeletionRequested = false; ///< Used to tell the server to delete this working set }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/isobus_virtual_terminal_working_set_base.hpp b/isobus/include/isobus/isobus/isobus_virtual_terminal_working_set_base.hpp new file mode 100644 index 000000000..193c7583a --- /dev/null +++ b/isobus/include/isobus/isobus/isobus_virtual_terminal_working_set_base.hpp @@ -0,0 +1,101 @@ +//================================================================================================ +/// @file isobus_virtual_terminal_working_set_base.hpp +/// +/// @brief A base class for a VT working set that isolates common working set functionality +/// so that things useful to VT designer application and a VT server application can be shared. +/// @author Adrian Del Grosso +/// +/// @copyright 2024 The Open-Agriculture Developers +//================================================================================================ +#ifndef ISOBUS_VIRTUAL_TERMINAL_WORKING_SET_BASE_HPP +#define ISOBUS_VIRTUAL_TERMINAL_WORKING_SET_BASE_HPP + +#include "isobus/isobus/isobus_virtual_terminal_objects.hpp" + +#include + +namespace isobus +{ + /// @brief A base class for a VT working set that isolates common working set functionality + /// so that things useful to VT designer application and a VT server application can be shared. + class VirtualTerminalWorkingSetBase + { + public: + /// @brief Takes a raw block of IOP data and parses it into VT objects + /// @param[in] iopData A pointer to the raw IOP data + /// @param[in] iopLength The length of the raw IOP data + /// @returns true if the IOP data was parsed successfully, otherwise false + bool parse_iop_into_objects(std::uint8_t *iopData, std::uint32_t iopLength); + + /// @brief Returns a colour from this working set's current colour table, by index + /// @param[in] colourIndex The index into the VT's colour table to retrieve + /// @returns A colour from this working set's current colour table, by index + VTColourVector get_colour(std::uint8_t colourIndex) const; + + /// @brief Returns the working set's object tree + /// @returns The working set's object tree + const std::map> &get_object_tree() const; + + /// @brief Returns a VT object from the object tree by object ID + /// @param[in] objectID The object ID to retrieve from the object tree + /// @returns A VT object from the object tree by object ID, or an empty shared pointer if not found + std::shared_ptr get_object_by_id(std::uint16_t objectID); + + /// @brief Returns the working set object in the object pool, if one exists + /// @returns The working set object in the object pool, if one exists, otherwise an empty shared pointer + std::shared_ptr get_working_set_object(); + + /// @brief Appends raw IOP data to the working set's IOP file data + /// @param[in] dataToAdd The raw IOP data to add to the working set + void add_iop_raw_data(std::vector &dataToAdd); + + /// @brief Returns the number of discrete IOP file chunks that have been added to the object pool + /// @returns The number of discrete IOP file chunks that have been added to the object pool + std::size_t get_number_iop_files() const; + + /// @brief Returns IOP file data by index of IOP file + /// @param[in] index The index of the IOP file to retrieve + /// @returns The IOP file data by index of IOP file + std::vector &get_iop_raw_data(std::size_t index); + + /// @brief Returns the object ID of the the faulting object if parsing the object pool failed + /// @returns The object ID of the faulting object if parsing the object pool failed + std::uint16_t get_object_pool_faulting_object_id(); + + protected: + /// @brief Adds an object to the object tree, and replaces an object of the same type + /// if there's already one in the tree with the same ID. + /// @param[in] objectToAdd The object to add to the object tree + /// @returns true if the object was added or replaced, otherwise false + bool add_or_replace_object(std::shared_ptr objectToAdd); + + /// @brief Parses one object in the remaining object pool data + /// @param[in,out] iopData A pointer to some object pool data + /// @param[in,out] iopLength The number of bytes remaining in the object pool + /// @returns true if an object was parsed + bool parse_next_object(std::uint8_t *&iopData, std::uint32_t &iopLength); + + /// @brief Checks if the object pool contains an object with the supplied object ID + /// @param[in] objectID The object ID to check for in the object pool + /// @returns true if an object with the specified ID exists in the object pool + bool get_object_id_exists(std::uint16_t objectID); + + /// @brief Returns the event ID from a byte. Does validation to ensure that the byte is valid. + /// If the proprietary range or reserved range is used, it will be considered invalid and event 0 will be returned. + /// @param[in] eventByte The byte to convert to an event ID + /// @returns The event ID from a byte, or event 0 if the byte is invalid + static EventID get_event_from_byte(std::uint8_t eventByte); + + /// @brief Sets the object ID associated with a faulting object during pool parsing + /// @param[in] value The object ID to set as the faulting object + void set_object_pool_faulting_object_id(std::uint16_t value); + + std::mutex managedWorkingSetMutex; ///< A mutex to protect the interface of the managed working set + VTColourTable workingSetColourTable; ///< This working set's colour table + std::map> vtObjectTree; ///< The C++ object representation (deserialized) of the object pool being managed + std::vector> iopFilesRawData; ///< Raw IOP File data from the client + std::uint16_t workingSetID = NULL_OBJECT_ID; ///< Stores the object ID of the working set object itself + std::uint16_t faultingObjectID = NULL_OBJECT_ID; ///< Stores the faulting object ID to send to a client when parsing the pool fails + }; +} // namespace isobus +#endif // ISOBUS_VIRTUAL_TERMINAL_WORKING_SET_BASE_HPP diff --git a/isobus/src/isobus_virtual_terminal_server_managed_working_set.cpp b/isobus/src/isobus_virtual_terminal_server_managed_working_set.cpp index 2fd42666c..36536731b 100644 --- a/isobus/src/isobus_virtual_terminal_server_managed_working_set.cpp +++ b/isobus/src/isobus_virtual_terminal_server_managed_working_set.cpp @@ -55,31 +55,6 @@ namespace isobus } } - bool VirtualTerminalServerManagedWorkingSet::parse_iop_into_objects(std::uint8_t *iopData, std::uint32_t iopLength) - { - uint32_t remainingLength = iopLength; - std::uint8_t *currentIopPointer = iopData; - bool retVal = true; - - if (iopLength > 0) - { - while (remainingLength > 0) - { - if (!parse_next_object(currentIopPointer, remainingLength)) - { - CANStackLogger::error("[WS]: Parsing object pool failed."); - retVal = false; - break; - } - } - } - else - { - retVal = false; - } - return retVal; - } - bool VirtualTerminalServerManagedWorkingSet::get_any_object_pools() const { return (0 != iopFilesRawData.size()); @@ -91,27 +66,6 @@ namespace isobus return processingState; } - std::uint16_t VirtualTerminalServerManagedWorkingSet::get_object_pool_faulting_object_id() - { - std::lock_guard lock(managedWorkingSetMutex); - return faultingObjectID; - } - - void VirtualTerminalServerManagedWorkingSet::add_iop_raw_data(std::vector &dataToAdd) - { - iopFilesRawData.push_back(dataToAdd); - } - - std::size_t VirtualTerminalServerManagedWorkingSet::get_number_iop_files() const - { - return iopFilesRawData.size(); - } - - std::vector &VirtualTerminalServerManagedWorkingSet::get_iop_raw_data(std::size_t index) - { - return iopFilesRawData.at(index); - } - std::shared_ptr VirtualTerminalServerManagedWorkingSet::get_control_function() const { return workingSetControlFunction; @@ -137,16 +91,6 @@ namespace isobus callbackHandles.clear(); } - VTColourVector VirtualTerminalServerManagedWorkingSet::get_colour(std::uint8_t colourIndex) const - { - return workingSetColourTable.get_colour(colourIndex); - } - - const std::map> &VirtualTerminalServerManagedWorkingSet::get_object_tree() const - { - return vtObjectTree; - } - bool VirtualTerminalServerManagedWorkingSet::get_was_object_pool_loaded_from_non_volatile_memory() const { return wasLoadedFromNonVolatileMemory; @@ -187,3165 +131,12 @@ namespace isobus return workingSetDeletionRequested; } - bool VirtualTerminalServerManagedWorkingSet::add_or_replace_object(std::shared_ptr objectToAdd) - { - bool retVal = false; - - if ((nullptr != objectToAdd) && - ((!get_object_id_exists(objectToAdd->get_id())) || - (objectToAdd->get_object_type() == vtObjectTree[objectToAdd->get_id()]->get_object_type()))) - { - vtObjectTree[objectToAdd->get_id()] = objectToAdd; - retVal = true; - } - else if (nullptr != objectToAdd) - { - CANStackLogger::error("[WS]: Cannot replace an object with duplicate ID %u with a different type of object." + isobus::to_string(static_cast(objectToAdd->get_id()))); - } - return retVal; - } - - bool VirtualTerminalServerManagedWorkingSet::parse_next_object(std::uint8_t *&iopData, std::uint32_t &iopLength) - { - bool retVal = false; - - if (iopLength > 3) - { - // We at least have object ID and type - std::uint16_t decodedID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - VirtualTerminalObjectType decodedType = static_cast(iopData[2]); - - switch (decodedType) - { - case VirtualTerminalObjectType::WorkingSet: - { - if ((NULL_OBJECT_ID == workingSetID) || - ((nullptr != get_object_by_id(workingSetID)) && - (get_object_by_id(workingSetID)->get_id() == decodedID))) - { - workingSetID = decodedID; - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_background_color(iopData[3]); - tempObject->set_selectable(iopData[4]); - tempObject->set_active_mask(static_cast(iopData[5]) | (static_cast(iopData[6]) << 8)); - - // Now add child objects - const std::uint8_t childrenToFollow = iopData[7]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 6); // ID, X, Y 2 bytes each - const std::uint8_t numberOfMacrosToFollow = iopData[8]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - const std::uint8_t numberOfLanguagesToFollow = iopData[9]; - iopLength -= 10; // Subtract the bytes we've processed so far. - iopData += 10; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = static_cast(static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = static_cast(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - // Next, parse language list - if (iopLength >= static_cast(numberOfLanguagesToFollow * 2)) - { - for (std::uint_fast8_t i = 0; i < numberOfLanguagesToFollow; i++) - { - std::string langCode; - langCode.push_back(static_cast(iopData[0])); - langCode.push_back(static_cast(iopData[1])); - iopLength -= 2; - iopData += 2; - CANStackLogger::debug("[WS]: IOP Language parsed: " + langCode); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse working set language codes for object " + isobus::to_string(static_cast(decodedID))); - } - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse working set macros for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse working set children for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse working set object " + isobus::to_string(static_cast(decodedID))); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - else - { - CANStackLogger::error("[WS]: Multiple working set objects are not allowed in the object pool. Faulting object " + isobus::to_string(static_cast(decodedID))); - } - } - break; - - case VirtualTerminalObjectType::DataMask: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_background_color(iopData[3]); - tempObject->set_soft_key_mask(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - // Now add child objects - const std::uint8_t childrenToFollow = iopData[6]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 6); // ID, X, Y 2 bytes each - const std::uint8_t numberOfMacrosToFollow = iopData[7]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - iopLength -= 8; // Subtract the bytes we've processed so far. - iopData += 8; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = static_cast(static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = static_cast(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse data mask macros for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse data mask children for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse data mask object for object " + isobus::to_string(static_cast(decodedID))); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - break; - - case VirtualTerminalObjectType::AlarmMask: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_background_color(iopData[3]); - tempObject->set_soft_key_mask(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - - if (iopData[6] <= static_cast(AlarmMask::Priority::Low)) - { - tempObject->set_mask_priority(static_cast(iopData[6])); - - if (iopData[7] <= static_cast(AlarmMask::AcousticSignal::None)) - { - // Now add child objects - const std::uint8_t childrenToFollow = iopData[8]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 6); // ID, X, Y 2 bytes each - const std::uint8_t numberOfMacrosToFollow = iopData[9]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - iopLength -= 10; // Subtract the bytes we've processed so far. - iopData += 10; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = (static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = (static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse alarm mask macros for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse alarm mask children for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Invalid acoustic signal priority " + - isobus::to_string(static_cast(iopData[7])) + - " specified for alarm mask object " + - isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Invalid alarm mask priority " + - isobus::to_string(static_cast(iopData[6])) + - " specified for alarm mask object" + - isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse alarm mask object for object " + isobus::to_string(static_cast(decodedID))); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - break; - - case VirtualTerminalObjectType::Container: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_width((static_cast(iopData[3]) | (static_cast(iopData[4]) << 8))); - tempObject->set_height((static_cast(iopData[5]) | (static_cast(iopData[6]) << 8))); - tempObject->set_hidden(0 != iopData[7]); - - if (iopData[7] > 1) - { - CANStackLogger::warn("[WS]: Container " + - isobus::to_string(static_cast(decodedID)) + - " hidden attribute is not a supported value. Assuming that it is hidden."); - } - - // Now add child objects - const std::uint8_t childrenToFollow = iopData[8]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 6); // ID, X, Y 2 bytes each - const std::uint8_t numberOfMacrosToFollow = iopData[9]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - iopLength -= 10; // Subtract the bytes we've processed so far. - iopData += 10; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = static_cast(static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = static_cast(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse container macros for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse container children for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse container object"); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - break; - - case VirtualTerminalObjectType::WindowMask: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - retVal = true; - tempObject->set_id(decodedID); - - if ((iopData[3] != 1) && (iopData[3] != 2)) - { - CANStackLogger::warn("[WS]: Unknown window mask width for object %u. Allowed range is 1-2.", decodedID); - } - tempObject->set_width(iopData[3]); - - if ((iopData[4] < 1) || (iopData[4] > 6)) - { - CANStackLogger::warn("[WS]: Unknown window mask height for object %u. Allowed range is 1-6.", decodedID); - } - tempObject->set_height(iopData[4]); - - if (iopData[5] > 18) - { - CANStackLogger::error("[WS]: Unknown window mask type for object %u. Allowed range is 1-18.", decodedID); - retVal = false; - } - else - { - tempObject->set_window_type(static_cast(iopData[5])); - } - - if (retVal) - { - tempObject->set_background_color(iopData[6]); - tempObject->set_options(iopData[7]); - - const std::uint16_t name = (static_cast(iopData[8]) | (static_cast(iopData[9]) << 8)); - const std::uint16_t title = (static_cast(iopData[10]) | (static_cast(iopData[11]) << 8)); - const std::uint16_t icon = (static_cast(iopData[12]) | (static_cast(iopData[13]) << 8)); - - tempObject->set_name_object_id(name); - tempObject->set_title_object_id(title); - tempObject->set_icon_object_id(icon); - - const std::uint8_t numberOfObjectReferences = iopData[14]; - const std::uint8_t numberOfChildObjects = iopData[15]; - const std::uint8_t numberOfMacros = iopData[16]; - const std::uint16_t sizeOfMacros = (numberOfMacros * 2); - const std::uint16_t sizeOfChildren = (numberOfChildObjects * 6); // ID, X, Y 2 bytes each - - switch (tempObject->get_window_type()) - { - case WindowMask::WindowType::StringOutputValue1x1: - case WindowMask::WindowType::NumericOutputValueNoUnits1x1: - case WindowMask::WindowType::SingleButton1x1: - case WindowMask::WindowType::StringInputValue1x1: - case WindowMask::WindowType::SingleButton2x1: - case WindowMask::WindowType::HorizontalLinearBarGraphNoUnits2x1: - case WindowMask::WindowType::NumericOutputValueNoUnits2x1: - case WindowMask::WindowType::NumericInputValueNoUnits1x1: - case WindowMask::WindowType::HorizontalLinearBarGraphNoUnits1x1: - case WindowMask::WindowType::StringOutputValue2x1: - case WindowMask::WindowType::StringInputValue2x1: - case WindowMask::WindowType::NumericInputValueNoUnits2x1: - { - if (1 != numberOfObjectReferences) - { - retVal = false; - CANStackLogger::error("[WS]: Window mask %u has an invalid number of object references. Value must be exactly 1.", decodedID); - } - } - break; - - case WindowMask::WindowType::NumericOutputValueWithUnits1x1: - case WindowMask::WindowType::DoubleButton2x1: - case WindowMask::WindowType::NumericInputValueWithUnits1x1: - case WindowMask::WindowType::NumericOutputValueWithUnits2x1: - case WindowMask::WindowType::NumericInputValueWithUnits2x1: - case WindowMask::WindowType::DoubleButton1x1: - { - if (2 != numberOfObjectReferences) - { - retVal = false; - CANStackLogger::error("[WS]: Window mask %u has an invalid number of object references. Value must be exactly 2.", decodedID); - } - } - break; - - case WindowMask::WindowType::Freeform: - { - if (0 != numberOfObjectReferences) - { - retVal = false; - CANStackLogger::error("[WS]: Window mask %u has an invalid number of object references. Value must be exactly 0.", decodedID); - } - } - break; - } - - iopLength -= tempObject->get_minumum_object_length(); // Subtract the bytes we've processed so far. - iopData += tempObject->get_minumum_object_length(); // Move the pointer - - if (iopLength >= static_cast(2 * numberOfObjectReferences)) - { - for (std::uint_fast8_t i = 0; i < numberOfObjectReferences; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - tempObject->add_child(childID, 0, 0); - iopLength -= 2; - iopData += 2; - } - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < numberOfChildObjects; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = static_cast(static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = static_cast(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacros; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse macros for object " + isobus::to_string(static_cast(decodedID))); - retVal = false; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse children for object " + isobus::to_string(static_cast(decodedID))); - retVal = false; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse object references for object " + isobus::to_string(static_cast(decodedID))); - retVal = false; - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse window mask object."); - } - } - break; - - case VirtualTerminalObjectType::SoftKeyMask: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_background_color(iopData[3]); - - // Now add child objects - const std::uint8_t childrenToFollow = iopData[4]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 2); // ID 2 bytes - const std::uint8_t numberOfMacrosToFollow = iopData[5]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - iopLength -= 6; // Subtract the bytes we've processed so far. - iopData += 6; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - // For soft key masks, no x,y positions are included - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - tempObject->add_child(childID, 0, 0); - iopLength -= 2; - iopData += 2; - } - - // Next, parse macro list - - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse soft key mask macros for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse soft key mask children for object " + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse soft key mask object"); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - break; - - case VirtualTerminalObjectType::Key: - { - auto tempObject = std::make_shared(); - - if (iopLength >= tempObject->get_minumum_object_length()) - { - tempObject->set_id(decodedID); - tempObject->set_background_color(iopData[3]); - tempObject->set_key_code(iopData[4]); - - // Now add child objects - const std::uint8_t childrenToFollow = iopData[5]; - const std::uint16_t sizeOfChildren = (childrenToFollow * 6); // ID, X, Y 2 bytes each - const std::uint8_t numberOfMacrosToFollow = iopData[6]; - const std::uint16_t sizeOfMacros = (numberOfMacrosToFollow * 2); - iopLength -= 7; // Subtract the bytes we've processed so far. - iopData += 7; // Move the pointer - - if (iopLength >= sizeOfChildren) - { - for (std::uint_fast8_t i = 0; i < childrenToFollow; i++) - { - std::uint16_t childID = (static_cast(iopData[0]) | (static_cast(iopData[1]) << 8)); - std::int16_t childX = static_cast(static_cast(iopData[2]) | (static_cast(iopData[3]) << 8)); - std::int16_t childY = static_cast(static_cast(iopData[4]) | (static_cast(iopData[5]) << 8)); - tempObject->add_child(childID, childX, childY); - iopLength -= 6; - iopData += 6; - } - - // Next, parse macro list - - if (iopLength >= sizeOfMacros) - { - for (std::uint_fast8_t i = 0; i < numberOfMacrosToFollow; i++) - { - // If the first byte is 255, then more bytes are used! 4.6.22.3 - if (iopData[0] == static_cast(EventID::UseExtendedMacroReference)) - { - std::uint16_t macroID = (static_cast(iopData[1]) | (static_cast(iopData[3]) << 8)); - - if (EventID::Reserved != get_event_from_byte(iopData[2])) - { - tempObject->add_macro({ get_event_from_byte(iopData[2]), macroID }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", macroID, decodedID); - retVal = false; - break; - } - } - else - { - if (EventID::Reserved != get_event_from_byte(iopData[0])) - { - tempObject->add_macro({ get_event_from_byte(iopData[0]), iopData[1] }); - retVal = true; - } - else - { - CANStackLogger::error("[WS]: Macro with ID %u which is listed as part of object %u has an invalid or unsupported event ID.", iopData[1], decodedID); - retVal = false; - break; - } - } - - iopLength -= 2; - iopData += 2; - } - - if (0 == sizeOfMacros) - { - retVal = true; - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse macros for key object" + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to parse key children for object" + isobus::to_string(static_cast(decodedID))); - } - } - else - { - CANStackLogger::error("[WS]: Not enough IOP data to key object"); - } - - if (retVal) - { - retVal = add_or_replace_object(tempObject); - } - } - break; - - case VirtualTerminalObjectType::Button: - { - auto tempObject = std::make_shared