marklin_can: added reading of loks list (over CAN)

see #11
Dieser Commit ist enthalten in:
Reinder Feenstra 2023-07-30 00:48:17 +02:00
Ursprung 31e5be6405
Commit 6095969cd0
13 geänderte Dateien mit 661 neuen und 2 gelöschten Zeilen

Datei anzeigen

@ -116,6 +116,8 @@ file(GLOB SOURCES
"src/hardware/protocol/marklincan/*.cpp"
"src/hardware/protocol/marklincan/iohandler/*.hpp"
"src/hardware/protocol/marklincan/iohandler/*.cpp"
"src/hardware/protocol/marklincan/message/*.hpp"
"src/hardware/protocol/marklincan/message/*.cpp"
"src/hardware/protocol/traintasticdiy/*.hpp"
"src/hardware/protocol/traintasticdiy/*.cpp"
"src/hardware/protocol/traintasticdiy/iohandler/*.hpp"

Datei anzeigen

@ -29,6 +29,7 @@ struct Config
{
uint32_t defaultSwitchTime; //!< Default switch time in ms
bool debugLogRXTX;
bool debugConfigStream;
};
}

Datei anzeigen

@ -0,0 +1,115 @@
/**
* server/src/hardware/protocol/marklincan/configdatastreamcollector.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 "configdatastreamcollector.hpp"
namespace MarklinCAN {
//! CRC algorithm as documented by Marklin in the CS2 CAN documentation
static uint16_t crc16(uint16_t crc, uint8_t value)
{
constexpr uint16_t poly = 0x1021;
// Create the CRC "dividend" for polynomial arithmetic (binary arithmetic with no carries)
crc = crc ^ (static_cast<uint16_t>(value) << 8);
// "Divide" the poly into the dividend using CRC XOR subtraction CRC_acc holds the
// "remainder" of each divide. Only complete this division for 8 bits since input is 1 byte
for (int i = 0; i < 8; i++)
{
// Check if the MSB is set (if MSB is 1, then the POLY can "divide" into the "dividend")
if((crc & 0x8000) == 0x8000)
{
// if so, shift the CRC value, and XOR "subtract" the poly
crc = crc << 1;
crc ^= poly;
}
else
{
// if not, just shift the CRC value
crc = crc << 1;
}
}
// Return the final remainder (CRC value)
return crc;
}
static uint16_t crc16(const std::vector<std::byte>& data)
{
uint16_t crc = 0xFFFF;
for(auto value : data)
{
crc = crc16(crc, static_cast<uint8_t>(value));
}
if(data.size() % 8 != 0)
{
// unused nul bytes must be included in CRC:
const size_t n = 8 - (data.size() % 8);
for(size_t i = 0; i < n; i++)
crc = crc16(crc, 0x00);
}
return crc;
}
ConfigDataStreamCollector::ConfigDataStreamCollector(std::string name_)
: name{std::move(name_)}
{
}
ConfigDataStreamCollector::Status ConfigDataStreamCollector::process(const ConfigDataStream& message)
{
if(message.isData() && m_crc != 0x0000)
{
if(m_offset >= m_data.size()) /*[[unlikely]]*/
return ErrorToMuchData;
if(m_offset + 8 >= m_data.size()) // last message
{
std::memcpy(m_data.data() + m_offset, message.data, m_data.size() - m_offset);
m_offset = m_data.size();
if(crc16(m_data) != m_crc)
return ErrorInvalidCRC;
return Complete;
}
std::memcpy(m_data.data() + m_offset, message.data, 8);
m_offset += 8;
return Collecting;
}
else if(message.isStart() && m_crc == 0x0000)
{
m_data.resize(message.length());
m_crc = message.crc();
return Collecting;
}
return ErrorInvalidMessage;
}
}

Datei anzeigen

@ -0,0 +1,78 @@
/**
* server/src/hardware/protocol/marklincan/configdatastreamcollector.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_CONFIGDATASTREAMCOLLECTOR_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_CONFIGDATASTREAMCOLLECTOR_HPP
#include <cstddef>
#include <vector>
#include "message/configdata.hpp"
namespace MarklinCAN {
class ConfigDataStreamCollector
{
private:
uint16_t m_crc = 0;
std::vector<std::byte> m_data;
size_t m_offset = 0;
public:
enum Status
{
Collecting,
Complete,
ErrorInvalidMessage,
ErrorToMuchData,
ErrorInvalidCRC,
};
const std::string name;
ConfigDataStreamCollector(std::string name_);
const std::byte* data() const
{
return m_data.data();
}
size_t dataSize() const
{
return m_data.size();
}
const std::vector<std::byte>& bytes() const
{
return m_data;
}
Status process(const ConfigDataStream& message);
std::vector<std::byte>&& releaseData()
{
return std::move(m_data);
}
};
}
#endif

Datei anzeigen

@ -22,6 +22,8 @@
#include "kernel.hpp"
#include "messages.hpp"
#include "message/configdata.hpp"
#include "locomotivelist.hpp"
#include "uid.hpp"
#include "../dcc/dcc.hpp"
#include "../../decoder/decoder.hpp"
@ -31,8 +33,11 @@
#include "../../output/outputcontroller.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../log/log.hpp"
#include "../../../traintastic/traintastic.hpp"
#include "../../../utils/inrange.hpp"
#include "../../../utils/setthreadname.hpp"
#include "../../../utils/writefile.hpp"
#include "../../../utils/zlib.hpp"
namespace MarklinCAN {
@ -57,6 +62,7 @@ static std::tuple<bool, DecoderProtocol, uint16_t> uidToProtocolAddress(uint32_t
Kernel::Kernel(const Config& config, bool simulation)
: m_ioContext{1}
, m_simulation{simulation}
, m_debugDir{Traintastic::instance->debugDir()}
, m_config{config}
#ifndef NDEBUG
, m_started{false}
@ -94,6 +100,13 @@ void Kernel::setOnError(std::function<void()> callback)
m_onError = std::move(callback);
}
void Kernel::setOnLocomotiveListChanged(std::function<void(const std::shared_ptr<LocomotiveList>&)> callback)
{
assert(isEventLoopThread());
assert(!m_started);
m_onLocomotiveListChanged = std::move(callback);
}
void Kernel::setDecoderController(DecoderController* decoderController)
{
assert(isEventLoopThread());
@ -142,6 +155,8 @@ void Kernel::start()
send(AccessorySwitchTime(m_config.defaultSwitchTime / 10));
send(ConfigData("loks"));
if(m_onStarted)
EventLoop::call(
[this]()
@ -396,10 +411,33 @@ void Kernel::receive(const Message& message)
case Command::BootloaderCAN:
case Command::BootloaderTrack:
case Command::StatusDataConfig:
case Command::ConfigData:
case Command::ConfigDataStream:
// not (yet) implemented
break;
case Command::ConfigData:
if(message.isResponse() && message.dlc == 8)
{
m_configDataStreamCollector = std::make_unique<ConfigDataStreamCollector>(std::string{static_cast<const ConfigData&>(message).name()});
}
break;
case Command::ConfigDataStream:
if(m_configDataStreamCollector) /*[[likely]]*/
{
const auto status = m_configDataStreamCollector->process(static_cast<const ConfigDataStream&>(message));
if(status != ConfigDataStreamCollector::Collecting)
{
if(status == ConfigDataStreamCollector::Complete)
{
receiveConfigData(std::move(m_configDataStreamCollector));
}
else // error
{
m_configDataStreamCollector.reset();
}
}
}
break;
}
}
@ -573,4 +611,35 @@ void Kernel::postSend(const Message& message)
});
}
void Kernel::receiveConfigData(std::unique_ptr<ConfigDataStreamCollector> configData)
{
const auto basename = m_debugDir / m_logId / "configstream" / configData->name;
if(m_config.debugConfigStream)
{
writeFile(std::filesystem::path(basename).concat(".bin"), configData->bytes());
}
if(configData->name == "loks")
{
const size_t uncompressedSize = be_to_host(*reinterpret_cast<const uint32_t*>(configData->data()));
std::string locList;
if(ZLib::Uncompress::toString(configData->data() + sizeof(uint32_t), configData->dataSize() - sizeof(uint32_t), uncompressedSize, locList))
{
if(m_config.debugConfigStream)
{
writeFile(std::filesystem::path(basename).concat(".txt"), locList);
}
if(m_onLocomotiveListChanged)
{
EventLoop::call(
[this, list=std::make_shared<LocomotiveList>(locList)]()
{
m_onLocomotiveListChanged(list);
});
}
}
}
}
}

Datei anzeigen

@ -26,10 +26,12 @@
#include <memory>
#include <array>
#include <thread>
#include <filesystem>
#include <boost/asio/io_context.hpp>
#include <traintastic/enum/tristate.hpp>
#include "config.hpp"
#include "iohandler/iohandler.hpp"
#include "configdatastreamcollector.hpp"
class Decoder;
enum class DecoderChangeFlags;
@ -40,6 +42,7 @@ class OutputController;
namespace MarklinCAN {
struct Message;
class LocomotiveList;
class Kernel
{
@ -81,6 +84,7 @@ class Kernel
std::string m_logId;
std::function<void()> m_onStarted;
std::function<void()> m_onError;
std::function<void(const std::shared_ptr<LocomotiveList>&)> m_onLocomotiveListChanged;
DecoderController* m_decoderController = nullptr;
@ -92,6 +96,10 @@ class Kernel
std::array<TriState, outputDCCAddressMax - outputDCCAddressMin + 1> m_outputValuesDCC;
std::array<TriState, outputSX1AddressMax - outputSX1AddressMin + 1> m_outputValuesSX1;
std::unique_ptr<ConfigDataStreamCollector> m_configDataStreamCollector;
const std::filesystem::path m_debugDir;
Config m_config;
#ifndef NDEBUG
bool m_started;
@ -104,6 +112,8 @@ class Kernel
void send(const Message& message);
void postSend(const Message& message);
void receiveConfigData(std::unique_ptr<ConfigDataStreamCollector> configData);
public:
Kernel(const Kernel&) = delete;
Kernel& operator =(const Kernel&) = delete;
@ -192,6 +202,11 @@ class Kernel
*/
void setOnError(std::function<void()> callback);
/**
*
*/
void setOnLocomotiveListChanged(std::function<void(const std::shared_ptr<LocomotiveList>&)> callback);
/**
* \brief Set the decoder controller
* \param[in] decoderController The decoder controller

Datei anzeigen

@ -0,0 +1,146 @@
/**
* server/src/hardware/protocol/marklincan/locomotivelist.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 "locomotivelist.hpp"
#include <traintastic/enum/decoderprotocol.hpp>
#include "../dcc/dcc.hpp"
#include "../../../utils/fromchars.hpp"
#include "../../../utils/startswith.hpp"
namespace MarklinCAN {
static bool readLine(std::string_view& src, std::string_view& line)
{
const size_t n = src.find('\n');
if(n == std::string_view::npos)
return false;
line = src.substr(0, n);
src = src.substr(n + 1);
return true;
}
LocomotiveList::LocomotiveList(std::string_view list)
{
std::string_view line;
if(!readLine(list, line) || line != "[lokomotive]")
return;
while(readLine(list, line))
{
if(line == "lokomotive")
{
Locomotive locomotive;
while(readLine(list, line))
{
if(startsWith(line, " ."))
{
line = line.substr(2);
if(startsWith(line, "name="))
{
locomotive.name = line.substr(5);
}
else if(startsWith(line, "adresse=0x"))
{
fromChars(line.substr(10), locomotive.address, 16);
}
else if(startsWith(line, "typ="))
{
std::string_view typ = line.substr(4);
if(typ == "mfx")
{
locomotive.protocol = DecoderProtocol::MFX;
}
else if(typ == "dcc")
{
locomotive.protocol = DecoderProtocol::DCCShort; // or DCCLong (handled later)
}
else if(typ == "mm2_dil8" || typ == "mm2_prg")
{
locomotive.protocol = DecoderProtocol::Motorola;
}
}
else if(startsWith(line, "sid=0x"))
{
fromChars(line.substr(6), locomotive.sid, 16);
}
else if(startsWith(line, "mfxuid=0x"))
{
fromChars(line.substr(9), locomotive.mfxUID, 16);
}
else if(line == "funktionen")
{
Function function;
function.nr = 0xFF;
while(readLine(list, line))
{
if(startsWith(line, " .."))
{
line = line.substr(3);
if(startsWith(line, "nr="))
{
fromChars(line.substr(3), function.nr);
}
else if(startsWith(line, "typ="))
{
uint8_t typ;
if(fromChars(line.substr(4), typ).ec == std::errc())
{
(void)typ; //! \todo convert value to type/function (if constant)
}
}
}
else
{
list = {line.data(), line.size() + 1 + list.size()}; // restore list for next readLine
break;
}
}
if(function.nr != 0xFF)
{
locomotive.functions.push_back(function);
}
}
}
else
{
list = {line.data(), line.size() + 1 + list.size()}; // restore list for next readLine
break;
}
}
if(locomotive.protocol == DecoderProtocol::DCCShort && DCC::isLongAddress(locomotive.address))
{
locomotive.protocol = DecoderProtocol::DCCLong;
}
m_locomotives.emplace_back(std::move(locomotive));
}
}
}
}

Datei anzeigen

@ -0,0 +1,64 @@
/**
* server/src/hardware/protocol/marklincan/locomotivelist.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_LOCOMOTIVELIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_LOCOMOTIVELIST_HPP
#include <string_view>
#include <vector>
#include <memory>
#include <traintastic/enum/decoderprotocol.hpp>
#include <traintastic/enum/decoderfunctiontype.hpp>
#include <traintastic/enum/decoderfunctionfunction.hpp>
namespace MarklinCAN {
class LocomotiveList
{
public:
struct Function
{
uint8_t nr;
DecoderFunctionType type = DecoderFunctionType::OnOff;
DecoderFunctionFunction function = DecoderFunctionFunction::Generic;
};
struct Locomotive
{
std::string name;
uint16_t address = 0;
DecoderProtocol protocol = DecoderProtocol::None;
uint16_t sid = 0;
uint32_t mfxUID = 0;
std::vector<Function> functions;
};
private:
std::vector<Locomotive> m_locomotives;
public:
LocomotiveList(std::string_view list = {});
};
}
#endif

Datei anzeigen

@ -0,0 +1,74 @@
/**
* server/src/hardware/protocol/marklincan/message/configdata.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_MESSAGE_CONFIGDATA_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGE_CONFIGDATA_HPP
#include "../messages.hpp"
namespace MarklinCAN {
struct ConfigData : Message
{
ConfigData(std::string_view name_)
: Message(Command::ConfigData, false)
{
assert(name_.size() <= 8);
dlc = 8;
std::memcpy(data, name_.data(), std::min<size_t>(name_.size(), 8));
}
std::string_view name() const
{
const char* str = reinterpret_cast<const char*>(data);
return {str, strnlen(str, dlc)};
}
};
struct ConfigDataStream : Message
{
bool isStart() const
{
return dlc == 6 || dlc == 7;
}
uint32_t length() const
{
assert(isStart());
return be_to_host(*reinterpret_cast<const uint32_t*>(data));
}
uint16_t crc() const
{
assert(isStart());
return be_to_host(*reinterpret_cast<const uint16_t*>(data + sizeof(uint32_t)));
}
bool isData() const
{
return dlc == 8;
}
};
}
#endif

Datei anzeigen

@ -30,6 +30,7 @@ Settings::Settings(Object& _parent, std::string_view parentPropertyName)
: SubObject(_parent, parentPropertyName)
, defaultSwitchTime{this, "default_switch_time", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugConfigStream{this, "debug_config_stream", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
{
Attributes::addMinMax<uint32_t>(defaultSwitchTime, 0, 163'000);
//Attributes::addStep(defaultSwitchTime, 10);
@ -37,6 +38,8 @@ Settings::Settings(Object& _parent, std::string_view parentPropertyName)
Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX);
m_interfaceItems.add(debugLogRXTX);
m_interfaceItems.add(debugConfigStream);
}
Config Settings::config() const
@ -45,6 +48,7 @@ Config Settings::config() const
config.defaultSwitchTime = defaultSwitchTime;
config.debugLogRXTX = debugLogRXTX;
config.debugConfigStream = debugConfigStream;
return config;
}

Datei anzeigen

@ -36,6 +36,7 @@ class Settings final : public SubObject
Property<uint32_t> defaultSwitchTime;
Property<bool> debugLogRXTX;
Property<bool> debugConfigStream;
Settings(Object& _parent, std::string_view parentPropertyName);

Datei anzeigen

@ -0,0 +1,47 @@
/**
* server/src/utils/writefile.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 "writefile.hpp"
#include <fstream>
bool writeFile(const std::filesystem::path& filename, const void* data, size_t dataSize)
{
// try create directory if it doesn't exist
const auto path = filename.parent_path();
if(!std::filesystem::is_directory(path))
{
std::error_code ec;
std::filesystem::create_directories(path, ec);
if(ec)
return false;
}
std::ofstream file;
file.open(filename, std::ios::binary | std::ios::out | std::ios::trunc);
if(!file.is_open())
return false;
file.write(reinterpret_cast<const char*>(data), dataSize);
return true;
}

Datei anzeigen

@ -0,0 +1,43 @@
/**
* server/src/utils/writefile.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_UTILS_WRITEFILE_HPP
#define TRAINTASTIC_SERVER_UTILS_WRITEFILE_HPP
#include <cstddef>
#include <vector>
#include <filesystem>
bool writeFile(const std::filesystem::path& filename, const void* data, size_t dataSize);
inline bool writeFile(const std::filesystem::path& filename, std::string_view data)
{
return writeFile(filename, data.data(), data.size());
}
template<typename T>
inline bool writeFile(const std::filesystem::path& filename, const std::vector<T>& data)
{
return writeFile(filename, data.data(), data.size() * sizeof(T));
}
#endif