Ursprung
14f5fb1ab7
Commit
d98978e8ca
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
151
server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp
Normale Datei
151
server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp
Normale Datei
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
@ -362,5 +362,9 @@
|
||||
{
|
||||
"term": "hardware:marklin_can",
|
||||
"definition": "Märklin CAN"
|
||||
},
|
||||
{
|
||||
"term": "marklin_can_interface_type:socket_can",
|
||||
"definition": "SocketCAN (Linux only)"
|
||||
}
|
||||
]
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren