marklin_can: added SocketCAN iohandler

see #11
Dieser Commit ist enthalten in:
Reinder Feenstra 2023-07-12 23:37:19 +02:00
Ursprung 14f5fb1ab7
Commit d98978e8ca
9 geänderte Dateien mit 289 neuen und 6 gelöschten Zeilen

Datei anzeigen

@ -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)

Datei anzeigen

@ -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<IdObject&>(*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<const DecoderProtocol> MarklinCANInterface::decoderProtocols() const
@ -119,6 +135,16 @@ bool MarklinCANInterface::setOnline(bool& value, bool simulation)
case MarklinCANInterfaceType::NetworkUDP:
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::UDPIOHandler>(marklinCAN->config(), hostname.value());
break;
case MarklinCANInterfaceType::SocketCAN:
#ifdef __linux__
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SocketCANIOHandler>(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);
}

Datei anzeigen

@ -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<MarklinCANInterfaceType> type;
Property<std::string> hostname;
Property<std::string> interface;
ObjectProperty<MarklinCAN::Settings> marklinCAN;
MarklinCANInterface(World& world, std::string_view _id);

Datei anzeigen

@ -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 <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can/raw.h>
#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<struct sockaddr*>(&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<const struct can_frame*>(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
}
});
}
}

Datei anzeigen

@ -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 <string>
#include <linux/can.h>
#include <boost/asio/posix/stream_descriptor.hpp>
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<struct can_frame, 32> m_readBuffer;
size_t m_readBufferOffset = 0;
std::array<struct can_frame, 32> 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

Datei anzeigen

@ -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:

Datei anzeigen

@ -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<MarklinCANInterfaceType, 2> marklinCANInterfaceTypeValues{{
inline constexpr std::array<MarklinCANInterfaceType, 3> marklinCANInterfaceTypeValues{{
MarklinCANInterfaceType::NetworkTCP,
MarklinCANInterfaceType::NetworkUDP,
MarklinCANInterfaceType::SocketCAN,
}};
constexpr bool isNetwork(MarklinCANInterfaceType value)
{
return value == MarklinCANInterfaceType::NetworkTCP || value == MarklinCANInterfaceType::NetworkUDP;
}
#endif

Datei anzeigen

@ -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"
}
]

Datei anzeigen

@ -362,5 +362,9 @@
{
"term": "hardware:marklin_can",
"definition": "Märklin CAN"
},
{
"term": "marklin_can_interface_type:socket_can",
"definition": "SocketCAN (Linux only)"
}
]