diff --git a/server/src/hardware/interface/ecosinterface.cpp b/server/src/hardware/interface/ecosinterface.cpp index 97f699a6..173e11e5 100644 --- a/server/src/hardware/interface/ecosinterface.cpp +++ b/server/src/hardware/interface/ecosinterface.cpp @@ -24,6 +24,7 @@ #include "../input/list/inputlisttablemodel.hpp" #include "../output/list/outputlisttablemodel.hpp" #include "../protocol/ecos/iohandler/tcpiohandler.hpp" +#include "../protocol/ecos/iohandler/simulationiohandler.hpp" #include "../../core/attributes.hpp" #include "../../log/log.hpp" #include "../../log/logmessageexception.hpp" @@ -173,17 +174,14 @@ bool ECoSInterface::setOutputValue(uint32_t channel, uint32_t address, bool valu bool ECoSInterface::setOnline(bool& value, bool simulation) { - if(simulation) - { - Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); - return false; - } - if(!m_kernel && value) { try { - m_kernel = ECoS::Kernel::create(ecos->config(), hostname.value()); + if(simulation) + m_kernel = ECoS::Kernel::create(ecos->config()); + else + m_kernel = ECoS::Kernel::create(ecos->config(), hostname.value()); status.setValueInternal(InterfaceStatus::Initializing); diff --git a/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp b/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp deleted file mode 100644 index 94cad124..00000000 --- a/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - * server/src/hardware/protocol/ecos/iohandler/iohandler.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2021-2022 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 "iohandler.hpp" -#include "../kernel.hpp" - -namespace ECoS { - -IOHandler::IOHandler(Kernel& kernel) - : m_kernel{kernel} - , m_readBufferOffset{0} - , m_readPos{0} - , m_writeBufferOffset{0} -{ -} - -bool IOHandler::send(std::string_view message) -{ - if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) - return false; - - const bool wasEmpty = m_writeBufferOffset == 0; - memcpy(m_writeBuffer.data() + m_writeBufferOffset, message.data(), message.size()); - m_writeBufferOffset += message.size(); - - if(wasEmpty) - write(); - - return true; -} - -void IOHandler::processRead(size_t bytesTransferred) -{ - static constexpr std::string_view typeReply{"REPLY"}; - static constexpr std::string_view typeEvent{"EVENT"}; - static constexpr size_t typeLength = 5; - - std::string_view buffer{m_readBuffer.data(), m_readBufferOffset + bytesTransferred}; - - //! @todo this can be a bit optimized by remembering the "state" when a message in not yet complete. - while(m_readPos != buffer.size()) - { - m_readPos = buffer.find('<', m_readPos); - if(m_readPos != std::string_view::npos) - { - if((buffer.size() - m_readPos) >= typeLength) - { - std::string_view type{m_readBuffer.data() + m_readPos + 1, typeLength}; - if(type == typeReply || type == typeEvent) - { - size_t pos = buffer.find(std::string_view{"', pos); - if(end != std::string_view::npos) - { - receive(std::string_view{m_readBuffer.data() + m_readPos, end - m_readPos + 1}); - m_readPos = end + 1; - } - else - break; - } - else - break; - } - } - else - break; - } - else - m_readPos = buffer.size(); - } - - if(m_readPos > 0) - { - assert(m_readPos <= buffer.size()); - m_readBufferOffset = buffer.size() - m_readPos; - if(m_readBufferOffset > 0) - memmove(m_readBuffer.data(), m_readBuffer.data() + m_readPos, m_readBufferOffset); - m_readPos = 0; - } -} - -void IOHandler::receive(std::string_view message) -{ - m_kernel.receive(message); -} - -} diff --git a/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp b/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp index 5f09af57..7d8d5340 100644 --- a/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp +++ b/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp @@ -35,17 +35,11 @@ class IOHandler { protected: Kernel& m_kernel; - std::array m_readBuffer; - size_t m_readBufferOffset; - size_t m_readPos; - std::array m_writeBuffer; - size_t m_writeBufferOffset; - IOHandler(Kernel& kernel); - - void processRead(size_t bytesTransferred); - virtual void receive(std::string_view message); - virtual void write() = 0; + IOHandler(Kernel& kernel) + : m_kernel{kernel} + { + } public: IOHandler(const IOHandler&) = delete; @@ -56,7 +50,7 @@ class IOHandler virtual void start() = 0; virtual void stop() = 0; - bool send(std::string_view message); + virtual bool send(std::string_view message) = 0; }; } diff --git a/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp new file mode 100644 index 00000000..923bf0d9 --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp @@ -0,0 +1,74 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 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 "simulationiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" + +namespace ECoS { + +SimulationIOHandler::SimulationIOHandler(Kernel& kernel) + : IOHandler(kernel) +{ +} + +bool SimulationIOHandler::send(std::string_view message) +{ + Request request; + if(!parseRequest(message, request)) + return false; + + if(request.command == Command::request && request.options.size() == 1 && request.options[0] == Option::view) + { + return reply(std::string("\r\n\r\n")); + } + + if(request.objectId == ObjectId::ecos) + { + if(request.command == Command::get && request.options.size() == 1 && request.options[0] == Option::info) + { + return reply( + "\r\n" + "1 ECoS2\r\n" + "1 ProtocolVersion[0.5]\r\n" + "1 ApplicationVersion[4.2.6]\r\n" + "1 HardwareVersion[2.0]\r\n" + "\r\n"); + } + } + + return reply(std::string("\r\n\r\n")); +} + +bool SimulationIOHandler::reply(std::string_view message) +{ + // post the reply, so it has some delay + m_kernel.ioContext().post( + [this, data=std::string(message)]() + { + m_kernel.receive(data); + }); + + return true; +} + +} diff --git a/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp new file mode 100644 index 00000000..5d7e1154 --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp @@ -0,0 +1,49 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 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_ECOS_IOHANDLER_SIMULATIONIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_IOHANDLER_SIMULATIONIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include + +namespace ECoS { + +class SimulationIOHandler final : public IOHandler +{ + private: + bool reply(std::string_view message); + + public: + SimulationIOHandler(Kernel& kernel); + + void start() final {} + void stop() final {} + + bool send(std::string_view message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp index c4d6c824..81148abc 100644 --- a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp +++ b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp @@ -65,6 +65,21 @@ void TCPIOHandler::stop() m_socket.close(); } +bool TCPIOHandler::send(std::string_view message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + memcpy(m_writeBuffer.data() + m_writeBufferOffset, message.data(), message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + void TCPIOHandler::read() { m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), @@ -87,9 +102,62 @@ void TCPIOHandler::read() }); } +void TCPIOHandler::processRead(size_t bytesTransferred) +{ + static constexpr std::string_view typeReply{"REPLY"}; + static constexpr std::string_view typeEvent{"EVENT"}; + static constexpr size_t typeLength = 5; + + std::string_view buffer{m_readBuffer.data(), m_readBufferOffset + bytesTransferred}; + + //! @todo this can be a bit optimized by remembering the "state" when a message in not yet complete. + while(m_readPos != buffer.size()) + { + m_readPos = buffer.find('<', m_readPos); + if(m_readPos != std::string_view::npos) + { + if((buffer.size() - m_readPos) >= typeLength) + { + std::string_view type{m_readBuffer.data() + m_readPos + 1, typeLength}; + if(type == typeReply || type == typeEvent) + { + size_t pos = buffer.find(std::string_view{"', pos); + if(end != std::string_view::npos) + { + receive(std::string_view{m_readBuffer.data() + m_readPos, end - m_readPos + 1}); + m_readPos = end + 1; + } + else + break; + } + else + break; + } + } + else + break; + } + else + m_readPos = buffer.size(); + } + + if(m_readPos > 0) + { + assert(m_readPos <= buffer.size()); + m_readBufferOffset = buffer.size() - m_readPos; + if(m_readBufferOffset > 0) + memmove(m_readBuffer.data(), m_readBuffer.data() + m_readPos, m_readBufferOffset); + m_readPos = 0; + } +} + void TCPIOHandler::receive(std::string_view message) { - IOHandler::receive(message); + m_kernel.receive(message); + if(m_waitingForReply > 0 && isReply(message)) { m_waitingForReply--; diff --git a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp index 9022c934..88893e76 100644 --- a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp +++ b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp @@ -34,14 +34,19 @@ class TCPIOHandler final : public IOHandler static constexpr uint32_t transferWindow = 25; boost::asio::ip::tcp::socket m_socket; boost::asio::ip::tcp::endpoint m_endpoint; + std::array m_readBuffer; + size_t m_readBufferOffset = 0; + size_t m_readPos = 0; + std::array m_writeBuffer; + size_t m_writeBufferOffset = 0; bool m_writing = false; uint32_t m_waitingForReply = 0; - void read(); - protected: - void receive(std::string_view message) final; - void write() final; + void read(); + void processRead(size_t bytesTransferred); + void receive(std::string_view message); + void write(); public: TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port = 15471); @@ -49,6 +54,8 @@ class TCPIOHandler final : public IOHandler void start() final; void stop() final; + + bool send(std::string_view message) final; }; } diff --git a/server/src/hardware/protocol/ecos/messages.cpp b/server/src/hardware/protocol/ecos/messages.cpp index af63d92d..bc4e56d5 100644 --- a/server/src/hardware/protocol/ecos/messages.cpp +++ b/server/src/hardware/protocol/ecos/messages.cpp @@ -31,6 +31,42 @@ namespace ECoS { static const std::string_view startDelimiterReply = " n) + request.options.emplace_back(&message[n], pos - n); + if(message[pos] == ')') + break; + n = pos + 1; + } + + return true; +} + bool isReply(std::string_view message) { return startsWith(message, startDelimiterReply); diff --git a/server/src/hardware/protocol/ecos/messages.hpp b/server/src/hardware/protocol/ecos/messages.hpp index 197e1065..05dfe236 100644 --- a/server/src/hardware/protocol/ecos/messages.hpp +++ b/server/src/hardware/protocol/ecos/messages.hpp @@ -86,6 +86,13 @@ enum class Status : uint32_t Ok = 0, }; +struct Request +{ + std::string_view command; + uint16_t objectId; + std::vector options; +}; + struct Reply { std::string_view command; @@ -193,6 +200,8 @@ inline std::string release(uint16_t objectId, std::initializer_list