ecos: added simulation iohandler (incomplete)
supports ECoS get info and view command
Dieser Commit ist enthalten in:
Ursprung
ee5de7705b
Commit
d701efccb9
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
74
server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp
Normale Datei
74
server/src/hardware/protocol/ecos/iohandler/simulationiohandler.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
49
server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp
Normale Datei
49
server/src/hardware/protocol/ecos/iohandler/simulationiohandler.hpp
Normale Datei
@ -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
|
||||
|
||||
@ -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--;
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren