From d98978e8ca2af872e3ab61517a6ccae8bd12a745 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Wed, 12 Jul 2023 23:37:19 +0200 Subject: [PATCH] marklin_can: added SocketCAN iohandler see #11 --- server/CMakeLists.txt | 4 + .../interface/marklincaninterface.cpp | 48 +++++- .../interface/marklincaninterface.hpp | 3 + .../iohandler/socketcaniohandler.cpp | 151 ++++++++++++++++++ .../iohandler/socketcaniohandler.hpp | 58 +++++++ shared/src/traintastic/enum/logmessage.hpp | 3 + .../enum/marklincaninterfacetype.hpp | 12 +- shared/translations/en-us.json | 12 ++ shared/translations/neutral.json | 4 + 9 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp create mode 100644 server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 51adcba4..e2a99755 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -198,6 +198,10 @@ if(LINUX) target_link_libraries(traintastic-server PRIVATE PkgConfig::LIBSYSTEMD) target_link_libraries(traintastic-server-test PRIVATE PkgConfig::LIBSYSTEMD) endif() +else() + # socket CAN is only available on linux: + file(GLOB SOCKET_CAN_SOURCES "src/hardware/protocol/marklincan/iohandler/socketcaniohandler.*") + list(REMOVE_ITEM SOURCES ${SOCKET_CAN_SOURCES}) endif() if(WIN32) diff --git a/server/src/hardware/interface/marklincaninterface.cpp b/server/src/hardware/interface/marklincaninterface.cpp index c76f18f8..37b2a449 100644 --- a/server/src/hardware/interface/marklincaninterface.cpp +++ b/server/src/hardware/interface/marklincaninterface.cpp @@ -28,6 +28,9 @@ #include "../protocol/marklincan/iohandler/simulationiohandler.hpp" #include "../protocol/marklincan/iohandler/tcpiohandler.hpp" #include "../protocol/marklincan/iohandler/udpiohandler.hpp" +#ifdef __linux__ + #include "../protocol/marklincan/iohandler/socketcaniohandler.hpp" +#endif #include "../protocol/marklincan/kernel.hpp" #include "../protocol/marklincan/settings.hpp" #include "../../core/attributes.hpp" @@ -43,8 +46,13 @@ MarklinCANInterface::MarklinCANInterface(World& world, std::string_view _id) : Interface(world, _id) , DecoderController(*this, decoderListColumns) , InputController(static_cast(*this)) - , type{this, "type", MarklinCANInterfaceType::NetworkTCP, PropertyFlags::ReadWrite | PropertyFlags::Store} + , type{this, "type", MarklinCANInterfaceType::NetworkTCP, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](MarklinCANInterfaceType /*value*/) + { + typeChanged(); + }} , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , interface{this, "interface", "", PropertyFlags::ReadWrite | PropertyFlags::Store} , marklinCAN{this, "marklin_can", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} { name = "M\u00E4rklin CAN"; @@ -57,14 +65,22 @@ MarklinCANInterface::MarklinCANInterface(World& world, std::string_view _id) Attributes::addDisplayName(hostname, DisplayName::IP::hostname); Attributes::addEnabled(hostname, !online); + Attributes::addVisible(hostname, false); m_interfaceItems.insertBefore(hostname, notes); + Attributes::addDisplayName(interface, DisplayName::Hardware::interface); + Attributes::addEnabled(interface, !online); + Attributes::addVisible(interface, false); + m_interfaceItems.insertBefore(interface, notes); + Attributes::addDisplayName(marklinCAN, DisplayName::Hardware::marklinCAN); m_interfaceItems.insertBefore(marklinCAN, notes); m_interfaceItems.insertBefore(decoders, notes); m_interfaceItems.insertBefore(inputs, notes); + + typeChanged(); } tcb::span MarklinCANInterface::decoderProtocols() const @@ -119,6 +135,16 @@ bool MarklinCANInterface::setOnline(bool& value, bool simulation) case MarklinCANInterfaceType::NetworkUDP: m_kernel = MarklinCAN::Kernel::create(marklinCAN->config(), hostname.value()); break; + + case MarklinCANInterfaceType::SocketCAN: +#ifdef __linux__ + m_kernel = MarklinCAN::Kernel::create(marklinCAN->config(), interface.value()); + break; +#else + setState(InterfaceState::Error); + Log::log(*this, LogMessage::C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX); + return false; +#endif } } assert(m_kernel); @@ -144,7 +170,7 @@ bool MarklinCANInterface::setOnline(bool& value, bool simulation) m_kernel->setConfig(marklinCAN->config()); }); - Attributes::setEnabled({type, hostname}, false); + Attributes::setEnabled({type, hostname, interface}, false); } catch(const LogMessageException& e) { @@ -155,14 +181,15 @@ bool MarklinCANInterface::setOnline(bool& value, bool simulation) } else if(m_kernel && !value) { - Attributes::setEnabled({type, hostname}, true); + Attributes::setEnabled({type, hostname, interface}, true); m_marklinCANPropertyChanged.disconnect(); m_kernel->stop(); m_kernel.reset(); - setState(InterfaceState::Offline); + if(status->state != InterfaceState::Error) + setState(InterfaceState::Offline); } return true; } @@ -174,6 +201,13 @@ void MarklinCANInterface::addToWorld() InputController::addToWorld(inputListColumns); } +void MarklinCANInterface::loaded() +{ + Interface::loaded(); + + typeChanged(); +} + void MarklinCANInterface::destroying() { InputController::destroying(); @@ -217,3 +251,9 @@ void MarklinCANInterface::idChanged(const std::string& newId) if(m_kernel) m_kernel->setLogId(newId); } + +void MarklinCANInterface::typeChanged() +{ + Attributes::setVisible(hostname, isNetwork(type)); + Attributes::setVisible(interface, type == MarklinCANInterfaceType::SocketCAN); +} diff --git a/server/src/hardware/interface/marklincaninterface.hpp b/server/src/hardware/interface/marklincaninterface.hpp index 86c5fea5..faf88311 100644 --- a/server/src/hardware/interface/marklincaninterface.hpp +++ b/server/src/hardware/interface/marklincaninterface.hpp @@ -47,10 +47,12 @@ class MarklinCANInterface final boost::signals2::connection m_marklinCANPropertyChanged; void addToWorld() final; + void loaded() final; void destroying() final; void worldEvent(WorldState state, WorldEvent event) final; void idChanged(const std::string& newId) final; + void typeChanged(); protected: bool setOnline(bool& value, bool simulation) final; @@ -58,6 +60,7 @@ class MarklinCANInterface final public: Property type; Property hostname; + Property interface; ObjectProperty marklinCAN; MarklinCANInterface(World& world, std::string_view _id); diff --git a/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp b/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp new file mode 100644 index 00000000..226c3de5 --- /dev/null +++ b/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp @@ -0,0 +1,151 @@ +/** + * server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2023 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "socketcaniohandler.hpp" +#include +#include +#include +#include +#include +#include +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace MarklinCAN { + +SocketCANIOHandler::SocketCANIOHandler(Kernel& kernel, const std::string& interface) + : IOHandler(kernel) + , m_stream{kernel.ioContext()} +{ + int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if(fd < 0) + throw LogMessageException(LogMessage::E2022_SOCKET_CREATE_FAILED_X, std::string_view(strerror(errno))); + + struct ifreq ifr; + std::strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ); + if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0) + throw LogMessageException(LogMessage::E2023_SOCKET_IOCTL_FAILED_X, std::string_view(strerror(errno))); + + struct sockaddr_can addr; + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + if(bind(fd, reinterpret_cast(&addr), sizeof(addr)) < 0) + throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, std::string_view(strerror(errno))); + + m_stream.assign(fd); +} + +void SocketCANIOHandler::start() +{ + read(); +} + +void SocketCANIOHandler::stop() +{ + m_stream.cancel(); + m_stream.close(); +} + +bool SocketCANIOHandler::send(const Message& message) +{ + if(m_writeBufferOffset + 1 > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + + auto& frame = m_writeBuffer[m_writeBufferOffset]; + frame.can_id = CAN_EFF_FLAG | (message.id & CAN_EFF_MASK); + frame.len = message.dlc; + std::memcpy(frame.data, message.data, message.dlc); + + m_writeBufferOffset++; + + if(wasEmpty) + write(); + + return true; +} + +void SocketCANIOHandler::read() +{ + m_stream.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset * frameSize, (m_readBuffer.size() - m_readBufferOffset) * frameSize), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + auto framesTransferred = bytesTransferred / frameSize; + const auto* frame = reinterpret_cast(m_readBuffer.data()); + framesTransferred += m_readBufferOffset; + + while(framesTransferred > 0) + { + Message message; + message.id = frame->can_id & CAN_EFF_MASK; + message.dlc = frame->len; + std::memcpy(message.data, frame->data, frame->len); + m_kernel.receive(message); + frame++; + framesTransferred--; + } + + if(framesTransferred != 0) /*[[unlikely]]*/ + memmove(m_readBuffer.data(), frame, framesTransferred * frameSize); + m_readBufferOffset = framesTransferred; + + read(); + } + else if(ec != boost::asio::error::operation_aborted) + { + Log::log(m_kernel.logId(), LogMessage::E2008_SOCKET_READ_FAILED_X, ec); + //! \todo LogMessage + } + }); +} + +void SocketCANIOHandler::write() +{ + m_stream.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset * frameSize), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + const auto framesTransferred = bytesTransferred / frameSize; + if(framesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= framesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + framesTransferred * frameSize, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + Log::log(m_kernel.logId(), LogMessage::E2007_SOCKET_WRITE_FAILED_X, ec); + //! \todo LogMessage + } + }); +} + +} diff --git a/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp b/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp new file mode 100644 index 00000000..c8678a1f --- /dev/null +++ b/server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp @@ -0,0 +1,58 @@ +/** + * server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2023 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_SOCKETCANIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_SOCKETCANIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include +#include + +namespace MarklinCAN { + +class SocketCANIOHandler : public IOHandler +{ + private: + static constexpr size_t frameSize = sizeof(struct can_frame); + + boost::asio::posix::stream_descriptor m_stream; + std::array m_readBuffer; + size_t m_readBufferOffset = 0; + std::array m_writeBuffer; + size_t m_writeBufferOffset = 0; + + void read(); + void write(); + + public: + SocketCANIOHandler(Kernel& kernel, const std::string& interface); + + void start() final; + void stop() final; + + bool send(const Message& message) final; +}; + +} + +#endif diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index f4eab1ca..d59641f6 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -172,6 +172,8 @@ enum class LogMessage : uint32_t E2019_TIMEOUT_NO_RESPONSE_WITHIN_X_MS = LogMessageOffset::error + 2019, E2020_TOTAL_NUMBER_OF_MODULES_MAY_NOT_EXCEED_X = LogMessageOffset::error + 2020, E2021_STARTING_PCAP_LOG_FAILED_X = LogMessageOffset::error + 2021, + E2022_SOCKET_CREATE_FAILED_X = LogMessageOffset::error + 2022, + E2023_SOCKET_IOCTL_FAILED_X = LogMessageOffset::error + 2023, E9001_X_DURING_EXECUTION_OF_X_EVENT_HANDLER = LogMessageOffset::error + 9001, E9999_X = LogMessageOffset::error + 9999, @@ -191,6 +193,7 @@ enum class LogMessage : uint32_t C1013_CANT_LOAD_WORLD_SAVED_WITH_NEWER_VERSION_REQUIRES_AT_LEAST_X = LogMessageOffset::critical + 1013, C2001_ADDRESS_ALREADY_USED_AT_X = LogMessageOffset::critical + 2001, C2004_CANT_GET_FREE_SLOT = LogMessageOffset::critical + 2004, + C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX = LogMessageOffset::critical + 2005, C9999_X = LogMessageOffset::critical + 9999, // Fatal: diff --git a/shared/src/traintastic/enum/marklincaninterfacetype.hpp b/shared/src/traintastic/enum/marklincaninterfacetype.hpp index b9aaabb2..53773ffa 100644 --- a/shared/src/traintastic/enum/marklincaninterfacetype.hpp +++ b/shared/src/traintastic/enum/marklincaninterfacetype.hpp @@ -31,17 +31,25 @@ enum class MarklinCANInterfaceType : uint16_t { NetworkTCP = 0, NetworkUDP = 1, + SocketCAN = 2, }; -TRAINTASTIC_ENUM(MarklinCANInterfaceType, "marklin_can_interface_type", 2, +TRAINTASTIC_ENUM(MarklinCANInterfaceType, "marklin_can_interface_type", 3, { {MarklinCANInterfaceType::NetworkTCP, "network_tcp"}, {MarklinCANInterfaceType::NetworkUDP, "network_udp"}, + {MarklinCANInterfaceType::SocketCAN, "socket_can"}, }); -inline constexpr std::array marklinCANInterfaceTypeValues{{ +inline constexpr std::array marklinCANInterfaceTypeValues{{ MarklinCANInterfaceType::NetworkTCP, MarklinCANInterfaceType::NetworkUDP, + MarklinCANInterfaceType::SocketCAN, }}; +constexpr bool isNetwork(MarklinCANInterfaceType value) +{ + return value == MarklinCANInterfaceType::NetworkTCP || value == MarklinCANInterfaceType::NetworkUDP; +} + #endif diff --git a/shared/translations/en-us.json b/shared/translations/en-us.json index 59635639..f4637279 100644 --- a/shared/translations/en-us.json +++ b/shared/translations/en-us.json @@ -4230,5 +4230,17 @@ { "term": "marklin_can_interface_type:network_udp", "definition": "Network (UDP)" + }, + { + "term": "message:E2022", + "definition": "Socket create failed (%1)" + }, + { + "term": "message:E2023", + "definition": "Socket ioctl failed (%1)" + }, + { + "term": "message:C2005", + "definition": "SocketCAN is only available on Linux" } ] \ No newline at end of file diff --git a/shared/translations/neutral.json b/shared/translations/neutral.json index bcc96115..acf4eb65 100644 --- a/shared/translations/neutral.json +++ b/shared/translations/neutral.json @@ -362,5 +362,9 @@ { "term": "hardware:marklin_can", "definition": "Märklin CAN" + }, + { + "term": "marklin_can_interface_type:socket_can", + "definition": "SocketCAN (Linux only)" } ] \ No newline at end of file