diff --git a/core/driver/sockcanpp/CanDriver.cpp b/core/driver/sockcanpp/CanDriver.cpp new file mode 100644 index 0000000..17efd91 --- /dev/null +++ b/core/driver/sockcanpp/CanDriver.cpp @@ -0,0 +1,295 @@ +/** + * @file CanDriver.cpp + * @author Simon Cahill (simonc@online.de) + * @brief + * @version 0.1 + * @date 2020-07-01 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +////////////////////////////// +// SYSTEM INCLUDES // +////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +////////////////////////////// +// LOCAL INCLUDES // +////////////////////////////// +#include "CanDriver.hpp" +#include "CanId.hpp" +#include "CanMessage.hpp" +#include "exceptions/CanCloseException.hpp" +#include "exceptions/CanException.hpp" +#include "exceptions/CanInitException.hpp" +#include "exceptions/InvalidSocketException.hpp" + +namespace sockcanpp { + + using exceptions::CanCloseException; + using exceptions::CanException; + using exceptions::CanInitException; + using exceptions::InvalidSocketException; + + using std::mutex; + using std::queue; + using std::string; + using std::strncpy; + using std::unique_lock; + using std::chrono::milliseconds; + using std::this_thread::sleep_for; + + ////////////////////////////////////// + // PUBLIC IMPLEMENTATION // + ////////////////////////////////////// + + const int32_t CanDriver::CAN_MAX_DATA_LENGTH = 8; + const int32_t CanDriver::CAN_SOCK_RAW = CAN_RAW; + const int32_t CanDriver::CAN_SOCK_SEVEN = 7; + +#pragma region Object Construction + CanDriver::CanDriver(string canInterface, int32_t canProtocol, const CanId defaultSenderId): + CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) {} + + CanDriver::CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): + _defaultSenderId(defaultSenderId), _canProtocol(canProtocol), _canInterface(canInterface), _canFilterMask(filterMask), _socketFd(-1) { + initialiseSocketCan(); + } +#pragma endregion + +#pragma region I / O + /** + * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. + * + * @param timeout The time (in millis) to wait before timing out. + * + * @return true If messages are available on the bus. + * @return false Otherwise. + */ + bool CanDriver::waitForMessages(milliseconds timeout) { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + unique_lock locky(_lock); + + fd_set readFileDescriptors; + timeval waitTime; + waitTime.tv_sec = timeout.count() / 1000; + waitTime.tv_usec = timeout.count() * 1000; + + FD_ZERO(&readFileDescriptors); + FD_SET(_socketFd, &readFileDescriptors); + _queueSize = select(_socketFd + 1, &readFileDescriptors, 0, 0, &waitTime); + + return _queueSize > 0; + } + + /** + * @brief Attempts to read a message from the associated CAN bus. + * + * @return CanMessage The message read from the bus. + */ + CanMessage CanDriver::readMessage() { + return readMessageLock(); + } + + /** + * @brief readMessage deadlock guard, attempts to read a message from the associated CAN bus. + * + * @return CanMessage The message read from the bus. + */ + CanMessage CanDriver::readMessageLock(bool const lock) { + std::unique_ptr> _lockLck{nullptr}; + if (lock) + _lockLck = std::unique_ptr>{new std::unique_lock{_lock}}; + if (0 > _socketFd) + throw InvalidSocketException("Invalid socket!", _socketFd); + int32_t readBytes{0}; + can_frame canFrame; + memset(&canFrame, 0, sizeof(can_frame)); + readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); + if (0 > readBytes) + throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); + return CanMessage{canFrame}; + } + + /** + * @brief Attempts to send a CAN message on the associated bus. + * + * @param message The message to be sent. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The amount of bytes sent on the bus. + */ + int32_t CanDriver::sendMessage(const CanMessage message, bool forceExtended) { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + unique_lock locky(_lock); + + int32_t bytesWritten = 0; + + if (message.getFrameData().size() > CAN_MAX_DATA_LENGTH) { + throw CanException(formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH), _socketFd); + } + + auto canFrame = message.getRawFrame(); + + if (forceExtended || (message.getCanId() > CAN_SFF_MASK)) { canFrame.can_id |= CAN_EFF_FLAG; } + + bytesWritten = write(_socketFd, (const void*)&canFrame, sizeof(canFrame)); + + if (bytesWritten == -1) { throw CanException(formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)), _socketFd); } + + return bytesWritten; + } + + /** + * @brief Attempts to send a queue of messages on the associated CAN bus. + * + * @param messages A queue containing the messages to be sent. + * @param delay If greater than 0, will delay the sending of the next message. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The total amount of bytes sent. + */ + int32_t CanDriver::sendMessageQueue(queue messages, milliseconds delay, bool forceExtended) { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t totalBytesWritten = 0; + + while (!messages.empty()) { + totalBytesWritten += sendMessage(messages.front(), forceExtended); + messages.pop(); + } + + return totalBytesWritten; + } + + /** + * @brief Attempts to read all messages stored in the buffer for the associated CAN bus. + * + * @return queue A queue containing the messages read from the bus buffer. + */ + queue CanDriver::readQueuedMessages() { + if (_socketFd < 0) + throw InvalidSocketException("Invalid socket!", _socketFd); + unique_lock locky(_lock); + queue messages; + for (int32_t i = _queueSize; 0 < i; --i) + messages.emplace(readMessageLock(false)); + return messages; + } + + /** + * @brief Attempts to set the filter mask for the associated CAN bus. + * + * @param mask The bit mask to apply. + */ + void CanDriver::setCanFilterMask(const int32_t mask) { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + unique_lock locky(_lock); + can_filter canFilter; + + canFilter.can_id = _defaultSenderId; + canFilter.can_mask = mask; + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, &canFilter, sizeof(canFilter)) == -1) { + throw CanInitException(formatString("FAILED to set CAN filter mask %x on socket %d! Error: %d => %s", mask, _socketFd, errno, strerror(errno))); + } + + _canFilterMask = mask; + } +#pragma endregion + + ////////////////////////////////////// + // PROTECTED IMPLEMENTATION // + ////////////////////////////////////// + +#pragma region Socket Management + + /** + * @brief Initialises the underlying CAN socket. + */ + void CanDriver::initialiseSocketCan() { + // unique_lock locky(_lock); + + struct sockaddr_can address; + struct ifreq ifaceRequest; + int64_t fdOptions = 0; + int32_t tmpReturn; + + memset(&address, 0, sizeof(sizeof(struct sockaddr_can))); + memset(&ifaceRequest, 0, sizeof(sizeof(struct ifreq))); + + _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); + + if (_socketFd == -1) { + throw CanInitException(formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno))); + } + + strcpy(ifaceRequest.ifr_name, _canInterface.c_str()); + + if ((tmpReturn = ioctl(_socketFd, SIOCGIFINDEX, &ifaceRequest)) == -1) { + throw CanInitException(formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", _canInterface.c_str(), errno, + strerror(errno))); + } + + fdOptions = fcntl(_socketFd, F_GETFL); + fdOptions |= O_NONBLOCK; + tmpReturn = fcntl(_socketFd, F_SETFL, fdOptions); + + address.can_family = AF_CAN; + address.can_ifindex = ifaceRequest.ifr_ifindex; + + setCanFilterMask(_canFilterMask); + + if ((tmpReturn = bind(_socketFd, (struct sockaddr*)&address, sizeof(address))) == -1) { + throw CanInitException(formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno))); + } + } + + /** + * @brief Closes the underlying CAN socket. + */ + void CanDriver::uninitialiseSocketCan() { + unique_lock locky(_lock); + + if (_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } + + if (close(_socketFd) == -1) { throw CanCloseException(formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno))); } + + _socketFd = -1; + } +#pragma endregion + +} // namespace sockcanpp diff --git a/core/driver/sockcanpp/CanDriver.hpp b/core/driver/sockcanpp/CanDriver.hpp new file mode 100644 index 0000000..f7f31d0 --- /dev/null +++ b/core/driver/sockcanpp/CanDriver.hpp @@ -0,0 +1,141 @@ +/** + * @file CanDriver.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the declarations for the SocketCAN wrapper in C++. + * @version 0.1 + * @date 2020-07-01 + * + * @copyright Copyright (c) 2020 + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP +#define LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP + +////////////////////////////// +// SYSTEM INCLUDES // +////////////////////////////// +#include +#include +#include +#include + +////////////////////////////// +// LOCAL INCLUDES // +////////////////////////////// +#include "CanId.hpp" +#include "CanMessage.hpp" + +/** + * @brief Main library namespace. + * + * This namespace contains the library's main code. + */ +namespace sockcanpp { + + using std::chrono::milliseconds; + using std::mutex; + using std::string; + using std::queue; + + /** + * @brief CanDriver class; handles communication via CAN. + * + * This class provides the means of easily communicating with other devices via CAN in C++. + * + * @remarks + * This class may be inherited by other applications and modified to suit your needs. + */ + class CanDriver { + public: // +++ Static +++ + static const int32_t CAN_MAX_DATA_LENGTH; ///!< The maximum amount of bytes allowed in a single CAN frame + static const int32_t CAN_SOCK_RAW; ///!< The raw CAN protocol + static const int32_t CAN_SOCK_SEVEN; ///!< A separate CAN protocol, used by certain embedded device OEMs. + + public: // +++ Constructor / Destructor +++ + CanDriver(const string canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); ///!< Constructor + CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); + CanDriver() {} + virtual ~CanDriver() { uninitialiseSocketCan(); } ///!< Destructor + + public: // +++ Getter / Setter +++ + CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } ///!< Sets the default sender ID + + const CanId getDefaultSenderId() const { return this->_defaultSenderId; } ///!< Gets the default sender ID + + const int32_t getFilterMask() const { return this->_canFilterMask; } ///!< Gets the filter mask used by this instance + const int32_t getMessageQueueSize() const { return this->_queueSize; } ///!< Gets the amount of CAN messages found after last calling waitForMessages() + const int32_t getSocketFd() const { return this->_socketFd; } ///!< The socket file descriptor used by this instance. + + public: // +++ I/O +++ + virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); ///!< Waits for CAN messages to appear + + virtual CanMessage readMessage(); ///!< Attempts to read a single message from the bus + + virtual int32_t sendMessage(const CanMessage message, bool forceExtended = false); ///!< Attempts to send a single CAN message + virtual int32_t sendMessageQueue(queue messages, + milliseconds delay = milliseconds(20), bool forceExtended = false); ///!< Attempts to send a queue of messages + + virtual queue readQueuedMessages(); ///!< Attempts to read all queued messages from the bus + + virtual void setCanFilterMask(const int32_t mask); ///!< Attempts to set a new CAN filter mask to the BIOS + + protected: // +++ Socket Management +++ + virtual void initialiseSocketCan(); ///!< Initialises socketcan + virtual void uninitialiseSocketCan(); ///!< Uninitialises socketcan + + private: + virtual CanMessage readMessageLock(bool const lock = true); ///!< readMessage deadlock guard + CanId _defaultSenderId; ///!< The ID to send messages with if no other ID was set. + + int32_t _canFilterMask; ///!< The bit mask used to filter CAN messages + int32_t _canProtocol; ///!< The protocol used when communicating via CAN + int32_t _socketFd; ///!< The CAN socket file descriptor + int32_t _queueSize; ////!< The size of the message queue read by waitForMessages() + + mutex _lock; ///!< Mutex for thread-safety. + + string _canInterface; ///!< The CAN interface used for communication (e.g. can0, can1, ...) + + }; + + + + /** + * @brief Formats a std string object. + * + * @remarks Yoinked from https://github.com/Beatsleigher/liblogpp :) + * + * @tparam Args The formatting argument types. + * @param format The format string. + * @param args The format arguments (strings must be converted to C-style strings!) + * + * @return string The formatted string. + */ + template + string formatString(const string& format, Args... args) { + using std::unique_ptr; + auto stringSize = snprintf(NULL, 0, format.c_str(), args...) + 1; // +1 for \0 + unique_ptr buffer(new char[stringSize]); + + snprintf(buffer.get(), stringSize, format.c_str(), args...); + + return string(buffer.get(), buffer.get() + stringSize - 1); // std::string handles termination for us. + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP diff --git a/core/driver/sockcanpp/CanId.hpp b/core/driver/sockcanpp/CanId.hpp new file mode 100644 index 0000000..d2f8a47 --- /dev/null +++ b/core/driver/sockcanpp/CanId.hpp @@ -0,0 +1,236 @@ +/** + * @file CanId.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of a value-type representing a CAN identifier. + * @version 0.1 + * @date 2020-07-01 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKPP_INCLUDE_CANID_HPP +#define LIBSOCKPP_INCLUDE_CANID_HPP + +////////////////////////////// +// SYSTEM INCLUDES // +////////////////////////////// +#include +#include +#include +#include + +namespace sockcanpp { + + using std::bitset; + using std::error_code; + using std::exception; + using std::generic_category; + using std::system_error; + + /** + * @brief Represents a CAN ID in a simple and easy-to-use manner. + */ + struct CanId { + public: // +++ Constructors +++ + CanId(const CanId& orig): _identifier(orig._identifier), _isErrorFrame(orig._isErrorFrame), + _isRemoteTransmissionRequest(orig._isRemoteTransmissionRequest), _isStandardFrameId(orig._isStandardFrameId), + _isExtendedFrameId(orig._isExtendedFrameId) { /* copy */ } + + CanId(const uint32_t identifier): _identifier(identifier) { + // TODO: Switch to using bitmasks! + + if (isValidIdentifier(identifier)) { + if (((int32_t)log2(identifier) + 1) < 11) { + _isStandardFrameId = true; + } else { _isExtendedFrameId = true; } + } else if (isErrorFrame(identifier)) { + _isErrorFrame = true; + } else if (isRemoteTransmissionRequest(identifier)) { + _isRemoteTransmissionRequest = true; + } + } + + CanId(): _identifier(0), _isStandardFrameId(true) { } + + public: // +++ Operators +++ + +#pragma region "Implicit Cast Operators" + operator int16_t() const { return isStandardFrameId() ? (int16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } + operator uint16_t() const { return isStandardFrameId() ? (uint16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } + operator int32_t() const { return _identifier; } + operator uint32_t() const { return _identifier; } +#pragma endregion + +#pragma region "Bitwise Operators" + CanId operator &(CanId& x) const { return _identifier & x._identifier; } + CanId operator &(const CanId x) const { return _identifier & x._identifier; } + CanId operator &(const int16_t x) const { return _identifier & x; } + CanId operator &(const uint16_t x) const { return _identifier & x; } + CanId operator &(const int32_t x) const { return _identifier & x; } + CanId operator &(const uint32_t x) const { return _identifier & x; } + CanId operator &(const int64_t x) const { return _identifier & x; } + CanId operator &(const uint64_t x) const { return _identifier & x; } + + CanId operator |(CanId& x) const { return _identifier | x._identifier; } + CanId operator |(const CanId x) const { return _identifier | x._identifier; } + CanId operator |(const int16_t x) const { return _identifier | x; } + CanId operator |(const uint16_t x) const { return _identifier | x; } + CanId operator |(const int32_t x) const { return _identifier | x; } + CanId operator |(const uint32_t x) const { return _identifier | x; } + CanId operator |(const int64_t x) const { return _identifier | x; } + CanId operator |(const uint64_t x) const { return _identifier | x; } +#pragma endregion + +#pragma region "Comparison Operators" + bool operator ==(CanId& x) const { return _identifier == x._identifier; } + bool operator ==(const CanId& x) const { return _identifier == x._identifier; } + bool operator ==(const int16_t x) const { return _identifier == x; } + bool operator ==(const uint16_t x) const { return _identifier == x; } + bool operator ==(const int32_t x) const { return _identifier == x; } + bool operator ==(const uint32_t x) const { return _identifier == x; } + bool operator ==(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x == _identifier; } + bool operator ==(const uint64_t x) const { return x > UINT32_MAX ? false : x == _identifier; } + bool operator !=(CanId& x) const { return _identifier != x._identifier; } + bool operator !=(const CanId& x) const { return _identifier != x._identifier; } + bool operator !=(const int16_t x) const { return _identifier != x; } + bool operator !=(const uint16_t x) const { return _identifier != x; } + bool operator !=(const int32_t x) const { return _identifier != x; } + bool operator !=(const uint32_t x) const { return _identifier != x; } + bool operator !=(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x != _identifier; } + bool operator !=(const uint64_t x) const { return x > UINT32_MAX ? false : x != _identifier; } + + bool operator <(CanId& x) const { return x._identifier < _identifier; } + bool operator <(int32_t x) const { return x < _identifier; } + bool operator <(uint32_t x) const { return x < _identifier; } + bool operator <(int16_t x) const { return x < _identifier; } + bool operator <(uint16_t x) const { return x < _identifier; } + bool operator <=(CanId& x) const { return x._identifier <= _identifier; } + bool operator >(CanId& x) const { return x._identifier > _identifier; } + bool operator >(int32_t x) const { return x > _identifier; } + bool operator >(uint32_t x) const { return x > _identifier; } + bool operator >(int16_t x) const { return x > _identifier; } + bool operator >(uint16_t x) const { return x > _identifier; } + bool operator >=(CanId& x) const { return x._identifier >= _identifier; } + bool operator <(const CanId& x) const { return x._identifier < _identifier; } + bool operator <=(const CanId& x) const { return x._identifier <= _identifier; } + bool operator >(const CanId& x) const { return x._identifier > _identifier; } + bool operator >=(const CanId& x) const { return x._identifier >= _identifier; } +#pragma endregion + +#pragma region "Assignment Operators" + CanId operator =(const int32_t val) { + uint32_t tmpVal = val; + auto tmp = (isValidIdentifier(tmpVal) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); + return tmp; + } + + CanId operator =(const uint32_t val) { + uint32_t tmp = val; + return (isValidIdentifier(tmp) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); + } + + CanId operator =(const int64_t val) { return operator =((int32_t)val); } +#pragma endregion + +#pragma region "Arithmetic Operators" + CanId operator +(CanId& x) const { return _identifier + x._identifier; } + CanId operator +(const CanId& x) const { return _identifier + x._identifier; } + CanId operator +(const int16_t x) const { return _identifier + x; } + CanId operator +(const uint16_t x) const { return _identifier + x; } + CanId operator +(const int32_t x) const { return _identifier + x; } + CanId operator +(const uint32_t x) const { return _identifier + x; } + CanId operator +(const int64_t x) const { return _identifier + x; } + CanId operator +(const uint64_t x) const { return _identifier + x; } + + CanId operator -(CanId& x) const { return _identifier - x._identifier; } + CanId operator -(const CanId& x) const { return _identifier - x._identifier; } + CanId operator -(const int16_t x) const { return _identifier - x; } + CanId operator -(const uint16_t x) const { return _identifier - x; } + CanId operator -(const int32_t x) const { return _identifier - x; } + CanId operator -(const uint32_t x) const { return _identifier - x; } + CanId operator -(const int64_t x) const { return _identifier - x; } + CanId operator -(const uint64_t x) const { return _identifier - x; } +#pragma endregion + + public: // +++ Validity Checks +++ + /** + * @brief Indicates whether or not a given integer is a valid CAN identifier. + * + * @param value The integer to check. + * + * @return true If value is a valid CAN identifier. + * @return false Otherwise. + */ + static bool isValidIdentifier(uint32_t value) { + int32_t tmpValue = ((int32_t)log2(value) + 2); // Get bit count + + // Check for extended frame flag + if (tmpValue >= 29) { + value = (value & CAN_EFF_FLAG) ? (value & CAN_EFF_MASK) : (value & CAN_SFF_MASK); + tmpValue = ((int32_t)log2(value) + 1); // Get bit count again + } + + return (value == 0) /* Default value, also valid ID */ || ((tmpValue <= 29 && tmpValue > 0)); + } + + /** + * @brief Indicates whether or not a given integer contains the error frame flag or not. + * + * @param value The integer to check. + * + * @return true If value has the error frame flag (bit) set to 1. + * @return false Otherwise. + */ + static bool isErrorFrame(uint32_t value) { + try { return bitset(value).test(29); } + catch (...) { return false; /* Brute-force, but works. */ } + } + + /** + * @brief Indicates whether the received frame is a remote transmission request. + * + * @param value The integer to check. + * + * @return true If the frame is a remote transmission request. + * @return false Otherwise. + */ + static bool isRemoteTransmissionRequest(uint32_t value) { + try { return bitset(value).test(30); } + catch (...) { return false; /* Brute-force, but works. */ } + } + + public: // +++ Getters +++ + bool hasErrorFrameFlag() const { return _isErrorFrame; } + bool hasRtrFrameFlag() const { return _isRemoteTransmissionRequest; } + bool isStandardFrameId() const { return _isStandardFrameId; } + bool isExtendedFrameId() const { return _isExtendedFrameId; } + + public: // +++ Equality Checks +++ + bool equals(CanId otherId) const { return *this == otherId; } + + private: // +++ Variables +++ + bool _isErrorFrame = false; + bool _isRemoteTransmissionRequest = false; + bool _isStandardFrameId = false; + bool _isExtendedFrameId = false; + + uint32_t _identifier = 0; + }; + +} + +#endif // LIBSOCKPP_INCLUDE_CANID_HPP diff --git a/core/driver/sockcanpp/CanMessage.hpp b/core/driver/sockcanpp/CanMessage.hpp new file mode 100644 index 0000000..675c072 --- /dev/null +++ b/core/driver/sockcanpp/CanMessage.hpp @@ -0,0 +1,89 @@ +/** + * @file CanMessage.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of a CAN message representation in C++. + * @version 0.1 + * @date 2020-07-01 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP +#define LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP + +////////////////////////////// +// SYSTEM INCLUDES // +////////////////////////////// +#include + +#include +#include +#include +#include + +////////////////////////////// +// LOCAL INCLUDES // +////////////////////////////// +#include "CanId.hpp" + +namespace sockcanpp { + + using std::error_code; + using std::generic_category; + using std::memcpy; + using std::string; + using std::system_error; + + /** + * @brief Represents a CAN message that was received. + */ + class CanMessage { + public: // +++ Constructor / Destructor +++ + CanMessage(const struct can_frame frame): + _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } + + CanMessage(const CanId canId, const string frameData): _canIdentifier(canId), _frameData(frameData) { + if (frameData.size() > 8) { + throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); + } + + struct can_frame rawFrame; + rawFrame.can_id = canId; + memcpy(rawFrame.data, frameData.data(), frameData.size()); + rawFrame.can_dlc = frameData.size(); + + _rawFrame = rawFrame; + } + + virtual ~CanMessage() {} + + public: // +++ Getters +++ + const CanId getCanId() const { return this->_canIdentifier; } + const string getFrameData() const { return this->_frameData; } + const can_frame getRawFrame() const { return this->_rawFrame; } + + private: + CanId _canIdentifier; + + string _frameData; + + struct can_frame _rawFrame; + }; + +} + +#endif // LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP diff --git a/core/driver/sockcanpp/README.md b/core/driver/sockcanpp/README.md new file mode 100644 index 0000000..4d40df1 --- /dev/null +++ b/core/driver/sockcanpp/README.md @@ -0,0 +1,124 @@ + +``` +BaseOn: + https://github.com/SimonCahill/libsockcanpp + 4d0e6a3e679a34dee4449a9d22b7f794a3cf9871 +``` + + +# Usage +```cpp +/** + * @file Main.cpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of a test application using this library. + * @version 0.1 + * @date 2020-07-02 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include + +using sockcanpp::CanDriver; +using sockcanpp::CanId; +using sockcanpp::exceptions::CanException; +using sockcanpp::exceptions::CanInitException; +using sockcanpp::exceptions::InvalidSocketException; +using sockcanpp::CanMessage; + +using std::cerr; +using std::cout; +using std::endl; +using std::string; + +void printHelp(string); + +int main(int32_t argCount, char** argValues) { + int32_t desiredCanSocket = 0; + string canInterface; + + if (argCount > 2) { + for (int32_t i = 1; i < argCount; i++) { + if (argValues[i] == "--help" || argValues[i] == "-h") { + printHelp(argValues[0]); + return 0; + } else if (argValues[i] == "-protocol") { + desiredCanSocket = atoi(argValues[i + 1]); + i += 1; + continue; + } else if (argValues[i] == "-iface") { + canInterface = (argValues[i + 1]); + i += 1; + continue; + } + } + } + + if (desiredCanSocket <= 0) + desiredCanSocket = CanDriver::CAN_SOCK_RAW; + if (canInterface == "") + canInterface = "can0"; + + CanDriver* canDriver; + try { + canDriver = new CanDriver(canInterface, CAN_RAW); + } catch (CanInitException& ex) { + cerr << "An error occurred while initialising CanDriver: " << ex.what() << endl; + delete canDriver; + return -1; + } + + while (true) { + printf("Writing test message:\n"); + try { canDriver->sendMessage(CanMessage(0x555, "abcdefg8")); } + catch (CanException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } + catch (InvalidSocketException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } + + printf("Reading messages\n"); + if (!canDriver->waitForMessages()) continue; + + cout << "Reading queue..." << endl; + auto canMessages = canDriver->readQueuedMessages(); + while (!canMessages.empty()) { + auto msg = canMessages.front(); + canMessages.pop(); + + cout << "CAN ID: " << (int32_t)msg.getCanId() << endl + << "CAN data: "; + for (auto byte : msg.getFrameData()) + cout << std::hex << byte << " "; + cout << endl; + } + } +} + +void printHelp(string appname) { + cout << appname << endl << endl + << "-h\t\tPrints this menu" << endl + << "--help\t\tPrints this menu" << endl + << "-protocol " << endl + << "-iface " << endl; +} +``` + diff --git a/core/driver/sockcanpp/exceptions/CanCloseException.hpp b/core/driver/sockcanpp/exceptions/CanCloseException.hpp new file mode 100644 index 0000000..854d4cb --- /dev/null +++ b/core/driver/sockcanpp/exceptions/CanCloseException.hpp @@ -0,0 +1,53 @@ +/** + * @file CanCloseException.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of an exception that may be thrown when an error occurs while closing a CAN socket. + * @version 0.1 + * @date 2020-07-02 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP +#define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP + +#include +#include + +namespace sockcanpp { namespace exceptions { + + using std::exception; + using std::string; + + /** + * @brief An exception that may be thrown when an error occurs while closing a CAN socket. + */ + class CanCloseException: public exception { + public: // +++ Constructor / Destructor +++ + CanCloseException(string message): _message(message) {} + ~CanCloseException() {} + + public: // +++ Overrides +++ + const char* what() { return _message.c_str(); } + + private: + string _message; + }; + +} /* exceptions */ } /* sockcanpp */ + +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP \ No newline at end of file diff --git a/core/driver/sockcanpp/exceptions/CanException.hpp b/core/driver/sockcanpp/exceptions/CanException.hpp new file mode 100644 index 0000000..3c71f5b --- /dev/null +++ b/core/driver/sockcanpp/exceptions/CanException.hpp @@ -0,0 +1,58 @@ +/** + * @file CanException.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of a general-purpose exception which may be thrown when an error occurs when performing IO on a CAN socket. + * @version 0.1 + * @date 2020-07-02 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP +#define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP + +#include +#include + +namespace sockcanpp { namespace exceptions { + + using std::exception; + using std::string; + + /** + * @brief An exception that may be thrown when an error occurs while closing a CAN socket. + */ + class CanException: public exception { + public: // +++ Constructor / Destructor +++ + CanException(string message, int32_t socket): _message(message), _socket(socket) {} + ~CanException() {} + + public: // +++ Overrides +++ + const char* what() { return _message.c_str(); } + + public: // +++ Getter +++ + const int32_t getSocket() const { return _socket; } + + private: + int32_t _socket; + + string _message; + }; + +} /* exceptions */ } /* sockcanpp */ + +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP \ No newline at end of file diff --git a/core/driver/sockcanpp/exceptions/CanInitException.hpp b/core/driver/sockcanpp/exceptions/CanInitException.hpp new file mode 100644 index 0000000..8f3707a --- /dev/null +++ b/core/driver/sockcanpp/exceptions/CanInitException.hpp @@ -0,0 +1,53 @@ +/** + * @file CanInitException.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of an exception that is thrown when a CAN socket couldn't be inintialised. + * @version 0.1 + * @date 2020-07-02 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP +#define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP + +#include +#include + +namespace sockcanpp { namespace exceptions { + + using std::exception; + using std::string; + + /** + * @brief An exception that may be thrown when an error occurred while initialising a CAN socket. + */ + class CanInitException: public exception { + public: // +++ Constructor / Destructor +++ + CanInitException(string message): _message(message) { } + virtual ~CanInitException() { } + + public: // +++ Override +++ + const char* what() { return _message.c_str(); } + + private: + string _message; + }; + +} /* exceptions */ } /* sockcanpp */ + +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP \ No newline at end of file diff --git a/core/driver/sockcanpp/exceptions/InvalidSocketException.hpp b/core/driver/sockcanpp/exceptions/InvalidSocketException.hpp new file mode 100644 index 0000000..f7ab7dd --- /dev/null +++ b/core/driver/sockcanpp/exceptions/InvalidSocketException.hpp @@ -0,0 +1,58 @@ +/** + * @file InvalidSocketException.hpp + * @author Simon Cahill (simonc@online.de) + * @brief Contains the implementation of an exception that may be thrown when an invalid CAN socket is detected. + * @version 0.1 + * @date 2020-07-02 + * + * @copyright Copyright (c) 2020 Simon Cahill + * + * Copyright 2020 Simon Cahill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP +#define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP + +#include +#include + +namespace sockcanpp { namespace exceptions { + + using std::exception; + using std::string; + + /** + * @brief An exception that may be thrown when an error occurs while closing a CAN socket. + */ + class InvalidSocketException: public exception { + public: // +++ Constructor / Destructor +++ + InvalidSocketException(string message, int32_t socket): _message(message), _socket(socket) {} + ~InvalidSocketException() {} + + public: // +++ Overrides +++ + const char* what() { return _message.c_str(); } + + public: // +++ Getter +++ + const int32_t getSocket() const { return _socket; } + + private: + int32_t _socket; + + string _message; + }; + +} /* exceptions */ } /* sockcanpp */ + +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP \ No newline at end of file diff --git a/module.cmake b/module.cmake index 12b3c6d..19e9a26 100644 --- a/module.cmake +++ b/module.cmake @@ -26,6 +26,7 @@ set(DEP_SRC dep/iflytopcpp/core/components/process/process_unix.cpp dep/iflytopcpp/core/components/process/process.cpp dep/iflytopcpp/core/components/timer/simple_timer.cpp + dep/iflytopcpp/core/driver/sockcanpp/CanDriver.cpp # dep/iflytopcpp/core/components/audio/wav_recorder.cpp) set(DEP_DEFINE ${DEP_DEFINE})