Skip to content

Commit

Permalink
feat(vt-client): add attribute tracking and updating
Browse files Browse the repository at this point in the history
Also introduced callbacks for transmitted messages by our device from any CF
  • Loading branch information
GwnDaan committed Jan 30, 2024
1 parent 0192b52 commit 8c11baa
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 3 deletions.
26 changes: 26 additions & 0 deletions isobus/include/isobus/isobus/can_network_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ namespace isobus
/// @param[in] parent A generic context variable that helps identify what object the callback was destined for
void remove_any_control_function_parameter_group_number_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent);

/// @brief Registers a callback for when any message is put on the bus by us by any control function
/// @param[in] callback The callback that will be called when any control function transmits a message
/// @param[in] parent A generic context variable that helps identify what object the callback is destined for
void add_any_control_function_transmit_callback(CANLibCallback callback, void *parent);

/// @brief This is how you remove a callback added with add_any_control_function_transmit_callback
/// @param[in] callback The callback that will be removed
/// @param[in] parent A generic context variable that helps identify what object the callback was destined for
void remove_any_control_function_transmit_callback(CANLibCallback callback, void *parent);

/// @brief Returns an internal control function if the passed-in control function is an internal type
/// @param[in] controlFunction The control function to get the internal control function from
/// @returns An internal control function casted from the passed in control function
Expand Down Expand Up @@ -299,6 +309,11 @@ namespace isobus
/// @returns The message that was at the front of the queue, or an invalid message if the queue is empty
CANMessage get_next_can_message_from_rx_queue();

/// @brief Get the next CAN message from the received message queue, and remove it from the queue
/// @note This will only ever get an 8 byte message because they are directly translated from CAN frames.
/// @returns The message that was at the front of the queue, or an invalid message if the queue is empty
CANMessage get_next_can_message_from_tx_queue();

/// @brief Informs the network manager that a control function object has been created
/// @param[in] controlFunction The control function that was created
void on_control_function_created(std::shared_ptr<ControlFunction> controlFunction);
Expand All @@ -307,6 +322,10 @@ namespace isobus
/// @param[in] currentMessage The message to process
void process_any_control_function_pgn_callbacks(const CANMessage &currentMessage);

/// @brief Processes a can message for callbacks added with add_any_control_function_transmit_callback
/// @param[in] currentMessage The message to process
void process_any_control_function_transmit_callbacks(const CANMessage &currentMessage);

/// @brief Validates that a CAN message has not caused an address violation.
/// If a violation is found, the network manager will notify the affected address claim state machine
/// to re-claim as is required by ISO 11783-5, and will attempt to activate a DTC that is defined in ISO 11783-5.
Expand Down Expand Up @@ -338,6 +357,9 @@ namespace isobus
/// @brief Processes the internal received message queue
void process_rx_messages();

/// @brief Processes the internal transmitted message queue
void process_tx_messages();

/// @brief Checks to see if any control function didn't claim during a round of
/// address claiming and removes it if needed.
void prune_inactive_control_functions();
Expand Down Expand Up @@ -383,14 +405,18 @@ namespace isobus

std::list<ParameterGroupNumberCallbackData> protocolPGNCallbacks; ///< A list of PGN callback registered by CAN protocols
std::queue<CANMessage> receivedMessageQueue; ///< A queue of received messages to process
std::queue<CANMessage> transmittedMessageQueue; ///< A queue of transmitted messages to process (already sent, so changes to the message won't affect the bus)
std::list<ControlFunctionStateCallback> controlFunctionStateCallbacks; ///< List of all control function state callbacks
std::vector<ParameterGroupNumberCallbackData> globalParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks
std::vector<ParameterGroupNumberCallbackData> anyControlFunctionParameterGroupNumberCallbacks; ///< A list of all global PGN callbacks
std::vector<std::pair<CANLibCallback, void *>> anyControlFunctionTransmitCallbacks; ///< A list of all transmitted message callbacks
EventDispatcher<std::shared_ptr<InternalControlFunction>> addressViolationEventDispatcher; ///< An event dispatcher for notifying consumers about address violations
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::mutex receivedMessageQueueMutex; ///< A mutex for receive messages thread safety
std::mutex transmittedMessageQueueMutex; ///< A mutex for protecting the transmitted message queue
std::mutex protocolPGNCallbacksMutex; ///< A mutex for PGN callback thread safety
std::mutex anyControlFunctionCallbacksMutex; ///< Mutex to protect the "any CF" callbacks
std::mutex anyTransmittedMessageCallbacksMutex; ///< Mutex to protect the transmitted message callbacks
std::mutex busloadUpdateMutex; ///< A mutex that protects the busload metrics since we calculate it on our own thread
std::mutex controlFunctionStatusCallbacksMutex; ///< A Mutex that protects access to the control function status callback list
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace isobus
/// @brief Adds a data/alarm mask to track the soft key mask for.
/// @param[in] dataOrAlarmMaskId The data/alarm mask to track the soft key mask for.
/// @param[in] initialSoftKeyMaskId The initial soft key mask to associate with the data/alarm mask.
void add_tracked_soft_key_mask(std::uint16_t dataOrAlarmMaskId, std::uint16_t initialSoftKeyMaskId);
void add_tracked_soft_key_mask(std::uint16_t dataOrAlarmMaskId, std::uint16_t initialSoftKeyMaskId = 0);

/// @brief Removes a data/alarm mask from tracking the soft key mask for.
/// @param[in] dataOrAlarmMaskId The data/alarm mask to remove the soft key mask from tracking for.
Expand All @@ -93,6 +93,23 @@ namespace isobus
/// @return True if the working set is active, false otherwise.
bool is_working_set_active() const;

/// @brief Adds an attribute to track.
/// @param[in] objectId The object id of the attribute to track.
/// @param[in] attribute The attribute to track.
/// @param[in] initialValue The initial value of the attribute to track.
void add_tracked_attribute(std::uint16_t objectId, std::uint8_t attribute, std::uint32_t initialValue = 0);

/// @brief Removes an attribute from tracking.
/// @param[in] objectId The object id of the attribute to remove from tracking.
/// @param[in] attribute The attribute to remove from tracking.
void remove_tracked_attribute(std::uint16_t objectId, std::uint8_t attribute);

/// @brief Get the value of an attribute of a tracked object.
/// @param[in] objectId The object id of the attribute to get.
/// @param[in] attribute The attribute to get.
/// @return The value of the attribute of the tracked object.
std::uint32_t get_attribute(std::uint16_t objectId, std::uint8_t attribute) const;

protected:
std::shared_ptr<ControlFunction> client; ///< The control function of the virtual terminal client to track.
std::shared_ptr<ControlFunction> server; ///< The control function of the server the client is connected to.
Expand All @@ -116,7 +133,7 @@ namespace isobus
std::size_t maxDataAndAlarmMaskHistorySize = 100; ///< Holds the maximum size of the data/alarm mask history.
std::uint8_t activeWorkingSetAddress = NULL_CAN_ADDRESS; ///< Holds the address of the control function that currently has
std::map<std::uint16_t, std::uint16_t> softKeyMasks; ///< Holds the data/alarms masks with their associated soft keys masks for tracked objects.
//! TODO: std::map<std::uint16_t, std::pair<std::uint8_t, std::uint32_t>> attributeStates; ///< Holds the 'attribute' state of tracked objects.
std::map<std::uint16_t, std::map<std::uint8_t, std::uint32_t>> attributeStates; ///< Holds the 'attribute' state of tracked objects.
//! TODO: std::map<std::uint16_t, std::uint8_t> alarmMaskPrioritiesStates; ///< Holds the 'alarm mask priority' state of tracked objects.
//! TODO: std::map<std::uint16_t, std::pair<std::uint8_t, std::uint16_t>> listItemStates; ///< Holds the 'list item' state of tracked objects.
//! TODO: add lock/unlock mask state
Expand Down Expand Up @@ -147,6 +164,16 @@ namespace isobus
/// @brief Processes a ECU->VT message received by the connected server, sent from any control function.
/// @param[in] message The message to process.
void process_message_to_connected_server(const CANMessage &message);

/// @brief Data structure to hold the properties of a change attribute command
struct ChangeAttributeCommand
{
std::uint16_t objectId; ///< Holds the id of to be changed object.
std::uint8_t attribute; ///< Holds the id of the attribute to be changed of the specified object.
std::uint32_t value; ///< Holds the value to change the attribute to.
};

std::map<std::shared_ptr<ControlFunction>, ChangeAttributeCommand> pendingChangeAttributeCommands; ///< Holds the pending change attribute command for a control function.
};
} // namespace isobus

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ namespace isobus
/// @return True if the soft key mask was set active successfully, false otherwise.
bool set_active_soft_key_mask(VirtualTerminalClient::MaskType maskType, std::uint16_t maskId, std::uint16_t softKeyMaskId);

/// @brief Sets the value of an attribute of a tracked object.
/// @note If the to be tracked working set consists of more than the master,
/// this function is incompatible with a VT prior to version 4. For working sets consisting
/// of only the master, this function is compatible with any VT version.
/// @param[in] objectId The object id of the attribute to set.
/// @param[in] attribute The attribute to set.
/// @param[in] value The value to set the attribute to.
/// @return True if the attribute was set successfully, false otherwise.
bool set_attribute(std::uint16_t objectId, std::uint8_t attribute, std::uint32_t value);

private:
/// @brief Processes a numeric value change event
/// @param[in] event The numeric value change event to process.
Expand Down
80 changes: 80 additions & 0 deletions isobus/src/can_network_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ namespace isobus
{
get_next_can_message_from_rx_queue();
}
while (!transmittedMessageQueue.empty())
{
get_next_can_message_from_tx_queue();
}
initialized = true;
}

Expand Down Expand Up @@ -85,6 +89,27 @@ namespace isobus
}
}

void CANNetworkManager::add_any_control_function_transmit_callback(CANLibCallback callback, void *parent)
{
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::lock_guard<std::mutex> lock(anyTransmittedMessageCallbacksMutex);
#endif
anyControlFunctionTransmitCallbacks.emplace_back(callback, parent);
}

void CANNetworkManager::remove_any_control_function_transmit_callback(CANLibCallback callback, void *parent)
{
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::lock_guard<std::mutex> lock(anyTransmittedMessageCallbacksMutex);
#endif
std::pair<CANLibCallback, void *> tempObject(callback, parent);
auto callbackLocation = std::find(anyControlFunctionTransmitCallbacks.begin(), anyControlFunctionTransmitCallbacks.end(), tempObject);
if (anyControlFunctionTransmitCallbacks.end() != callbackLocation)
{
anyControlFunctionTransmitCallbacks.erase(callbackLocation);
}
}

std::shared_ptr<InternalControlFunction> CANNetworkManager::get_internal_control_function(std::shared_ptr<ControlFunction> controlFunction)
{
std::shared_ptr<InternalControlFunction> retVal = nullptr;
Expand Down Expand Up @@ -228,6 +253,7 @@ namespace isobus
update_new_partners();

process_rx_messages();
process_tx_messages();

update_internal_cfs();

Expand Down Expand Up @@ -322,6 +348,23 @@ namespace isobus
void CANNetworkManager::process_transmitted_can_message_frame(const CANMessageFrame &txFrame)
{
update_busload(txFrame.channel, txFrame.get_number_bits_in_message());

CANIdentifier identifier(txFrame.identifier);
CANMessage message(CANMessage::Type::Transmit,
identifier,
txFrame.data,
txFrame.dataLength,
get_control_function(txFrame.channel, identifier.get_source_address()),
get_control_function(txFrame.channel, identifier.get_destination_address()),
txFrame.channel);

if (initialized)
{
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::lock_guard<std::mutex> lock(transmittedMessageQueueMutex);
#endif
transmittedMessageQueue.push(std::move(message));
}
}

void CANNetworkManager::on_control_function_destroyed(std::shared_ptr<ControlFunction> controlFunction, CANLibBadge<ControlFunction>)
Expand Down Expand Up @@ -905,6 +948,20 @@ namespace isobus
return CANMessage::create_invalid_message();
}

CANMessage CANNetworkManager::get_next_can_message_from_tx_queue()
{
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::lock_guard<std::mutex> lock(transmittedMessageQueueMutex);
#endif
if (!transmittedMessageQueue.empty())
{
CANMessage retVal = std::move(transmittedMessageQueue.front());
transmittedMessageQueue.pop();
return retVal;
}
return CANMessage::create_invalid_message();
}

void CANNetworkManager::on_control_function_created(std::shared_ptr<ControlFunction> controlFunction)
{
if (ControlFunction::Type::Internal == controlFunction->get_type())
Expand Down Expand Up @@ -933,6 +990,17 @@ namespace isobus
}
}

void CANNetworkManager::process_any_control_function_transmit_callbacks(const CANMessage &currentMessage)
{
#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO
std::lock_guard<std::mutex> lock(anyTransmittedMessageCallbacksMutex);
#endif
for (const auto &callback : anyControlFunctionTransmitCallbacks)
{
callback.first(currentMessage, callback.second);
}
}

void CANNetworkManager::process_can_message_for_address_violations(const CANMessage &currentMessage)
{
auto sourceAddress = currentMessage.get_identifier().get_source_address();
Expand Down Expand Up @@ -1064,6 +1132,18 @@ namespace isobus
}
}

void CANNetworkManager::process_tx_messages()
{
// We may miss a message without locking the mutex when checking if empty, but that's okay. It will be picked up on the next iteration
while (!transmittedMessageQueue.empty())
{
CANMessage currentMessage = get_next_can_message_from_tx_queue();

// Update listen-only callbacks
process_any_control_function_transmit_callbacks(currentMessage);
}
}

void CANNetworkManager::prune_inactive_control_functions()
{
for (std::uint_fast8_t channelIndex = 0; channelIndex < CAN_PORT_MAXIMUM; channelIndex++)
Expand Down
104 changes: 103 additions & 1 deletion isobus/src/isobus_virtual_terminal_client_state_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,59 @@ namespace isobus
return (client != nullptr) && client->get_address_valid() && (client->get_address() == activeWorkingSetAddress);
}

void VirtualTerminalClientStateTracker::add_tracked_attribute(std::uint16_t objectId, std::uint8_t attribute, std::uint32_t initialValue)
{
if (attributeStates.find(objectId) == attributeStates.end())
{
attributeStates[objectId] = {};
}

auto &attributeMap = attributeStates.at(objectId);
if (attributeMap.find(attribute) != attributeMap.end())
{
CANStackLogger::warn("[VTStateHelper] add_tracked_attribute: attribute '%lu' of objectId '%lu' already tracked", attribute, objectId);
return;
}

attributeMap[attribute] = initialValue;
}

void VirtualTerminalClientStateTracker::remove_tracked_attribute(std::uint16_t objectId, std::uint8_t attribute)
{
if (attributeStates.find(objectId) == attributeStates.end())
{
CANStackLogger::warn("[VTStateHelper] remove_tracked_attribute: objectId '%lu' was not tracked", objectId);
return;
}

auto &attributeMap = attributeStates.at(objectId);
if (attributeMap.find(attribute) == attributeMap.end())
{
CANStackLogger::warn("[VTStateHelper] remove_tracked_attribute: attribute '%lu' of objectId '%lu' was not tracked", attribute, objectId);
return;
}

attributeMap.erase(attribute);
}

std::uint32_t VirtualTerminalClientStateTracker::get_attribute(std::uint16_t objectId, std::uint8_t attribute) const
{
if (attributeStates.find(objectId) == attributeStates.end())
{
CANStackLogger::warn("[VTStateHelper] get_attribute: objectId '%lu' not tracked", objectId);
return 0;
}

const auto &attributeMap = attributeStates.at(objectId);
if (attributeMap.find(attribute) == attributeMap.end())
{
CANStackLogger::warn("[VTStateHelper] get_attribute: attribute '%lu' of objectId '%lu' not tracked", attribute, objectId);
return 0;
}

return attributeMap.at(attribute);
}

void VirtualTerminalClientStateTracker::cache_active_mask(std::uint16_t maskId)
{
if (activeDataOrAlarmMask != maskId)
Expand Down Expand Up @@ -282,13 +335,62 @@ namespace isobus
}
break;

case static_cast<std::uint8_t>(VirtualTerminalClient::Function::ChangeAttributeCommand):
{
if (CAN_DATA_LENGTH == message.get_data_length())
{
auto errorCode = message.get_uint8_at(4);
if (errorCode == 0)
{
std::uint16_t objectId = message.get_uint16_at(1);
std::uint8_t attribute = message.get_uint8_at(3);
std::uint8_t error = message.get_uint8_at(4);

if (pendingChangeAttributeCommands.find(message.get_destination_control_function()) != pendingChangeAttributeCommands.end())
{
const auto &pendingCommand = pendingChangeAttributeCommands.at(message.get_source_control_function());
if ((pendingCommand.objectId == objectId) && (pendingCommand.attribute == attribute) && (0 == error))
{
std::uint32_t value = message.get_uint32_at(5);
attributeStates[objectId][attribute] = value;
}
pendingChangeAttributeCommands.erase(message.get_destination_control_function());
}
}
}
}
break;

default:
break;
}
}

void VirtualTerminalClientStateTracker::process_message_to_connected_server(const CANMessage &message)
{
//! TODO: will be used for change attribute command
std::uint8_t function = message.get_uint8_at(0);
switch (function)
{
case static_cast<std::uint8_t>(VirtualTerminalClient::Function::ChangeAttributeCommand):
{
if (CAN_DATA_LENGTH == message.get_data_length())
{
std::uint16_t objectId = message.get_uint16_at(1);
std::uint8_t attribute = message.get_uint8_at(3);

// Only track the change if the attribute should be tracked
if ((attributeStates.find(objectId) != attributeStates.end()) &&
(attributeStates.at(objectId).find(attribute) != attributeStates.at(objectId).end()))
{
std::uint32_t value = message.get_uint32_at(4);
pendingChangeAttributeCommands[message.get_source_control_function()] = { objectId, attribute, value };
}
}
}
break;

default:
break;
}
}
} // namespace isobus
Loading

0 comments on commit 8c11baa

Please sign in to comment.