ecos: added simulation iohandler (incomplete)

supports ECoS get info and view command
Dieser Commit ist enthalten in:
Reinder Feenstra 2022-04-03 22:01:36 +02:00
Ursprung ee5de7705b
Commit d701efccb9
9 geänderte Dateien mit 258 neuen und 131 gelöschten Zeilen

Datei anzeigen

@ -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::TCPIOHandler>(ecos->config(), hostname.value());
if(simulation)
m_kernel = ECoS::Kernel::create<ECoS::SimulationIOHandler>(ecos->config());
else
m_kernel = ECoS::Kernel::create<ECoS::TCPIOHandler>(ecos->config(), hostname.value());
status.setValueInternal(InterfaceStatus::Initializing);

Datei anzeigen

@ -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{"<END"}, m_readPos);
if(pos != std::string_view::npos)
{
size_t end = buffer.find('>', 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);
}
}

Datei anzeigen

@ -35,17 +35,11 @@ class IOHandler
{
protected:
Kernel& m_kernel;
std::array<char, 32 * 1024> m_readBuffer;
size_t m_readBufferOffset;
size_t m_readPos;
std::array<char, 32 * 1024> 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;
};
}

Datei anzeigen

@ -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("<REPLY ").append(message).append(">\r\n<END 0 (OK)>\r\n"));
}
if(request.objectId == ObjectId::ecos)
{
if(request.command == Command::get && request.options.size() == 1 && request.options[0] == Option::info)
{
return reply(
"<REPLY get(1, info)>\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"
"<END 0 (OK)>\r\n");
}
}
return reply(std::string("<REPLY ").append(message).append(">\r\n<END 999 (Traintastic: no simulation support)>\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;
}
}

Datei anzeigen

@ -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 <array>
#include <cstddef>
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

Datei anzeigen

@ -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{"<END"}, m_readPos);
if(pos != std::string_view::npos)
{
size_t end = buffer.find('>', 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--;

Datei anzeigen

@ -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<char, 32 * 1024> m_readBuffer;
size_t m_readBufferOffset = 0;
size_t m_readPos = 0;
std::array<char, 32 * 1024> 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;
};
}

Datei anzeigen

@ -31,6 +31,42 @@ namespace ECoS {
static const std::string_view startDelimiterReply = "<REPLY ";
static const std::string_view endDelimiter = "<END ";
bool parseRequest(std::string_view message, Request& request)
{
// read command:
size_t pos;
if((pos = message.find('(')) == std::string_view::npos)
return false;
request.command = message.substr(0, pos);
// read objectId:
auto r = fromChars(message.substr(pos + 1), request.objectId);
if(r.ec != std::errc())
return false;
// read arguments
size_t n = r.ptr - message.data();
while((pos = std::min(std::min(message.find(',', n), message.find(')', n)), message.find('[', n))) != std::string_view::npos)
{
if(message[pos] == '[')
{
if((pos = message.find(']', pos)) == std::string_view::npos)
return false;
if((pos = std::min(message.find(',', pos), message.find(')', pos))) == std::string_view::npos)
return false;
}
while(message[n] == ' ' && n < pos)
n++;
if(pos > 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);

Datei anzeigen

@ -86,6 +86,13 @@ enum class Status : uint32_t
Ok = 0,
};
struct Request
{
std::string_view command;
uint16_t objectId;
std::vector<std::string_view> options;
};
struct Reply
{
std::string_view command;
@ -193,6 +200,8 @@ inline std::string release(uint16_t objectId, std::initializer_list<std::string_
return buildCommand(Command::release, objectId, options);
}
bool parseRequest(std::string_view message, Request& request);
bool isReply(std::string_view message);
bool parseReply(std::string_view message, Reply& reply);
bool parseEvent(std::string_view message, Event& event);