[booster] Added (smart) booster status reading support, see #212

Dieser Commit ist enthalten in:
Reinder Feenstra 2025-12-31 00:33:50 +01:00
Ursprung f8b28fffc3
Commit 0b4ac00777
34 geänderte Dateien mit 1828 neuen und 28 gelöschten Zeilen

Datei anzeigen

@ -408,6 +408,11 @@ MainWindow::MainWindow(QWidget* parent) :
menu->addAction(Locale::tr("world:inputs") + "...", [this](){ showObject("world.inputs", Locale::tr("world:inputs")); });
menu->addAction(Locale::tr("world:outputs") + "...", [this](){ showObject("world.outputs", Locale::tr("world:outputs")); });
menu->addAction(Locale::tr("hardware:identifications") + "...", [this](){ showObject("world.identifications", Locale::tr("hardware:identifications")); });
menu->addAction(Locale::tr("hardware:boosters").append("..."),
[this]()
{
showObject("world.boosters", Locale::tr("hardware:boosters"));
});
boardsAction = m_menuObjects->addAction(Theme::getIcon("board"), Locale::tr("world:boards") + "...", [this](){ showObject("world.boards", Locale::tr("world:boards")); });
m_menuObjects->addAction(
Theme::getIcon("zone"),

Datei anzeigen

@ -107,6 +107,10 @@ QWidget* createWidget(const ObjectPtr& object, QWidget* parent)
{
return new TileWidget(object, parent);
}
else if(object->classId() == "booster")
{
return new TileWidget(object, parent);
}
else
return new ObjectEditWidget(object, parent);
}

Datei anzeigen

@ -83,6 +83,9 @@ file(GLOB SOURCES
"src/core/*.hpp"
"src/core/*.cpp"
"src/enum/*.hpp"
"src/hardware/booster/*.cpp"
"src/hardware/booster/drivers/*.cpp"
"src/hardware/booster/list/*.cpp"
"src/hardware/decoder/*.hpp"
"src/hardware/decoder/*.cpp"
"src/hardware/decoder/list/*.hpp"
@ -133,6 +136,7 @@ file(GLOB SOURCES
"src/hardware/protocol/loconet/message/uhlenbrock/*.cpp"
"src/hardware/protocol/loconet/iohandler/*.hpp"
"src/hardware/protocol/loconet/iohandler/*.cpp"
"src/hardware/protocol/loconet/lncv/*.cpp"
"src/hardware/protocol/marklincan/*.hpp"
"src/hardware/protocol/marklincan/*.cpp"
"src/hardware/protocol/marklincan/iohandler/*.hpp"

Datei anzeigen

@ -0,0 +1,176 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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 "booster.hpp"
#include "drivers/boosterdriver.hpp"
#include "drivers/boosterdrivers.hpp"
#include "list/boosterlist.hpp"
#include "list/boosterlisttablemodel.hpp"
#include "../../core/attributes.hpp"
#include "../../core/objectproperty.tpp"
#include "../../utils/category.hpp"
#include "../../utils/contains.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/unit.hpp"
#include "../../world/world.hpp"
CREATE_IMPL(Booster)
Booster::Booster(World& world, std::string_view _id)
: IdObject(world, _id)
, name{this, "name", std::string(_id), PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}
, type{this, "type", "", PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, nullptr,
[this](std::string& value) -> bool
{
if(!contains<std::string_view>(BoosterDrivers::types(), value))
{
return false;
}
if(auto drv = BoosterDrivers::create(value, *this))
{
setDriver(std::move(drv));
return true;
}
return false;
}}
, driver{this, "driver", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, load_{this, "load", noValue, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, temperature{this, "temperature", noValue, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, current{this, "current", noValue, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, voltage{this, "voltage", noValue, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, inputVoltage{this, "inputVoltage", noValue, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
{
const bool editable = contains(m_world.state, WorldState::Edit);
Attributes::addDisplayName(name, DisplayName::Object::name);
Attributes::addEnabled(name, editable);
m_interfaceItems.add(name);
Attributes::addCustom(type, false);
Attributes::addEnabled(type, editable);
Attributes::addValues(type, BoosterDrivers::types());
Attributes::addAliases(type, BoosterDrivers::types(), BoosterDrivers::names());
m_interfaceItems.add(type);
Attributes::addCategory(load_, Category::status);
Attributes::addUnit(load_, Unit::percent);
Attributes::addVisible(load_, false);
m_interfaceItems.add(load_);
Attributes::addCategory(temperature, Category::status);
Attributes::addUnit(temperature, Unit::degreeCelcius);
Attributes::addVisible(temperature, false);
m_interfaceItems.add(temperature);
Attributes::addCategory(current, Category::status);
Attributes::addUnit(current, Unit::ampere);
Attributes::addVisible(current, false);
m_interfaceItems.add(current);
Attributes::addCategory(voltage, Category::status);
Attributes::addUnit(voltage, Unit::volt);
Attributes::addVisible(voltage, false);
m_interfaceItems.add(voltage);
Attributes::addCategory(inputVoltage, Category::status);
Attributes::addUnit(inputVoltage, Unit::volt);
Attributes::addVisible(inputVoltage, false);
m_interfaceItems.add(inputVoltage);
Attributes::addCategory(driver, Category::driver);
Attributes::addDisplayName(driver, {});
m_interfaceItems.add(driver);
setDriver(BoosterDrivers::create(BoosterDrivers::types().front(), *this));
}
void Booster::addToWorld()
{
IdObject::addToWorld();
m_world.boosters->addObject(shared_ptr<Booster>());
}
void Booster::destroying()
{
setDriver(nullptr);
m_world.boosters->removeObject(shared_ptr<Booster>());
IdObject::destroying();
}
void Booster::load(WorldLoader& loader, const nlohmann::json& data)
{
if(auto drv = BoosterDrivers::create(data.value<std::string_view>("type", {}), *this)) [[likely]]
{
setDriver(std::move(drv));
IdObject::load(loader, data);
}
else // unknown driver (this can only happen with corrupt/incorrect files)
{
auto dataCopy = data;
dataCopy.erase("driver"); // clear driver data, so defaults are used
IdObject::load(loader, dataCopy);
}
}
void Booster::worldEvent(WorldState state, WorldEvent event)
{
IdObject::worldEvent(state, event);
switch(event)
{
case WorldEvent::EditEnabled:
case WorldEvent::EditDisabled:
Attributes::setEnabled({name, type}, contains(state, WorldState::Edit));
break;
default:
break;
};
driver->worldEvent(state, event);
}
void Booster::setDriver(std::shared_ptr<BoosterDriver> drv)
{
if(driver)
{
driver->destroy();
}
// Reset all status values:
load_.setValueInternal(noValue);
temperature.setValueInternal(noValue);
current.setValueInternal(noValue);
voltage.setValueInternal(noValue);
inputVoltage.setValueInternal(noValue);
// Show/hide supported values:
{
using enum BoosterDriver::SupportedStatusValues;
const auto mask = drv->supportedStatusValues();
Attributes::setVisible(load_, contains(mask, Load));
Attributes::setVisible(temperature, contains(mask, Temperature));
Attributes::setVisible(current, contains(mask, Current));
Attributes::setVisible(voltage, contains(mask, Voltage));
Attributes::setVisible(inputVoltage, contains(mask, InputVoltage));
}
Attributes::setDisplayName(driver, drv->getName());
driver.setValueInternal(std::move(drv));
}

Datei anzeigen

@ -0,0 +1,61 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_BOOSTER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_BOOSTER_HPP
#include "../../core/idobject.hpp"
#include "../../core/property.hpp"
#include "../../core/objectproperty.hpp"
class BoosterDriver;
class Booster : public IdObject
{
CLASS_ID("booster")
DEFAULT_ID("booster")
CREATE_DEF(Booster)
public:
static constexpr auto noValue = std::numeric_limits<float>::quiet_NaN();
Property<std::string> name;
Property<std::string> type;
ObjectProperty<BoosterDriver> driver;
Property<float> load_; // load is already a function
Property<float> temperature;
Property<float> current;
Property<float> voltage;
Property<float> inputVoltage;
Booster(World& world, std::string_view _id);
protected:
void addToWorld() override;
void destroying() override;
void load(WorldLoader& loader, const nlohmann::json& data) override;
void worldEvent(WorldState state, WorldEvent event) override;
private:
void setDriver(std::shared_ptr<BoosterDriver> drv);
};
#endif

Datei anzeigen

@ -0,0 +1,84 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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 "boosterdriver.hpp"
#include "../booster.hpp"
using namespace std::string_view_literals;
BoosterDriver::BoosterDriver(Booster& booster)
: SubObject(booster, "driver"sv)
{
}
const std::string& BoosterDriver::boosterName() const
{
return booster().name.value();
}
void BoosterDriver::invalidateAll()
{
booster().load_.setValueInternal(Booster::noValue);
booster().temperature.setValueInternal(Booster::noValue);
booster().current.setValueInternal(Booster::noValue);
booster().voltage.setValueInternal(Booster::noValue);
booster().inputVoltage.setValueInternal(Booster::noValue);
}
void BoosterDriver::reportLoad(float value)
{
assert(contains(supportedStatusValues(), SupportedStatusValues::Load));
assert(value >= 0 || std::isnan(value));
booster().load_.setValueInternal(value);
}
void BoosterDriver::reportTemperature(float value)
{
assert(contains(supportedStatusValues(), SupportedStatusValues::Temperature));
assert(std::isfinite(value) || std::isnan(value));
booster().temperature.setValueInternal(value);
}
void BoosterDriver::reportCurrent(float value)
{
assert(contains(supportedStatusValues(), SupportedStatusValues::Current));
assert(value >= 0 || std::isnan(value));
booster().current.setValueInternal(value);
}
void BoosterDriver::reportVoltage(float value)
{
assert(contains(supportedStatusValues(), SupportedStatusValues::Voltage));
assert(value >= 0 || std::isnan(value));
booster().voltage.setValueInternal(value);
}
void BoosterDriver::reportInputVoltage(float value)
{
assert(contains(supportedStatusValues(), SupportedStatusValues::InputVoltage));
assert(value >= 0 || std::isnan(value));
booster().inputVoltage.setValueInternal(value);
}
Booster& BoosterDriver::booster() const
{
return static_cast<Booster&>(parent());
}

Datei anzeigen

@ -0,0 +1,106 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_DRIVERS_BOOSTERDRIVER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_BOOSTERDRIVER_HPP
#include "../../../core/subobject.hpp"
#define BOOSTER_DRIVER_CREATE_DEF \
public: \
static std::shared_ptr<BoosterDriver> create(Booster& booster)
#define BOOSTER_DRIVER_CREATE_IMPL(T) \
std::shared_ptr<BoosterDriver> T::create(Booster& booster) \
{ \
return std::make_shared<T>(booster); \
}
#define BOOSTER_DRIVER_CREATE(T) \
public: \
static std::shared_ptr<BoosterDriver> create(Booster& booster) \
{ \
return std::make_shared<T>(booster); \
}
#define BOOSTER_DRIVER_NAME(Name) \
public: \
static constexpr std::string_view name = Name; \
std::string_view getName() const override { return name; }
class Booster;
class BoosterDriver : public SubObject
{
public:
enum class SupportedStatusValues
{
Load = 1 << 0,
Temperature = 1 << 1,
Current = 1 << 2,
Voltage = 1 << 3,
InputVoltage = 1 << 4,
};
virtual std::string_view getName() const = 0;
virtual SupportedStatusValues supportedStatusValues() const = 0;
virtual void worldEvent(WorldState /*state*/, WorldEvent /*event*/)
{
}
protected:
static constexpr auto noValue = std::numeric_limits<float>::quiet_NaN();
BoosterDriver(Booster& booster);
const std::string& boosterName() const;
void invalidateAll();
void reportLoad(float value = noValue);
void reportTemperature(float value = noValue);
void reportCurrent(float value = noValue);
void reportVoltage(float value = noValue);
void reportInputVoltage(float value = noValue);
private:
Booster& booster() const;
};
constexpr BoosterDriver::SupportedStatusValues operator| (BoosterDriver::SupportedStatusValues lhs, BoosterDriver::SupportedStatusValues rhs)
{
using UT = std::underlying_type_t<BoosterDriver::SupportedStatusValues>;
return static_cast<BoosterDriver::SupportedStatusValues>(static_cast<UT>(lhs) | static_cast<UT>(rhs));
}
constexpr void operator|= (BoosterDriver::SupportedStatusValues& lhs, BoosterDriver::SupportedStatusValues rhs)
{
lhs = lhs | rhs;
}
constexpr bool contains(BoosterDriver::SupportedStatusValues value, BoosterDriver::SupportedStatusValues mask)
{
using UT = std::underlying_type_t<BoosterDriver::SupportedStatusValues>;
return (static_cast<UT>(value) & static_cast<UT>(mask)) == static_cast<UT>(mask);
}
#endif

Datei anzeigen

@ -0,0 +1,58 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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 "boosterdrivers.hpp"
#include "../../../utils/stripprefix.hpp"
#include "dr5033boosterdriver.hpp"
#include "power4boosterdriver.hpp"
#define BOOSTER_DRIVERS \
BOOSTER_DRIVER(DR5033BoosterDriver) \
BOOSTER_DRIVER(Power4BoosterDriver)
std::span<const std::string_view> BoosterDrivers::types()
{
static constexpr auto values = std::array{
#define BOOSTER_DRIVER(T) stripPrefix(T::classId, classIdPrefix),
BOOSTER_DRIVERS
#undef BOOSTER_DRIVER
};
return values;
}
std::span<const std::string_view> BoosterDrivers::names()
{
static constexpr auto values = std::array{
#define BOOSTER_DRIVER(T) T::name,
BOOSTER_DRIVERS
#undef BOOSTER_DRIVER
};
return values;
}
std::shared_ptr<BoosterDriver> BoosterDrivers::create(std::string_view typeId, Booster& booster)
{
#define BOOSTER_DRIVER(T) if(typeId == stripPrefix(T::classId, classIdPrefix)) { return std::make_shared<T>(booster); }
BOOSTER_DRIVERS
#undef BOOSTER_DRIVER
return {};
}

Datei anzeigen

@ -0,0 +1,41 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_DRIVERS_BOOSTERDRIVERS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_BOOSTERDRIVERS_HPP
#include <string_view>
#include <span>
#include <memory>
class Booster;
class BoosterDriver;
struct BoosterDrivers
{
static constexpr std::string_view classIdPrefix = "booster_driver.";
static std::span<const std::string_view> types();
static std::span<const std::string_view> names();
static std::shared_ptr<BoosterDriver> create(std::string_view typeId, Booster& booster);
};
#endif

Datei anzeigen

@ -0,0 +1,47 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_DRIVERS_DR5033BOOSTERDRIVER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_DR5033BOOSTERDRIVER_HPP
#include "loconetlncvboosterdriver.hpp"
class DR5033BoosterDriver final : public LocoNetLNCVBoosterDriver
{
CLASS_ID("booster_driver.dr5033");
BOOSTER_DRIVER_CREATE(DR5033BoosterDriver);
BOOSTER_DRIVER_NAME("Digikeijs DR5033");
public:
DR5033BoosterDriver(Booster& booster)
: LocoNetLNCVBoosterDriver(booster, 5033)
{
}
SupportedStatusValues supportedStatusValues() const final
{
using enum SupportedStatusValues;
return (Load | Temperature);
}
};
#endif

Datei anzeigen

@ -0,0 +1,111 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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 "loconetboosterdriver.hpp"
#include "../../interface/loconetinterface.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../utils/displayname.hpp"
#include "../../../world/getworld.hpp"
#include "../../../world/world.hpp"
LocoNetBoosterDriver::LocoNetBoosterDriver(Booster& booster)
: BoosterDriver(booster)
, interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript, nullptr,
[this](std::shared_ptr<LocoNetInterface> value)
{
if(interface)
{
m_interfacePropertyChanged.disconnect();
}
if(value)
{
m_interfacePropertyChanged = value->propertyChanged.connect(std::bind_front(&LocoNetBoosterDriver::interfacePropertyChanged, this));
}
return true;
}}
{
Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
Attributes::addEnabled(interface, false);
Attributes::addObjectList(interface, getWorld(*this).loconetInterfaces);
m_interfaceItems.add(interface);
}
void LocoNetBoosterDriver::destroying()
{
BoosterDriver::destroying();
m_interfacePropertyChanged.disconnect();
}
void LocoNetBoosterDriver::loaded()
{
BoosterDriver::loaded();
if(interface)
{
m_interfacePropertyChanged = interface->propertyChanged.connect(std::bind_front(&LocoNetBoosterDriver::interfacePropertyChanged, this));
}
}
void LocoNetBoosterDriver::worldEvent(WorldState state, WorldEvent event)
{
BoosterDriver::worldEvent(state, event);
switch(event)
{
case WorldEvent::EditDisabled:
case WorldEvent::EditEnabled:
updateEnabled();
break;
default:
break;
}
}
void LocoNetBoosterDriver::interfaceOnlineChanged(bool value)
{
if(!value) // offline
{
invalidateAll();
}
updateEnabled();
}
void LocoNetBoosterDriver::interfacePropertyChanged(BaseProperty& property)
{
if(property.name() == "online")
{
interfaceOnlineChanged(static_cast<Property<bool>&>(property).value());
}
}
void LocoNetBoosterDriver::updateEnabled()
{
const bool editable = contains(getWorld(*this).state, WorldState::Edit);
const bool online = interface && interface->online;
updateEnabled(editable, online);
}
void LocoNetBoosterDriver::updateEnabled(bool editable, bool online)
{
Attributes::setEnabled(interface, editable && !online);
}

Datei anzeigen

@ -0,0 +1,54 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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_BOOSTER_DRIVERS_LOCONETBOOSTERDRIVER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_LOCONETBOOSTERDRIVER_HPP
#include "boosterdriver.hpp"
#include "../../../core/objectproperty.hpp"
class LocoNetInterface;
class LocoNetBoosterDriver : public BoosterDriver
{
public:
ObjectProperty<LocoNetInterface> interface;
void worldEvent(WorldState state, WorldEvent event) override;
protected:
LocoNetBoosterDriver(Booster& booster);
void destroying() override;
void loaded() override;
virtual void interfaceOnlineChanged(bool value);
void updateEnabled();
virtual void updateEnabled(bool editable, bool online);
private:
boost::signals2::connection m_interfacePropertyChanged;
void interfacePropertyChanged(BaseProperty& property);
};
#endif

Datei anzeigen

@ -0,0 +1,188 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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 "loconetlncvboosterdriver.hpp"
#include "../../interface/loconetinterface.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../log/log.hpp"
#include "../../../utils/displayname.hpp"
#include "../../../utils/unit.hpp"
namespace
{
std::chrono::milliseconds startDelay(uint16_t address, uint16_t period)
{
constexpr uint32_t prime = 2654435761u; // Knuth
return std::chrono::milliseconds((address * prime) % (1000 * period));
}
}
LocoNetLNCVBoosterDriver::LocoNetLNCVBoosterDriver(Booster& booster, uint16_t moduleId)
: LocoNetBoosterDriver(booster)
, address{this, "address", addressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}
, pollInterval{this, "poll_interval", pollIntervalDefault, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript}
, m_moduleId{moduleId}
, m_pollTimer{EventLoop::ioContext}
{
Attributes::addDisplayName(address, DisplayName::Hardware::address);
Attributes::addEnabled(address, false);
Attributes::addMinMax(address, addressMin, addressMax);
m_interfaceItems.add(address);
Attributes::addDisplayName(pollInterval, "booster_driver:poll_interval");
Attributes::addEnabled(pollInterval, false);
Attributes::addMinMax(pollInterval, pollIntervalMin, pollIntervalMax);
Attributes::addUnit(pollInterval, Unit::seconds);
m_interfaceItems.add(pollInterval);
LocoNetBoosterDriver::updateEnabled();
}
void LocoNetLNCVBoosterDriver::destroying()
{
LocoNetBoosterDriver::destroying();
m_pollTimer.cancel();
}
void LocoNetLNCVBoosterDriver::interfaceOnlineChanged(bool value)
{
LocoNetBoosterDriver::interfaceOnlineChanged(value);
if(value) // online
{
m_softwareVersion.retries = 0;
m_temperature.retries = 0;
m_load.retries = 0;
// start poll timer with an address-based phase offset to spread bus load:
m_pollTimer.expires_after(startDelay(address, pollInterval));
m_pollTimer.async_wait(std::bind_front(&LocoNetLNCVBoosterDriver::poll, weak_ptr<LocoNetLNCVBoosterDriver>()));
}
else // offline
{
m_pollTimer.cancel();
}
}
void LocoNetLNCVBoosterDriver::updateEnabled(bool editable, bool online)
{
LocoNetBoosterDriver::updateEnabled(editable, online);
Attributes::setEnabled(address, editable && !online);
Attributes::setEnabled(pollInterval, editable);
}
void LocoNetLNCVBoosterDriver::poll(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, std::error_code ec)
{
if(ec)
{
return;
}
if(auto self = weak.lock())
{
if(self->m_softwareVersion.pollEnabled())
{
self->interface->readLNCV(self->m_moduleId, self->address, self->m_softwareVersion.lncv, std::bind_front(&LocoNetLNCVBoosterDriver::softwareVersionResponse, weak));
}
if(self->m_temperature.pollEnabled())
{
self->interface->readLNCV(self->m_moduleId, self->address, self->m_temperature.lncv, std::bind_front(&LocoNetLNCVBoosterDriver::temperatureResponse, weak));
}
if(self->m_load.pollEnabled())
{
self->interface->readLNCV(self->m_moduleId, self->address, self->m_load.lncv, std::bind_front(&LocoNetLNCVBoosterDriver::loadResponse, weak));
}
if(self->m_temperature.pollEnabled() || self->m_load.pollEnabled())
{
self->m_pollTimer.expires_at(self->m_pollTimer.expiry() + std::chrono::seconds(self->pollInterval.value()));
self->m_pollTimer.async_wait(std::bind_front(&LocoNetLNCVBoosterDriver::poll, weak));
}
}
}
void LocoNetLNCVBoosterDriver::softwareVersionResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec)
{
if(auto self = weak.lock())
{
if(!ec)
{
Log::log(self->parent(), LogMessage::I2006_BOOSTER_X_SOFTWARE_VERSION_X, self->boosterName(), value);
}
else
{
Log::log(self->parent(), LogMessage::E2025_READING_BOOSTER_X_SOFTWARE_VERSION_FAILED_X, self->boosterName(), ec.message());
}
self->m_softwareVersion.retries = retryLimit; // stop reading, try just once
}
}
void LocoNetLNCVBoosterDriver::temperatureResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec)
{
if(auto self = weak.lock())
{
if(!ec)
{
self->m_temperature.retries = 0;
self->reportTemperature(value);
}
else
{
self->m_temperature.retries++;
self->reportTemperature(); // invalidate value
Log::log(
self->parent(),
self->m_temperature.pollEnabled()
? LogMessage::W2026_READING_BOOSTER_X_TEMPERATURE_FAILED_X // keep trying -> warning
: LogMessage::E2026_READING_BOOSTER_X_TEMPERATURE_FAILED_X, // give up -> error
self->boosterName(),
ec.message());
}
}
}
void LocoNetLNCVBoosterDriver::loadResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec)
{
if(auto self = weak.lock())
{
if(!ec)
{
self->m_load.retries = 0;
self->reportLoad(value);
}
else
{
self->m_load.retries++;
self->reportLoad(); // invalidate value
Log::log(
self->parent(),
self->m_load.pollEnabled()
? LogMessage::W2027_READING_BOOSTER_X_LOAD_FAILED_X // keep trying -> warning
: LogMessage::E2027_READING_BOOSTER_X_LOAD_FAILED_X, // give up -> error
self->boosterName(),
ec.message());
}
}
}

Datei anzeigen

@ -0,0 +1,80 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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_BOOSTER_DRIVERS_LOCONETLNCVBOOSTERDRIVER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_LOCONETLNCVBOOSTERDRIVER_HPP
#include "loconetboosterdriver.hpp"
#include <boost/asio/steady_timer.hpp>
#include "../../../core/property.hpp"
class LocoNetLNCVBoosterDriver : public LocoNetBoosterDriver
{
public:
Property<uint16_t> address;
Property<uint16_t> pollInterval;
protected:
struct PollValue
{
uint16_t lncv;
bool enabled = true;
uint8_t retries = 0;
inline bool pollEnabled() const
{
return enabled && (retries < retryLimit);
}
};
static constexpr uint8_t retryLimit = 3;
const uint16_t m_moduleId;
PollValue m_softwareVersion{1};
PollValue m_temperature{6};
PollValue m_load{7};
LocoNetLNCVBoosterDriver(Booster& booster, uint16_t moduleId);
void destroying() override;
void interfaceOnlineChanged(bool value) final;
void updateEnabled(bool editable, bool online) final;
private:
static constexpr uint16_t addressMin = 0;
static constexpr uint16_t addressDefault = 1;
static constexpr uint16_t addressMax = 65534;
static constexpr uint16_t pollIntervalMin = 1;
static constexpr uint16_t pollIntervalDefault = 5;
static constexpr uint16_t pollIntervalMax = 30;
boost::asio::steady_timer m_pollTimer;
static void poll(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weakSelf, std::error_code ec);
static void softwareVersionResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec);
static void temperatureResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec);
static void loadResponse(const std::weak_ptr<LocoNetLNCVBoosterDriver>& weak, uint16_t value, std::error_code ec);
};
#endif

Datei anzeigen

@ -0,0 +1,46 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_DRIVERS_POWER4BOOSTERDRIVER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_DRIVERS_POWER4BOOSTERDRIVER_HPP
#include "loconetlncvboosterdriver.hpp"
class Power4BoosterDriver final : public LocoNetLNCVBoosterDriver
{
CLASS_ID("booster_driver.power4");
BOOSTER_DRIVER_CREATE(Power4BoosterDriver);
BOOSTER_DRIVER_NAME("Uhlenbrock Power 4");
public:
Power4BoosterDriver(Booster& booster)
: LocoNetLNCVBoosterDriver(booster, 6324)
{
}
SupportedStatusValues supportedStatusValues() const final
{
using enum SupportedStatusValues;
return (Load | Temperature);
}
};
#endif

Datei anzeigen

@ -0,0 +1,71 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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 "boosterlist.hpp"
#include "boosterlisttablemodel.hpp"
#include "../booster.hpp"
#include "../../../world/world.hpp"
#include "../../../world/getworld.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/method.tpp"
#include "../../../core/objectproperty.tpp"
#include "../../../utils/displayname.hpp"
BoosterList::BoosterList(Object& _parent, std::string_view parentPropertyName) :
ObjectList<Booster>(_parent, parentPropertyName),
create{*this, "create",
[this]()
{
auto& world = getWorld(parent());
return Booster::create(world, world.getUniqueId(Booster::defaultId));
}},
delete_{*this, "delete", std::bind(&BoosterList::deleteMethodHandler, this, std::placeholders::_1)}
{
const bool editable = contains(getWorld(parent()).state.value(), WorldState::Edit);
Attributes::addDisplayName(create, DisplayName::List::create);
Attributes::addEnabled(create, editable);
m_interfaceItems.add(create);
Attributes::addDisplayName(delete_, DisplayName::List::delete_);
Attributes::addEnabled(delete_, editable);
m_interfaceItems.add(delete_);
}
TableModelPtr BoosterList::getModel()
{
return std::make_shared<BoosterListTableModel>(*this);
}
void BoosterList::worldEvent(WorldState state, WorldEvent event)
{
ObjectList<Booster>::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
Attributes::setEnabled(create, editable);
Attributes::setEnabled(delete_, editable);
}
bool BoosterList::isListedProperty(std::string_view name)
{
return BoosterListTableModel::isListedProperty(name);
}

Datei anzeigen

@ -0,0 +1,47 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_LIST_BOOSTERLIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_LIST_BOOSTERLIST_HPP
#include "../../../core/objectlist.hpp"
#include "../../../core/method.hpp"
#include "../booster.hpp"
class BoosterList final : public ObjectList<Booster>
{
CLASS_ID("list.booster")
protected:
void worldEvent(WorldState state, WorldEvent event) final;
bool isListedProperty(std::string_view name) final;
public:
Method<std::shared_ptr<Booster>()> create;
Method<void(const std::shared_ptr<Booster>&)> delete_;
BoosterList(Object& _parent, std::string_view parentPropertyName);
~BoosterList() final = default;
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,75 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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 "boosterlisttablemodel.hpp"
#include "boosterlist.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../utils/displayname.hpp"
constexpr uint32_t columnId = 0;
constexpr uint32_t columnName = 1;
bool BoosterListTableModel::isListedProperty(std::string_view name)
{
return
name == "id" ||
name == "name";
}
BoosterListTableModel::BoosterListTableModel(BoosterList& list) :
ObjectListTableModel<Booster>(list)
{
setColumnHeaders({
DisplayName::Object::id,
DisplayName::Object::name
});
}
std::string BoosterListTableModel::getText(uint32_t column, uint32_t row) const
{
if(row < rowCount())
{
const Booster& booster = getItem(row);
switch(column)
{
case columnId:
return booster.id;
case columnName:
return booster.name;
default:
assert(false);
break;
}
}
return "";
}
void BoosterListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
{
if(property.name() == "id")
changed(row, columnId);
else if(property.name() == "name")
changed(row, columnName);
}

Datei anzeigen

@ -0,0 +1,47 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_BOOSTER_LIST_BOOSTERLISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_BOOSTER_LIST_BOOSTERLISTTABLEMODEL_HPP
#include "../../../core/objectlisttablemodel.hpp"
#include "boosterlist.hpp"
class BoosterList;
class BoosterListTableModel : public ObjectListTableModel<Booster>
{
friend class BoosterList;
protected:
void propertyChanged(BaseProperty& property, uint32_t row) final;
public:
CLASS_ID("table_model.list.booster")
static bool isListedProperty(std::string_view name);
BoosterListTableModel(BoosterList& boosterList);
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -38,6 +38,7 @@
#include "../protocol/loconet/iohandler/lbserveriohandler.hpp"
#include "../protocol/loconet/iohandler/z21iohandler.hpp"
#include "../../core/attributes.hpp"
#include "../../core/controllerlist.hpp"
#include "../../core/eventloop.hpp"
#include "../../core/method.tpp"
#include "../../core/objectproperty.tpp"
@ -136,6 +137,14 @@ bool LocoNetInterface::immPacket(std::span<uint8_t> dccPacket, uint8_t repeat)
return false;
}
void LocoNetInterface::readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback)
{
if(m_kernel)
{
m_kernel->readLNCV(moduleId, address, lncv, std::move(callback));
}
}
std::span<const DecoderProtocol> LocoNetInterface::decoderProtocols() const
{
static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
@ -415,6 +424,7 @@ void LocoNetInterface::addToWorld()
OutputController::addToWorld(outputListColumns);
IdentificationController::addToWorld(identificationListColumns);
LNCVProgrammingController::addToWorld();
m_world.loconetInterfaces->add(Object::shared_ptr<LocoNetInterface>());
}
void LocoNetInterface::loaded()
@ -426,6 +436,7 @@ void LocoNetInterface::loaded()
void LocoNetInterface::destroying()
{
m_world.loconetInterfaces->remove(Object::shared_ptr<LocoNetInterface>());
LNCVProgrammingController::destroying();
IdentificationController::destroying();
OutputController::destroying();

Datei anzeigen

@ -92,6 +92,8 @@ class LocoNetInterface final
//! \return \c true if send to command station, \c false otherwise.
bool immPacket(std::span<uint8_t> dccPacket, uint8_t repeat);
void readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback);
// DecoderController:
std::span<const DecoderProtocol> decoderProtocols() const final;
std::pair<uint16_t, uint16_t> decoderAddressMinMax(DecoderProtocol protocol) const final;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2024 Reinder Feenstra
* Copyright (C) 2021-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -33,6 +33,7 @@ struct Config
{
static constexpr uint16_t timeoutMin = 100; //!< Minimum timeout in milliseconds
static constexpr uint16_t timeoutMax = 10000; //!< Maximum timeout in milliseconds
static constexpr uint16_t lncvReadResponseTimeout = 100;
uint16_t echoTimeout; //!< Wait for echo timeout in milliseconds
uint16_t responseTimeout; //!< Wait for response timeout in milliseconds

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -22,6 +22,7 @@
#include "kernel.hpp"
#include "iohandler/iohandler.hpp"
#include "lncv/lncvutils.hpp"
#include "messages.hpp"
#include "../../decoder/decoder.hpp"
#include "../../decoder/decoderchangeflags.hpp"
@ -629,21 +630,49 @@ void Kernel::receive(const Message& message)
});
}
}
else if(m_lncvActive && m_onLNCVReadResponse &&
longAck.respondingOpCode() == OPC_IMM_PACKET && longAck.ack1 == 0x7F &&
Uhlenbrock::LNCVWrite::check(lastSentMessage()))
else if(longAck.respondingOpCode() == OPC_IMM_PACKET)
{
const auto& lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(lastSentMessage());
if(lncvWrite.lncv() == 0)
if(m_waitingForLNCVReadResponse)
{
m_lncvModuleAddress = lncvWrite.value();
}
EventLoop::call(
[this, lncvWrite]()
if(!m_lncvReads.empty() &&
m_lncvReads.front().moduleId == m_pendingLNCVRead.moduleId &&
m_lncvReads.front().address == m_pendingLNCVRead.address &&
m_lncvReads.front().lncv == m_pendingLNCVRead.lncv)
{
m_onLNCVReadResponse(true, lncvWrite.lncv(), lncvWrite.value());
});
m_pendingLNCVRead.reset();
auto ec = LNCV::parseLongAck(longAck.ack1);
if(!ec)
{
// 0x7F indicates a successful LONG_ACK, but for a read operation
// we expect a proper read response message instead. Receiving
// only a LONG_ACK is unexpected, so we treat it as
// unexpected response until proven otherwise.
ec = LNCV::Error::unexpectedResponse();
}
EventLoop::call(
[callback=std::move(m_lncvReads.front().callback), ec]()
{
callback(0, ec);
});
m_lncvReads.pop();
}
}
else if(m_lncvActive && m_onLNCVReadResponse &&
longAck.ack1 == 0x7F &&
Uhlenbrock::LNCVWrite::check(lastSentMessage()))
{
const auto& lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(lastSentMessage());
if(lncvWrite.lncv() == 0)
{
m_lncvModuleAddress = lncvWrite.value();
}
EventLoop::call(
[this, lncvWrite]()
{
m_onLNCVReadResponse(true, lncvWrite.lncv(), lncvWrite.value());
});
}
}
break;
}
@ -810,20 +839,38 @@ void Kernel::receive(const Message& message)
{
[[maybe_unused]] const auto& readSpecialOptionReply = static_cast<const Uhlenbrock::ReadSpecialOptionReply&>(message);
}
else if(m_onLNCVReadResponse && Uhlenbrock::LNCVReadResponse::check(message))
else if(Uhlenbrock::LNCVReadResponse::check(message))
{
const auto& lncvReadResponse = static_cast<const Uhlenbrock::LNCVReadResponse&>(message);
if(lncvReadResponse.lncv() == 0)
if(!m_lncvReads.empty() &&
m_lncvReads.front().moduleId == lncvReadResponse.moduleId() &&
m_lncvReads.front().address == m_pendingLNCVRead.address &&
m_lncvReads.front().lncv == lncvReadResponse.lncv())
{
m_lncvActive = true;
m_lncvModuleAddress = lncvReadResponse.value();
EventLoop::call(
[callback=m_lncvReads.front().callback, value=lncvReadResponse.value()]()
{
callback(value, {});
});
m_lncvReads.pop();
}
else if(m_onLNCVReadResponse)
{
if(lncvReadResponse.lncv() == 0)
{
m_lncvActive = true;
m_lncvModuleAddress = lncvReadResponse.value();
}
EventLoop::call(
[this, lncvReadResponse]()
{
m_onLNCVReadResponse(true, lncvReadResponse.lncv(), lncvReadResponse.value());
});
}
EventLoop::call(
[this, lncvReadResponse]()
{
m_onLNCVReadResponse(true, lncvReadResponse.lncv(), lncvReadResponse.value());
});
m_pendingLNCVRead.reset();
}
}
break;
@ -1012,6 +1059,18 @@ bool Kernel::immPacket(std::span<const uint8_t> dccPacket, uint8_t repeat)
return true;
}
void Kernel::readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback)
{
assert(isEventLoopThread());
m_ioContext.post(
[this, moduleId, address, lncv, callback]()
{
m_lncvReads.emplace(LNCVRead{moduleId, address, lncv, std::move(callback)});
send(Uhlenbrock::LNCVRead(moduleId, address, lncv), LocoNet::Kernel::LowPriority);
});
}
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
assert(isEventLoopThread());
@ -1439,9 +1498,18 @@ void Kernel::sendNextMessage()
m_waitingForEchoTimer.async_wait(std::bind(&Kernel::waitingForEchoTimerExpired, this, std::placeholders::_1));
m_waitingForResponse = hasResponse(message);
m_waitingForLNCVReadResponse = m_waitingForResponse && Uhlenbrock::LNCVRead::check(message);
if(m_waitingForResponse)
{
m_waitingForResponseTimer.expires_after(boost::asio::chrono::milliseconds(m_config.responseTimeout));
if(m_waitingForLNCVReadResponse)
{
const auto& lncvRead = static_cast<const Uhlenbrock::LNCVRead&>(message);
m_pendingLNCVRead.moduleId = lncvRead.moduleId();
m_pendingLNCVRead.address = lncvRead.address();
m_pendingLNCVRead.lncv = lncvRead.lncv();
}
const auto timeout = m_waitingForLNCVReadResponse ? Config::lncvReadResponseTimeout : m_config.responseTimeout;
m_waitingForResponseTimer.expires_after(boost::asio::chrono::milliseconds(timeout));
m_waitingForResponseTimer.async_wait(std::bind(&Kernel::waitingForResponseTimerExpired, this, std::placeholders::_1));
}
}
@ -1473,7 +1541,28 @@ void Kernel::waitingForResponseTimerExpired(const boost::system::error_code& ec)
if(ec)
return;
if(m_lncvActive && Uhlenbrock::LNCVStart::check(lastSentMessage()))
if(m_waitingForLNCVReadResponse)
{
if(!m_lncvReads.empty() &&
m_lncvReads.front().moduleId == m_pendingLNCVRead.moduleId &&
m_lncvReads.front().address == m_pendingLNCVRead.address &&
m_lncvReads.front().lncv == m_pendingLNCVRead.lncv)
{
m_pendingLNCVRead.reset();
EventLoop::call(
[callback=std::move(m_lncvReads.front().callback)]()
{
callback(0, LNCV::Error::noResponse());
});
m_lncvReads.pop();
}
assert(Uhlenbrock::LNCVRead::check(lastSentMessage()));
m_sendQueue[m_sentMessagePriority].pop();
m_waitingForResponse = false;
sendNextMessage();
}
else if(m_lncvActive && Uhlenbrock::LNCVStart::check(lastSentMessage()))
{
EventLoop::call(
[this, lncvStart=static_cast<const Uhlenbrock::LNCVStart&>(lastSentMessage())]()
@ -1484,6 +1573,9 @@ void Kernel::waitingForResponseTimerExpired(const boost::system::error_code& ec)
m_onLNCVReadResponse(false, lncvStart.address(), 0);
});
assert(Uhlenbrock::LNCVStart::check(lastSentMessage()));
m_sendQueue[m_sentMessagePriority].pop();
m_waitingForResponse = false;
sendNextMessage();
}
else

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
* Copyright (C) 2019-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -27,6 +27,7 @@
#include <array>
#include <unordered_map>
#include <filesystem>
#include <queue>
#include <boost/asio/steady_timer.hpp>
#include <boost/signals2/connection.hpp>
#include <span>
@ -149,6 +150,20 @@ class Kernel : public ::KernelBase
bool m_waitingForEcho;
boost::asio::steady_timer m_waitingForEchoTimer;
bool m_waitingForResponse;
bool m_waitingForLNCVReadResponse = false;
struct
{
uint16_t moduleId = 0;
uint16_t address = 0;
uint16_t lncv = 0;
void reset()
{
moduleId = 0;
address = 0;
lncv = 0;
}
} m_pendingLNCVRead;
boost::asio::steady_timer m_waitingForResponseTimer;
TriState m_globalPower;
@ -166,6 +181,14 @@ class Kernel : public ::KernelBase
uint16_t m_lncvModuleId = 0;
uint16_t m_lncvModuleAddress = 0;
OnLNCVReadResponse m_onLNCVReadResponse;
struct LNCVRead
{
uint16_t moduleId;
uint16_t address;
uint16_t lncv;
std::function<void(uint16_t, std::error_code)> callback;
};
std::queue<LNCVRead> m_lncvReads;
DecoderController* m_decoderController;
std::unordered_map<uint16_t, uint8_t> m_addressToSlot;
@ -406,6 +429,8 @@ class Kernel : public ::KernelBase
return immPacket(std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(&dccPacket), sizeof(T)), repeat);
}
void readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback);
/**
*
*

Datei anzeigen

@ -0,0 +1,100 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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 "lncverror.hpp"
namespace {
class Category final : public std::error_category
{
public:
const char* name() const noexcept final
{
return "loconet.lncv";
}
std::string message(int ev) const final
{
switch (static_cast<LocoNet::LNCV::Errc>(ev))
{
using enum LocoNet::LNCV::Errc;
case UnexpectedResponse:
return "Unexpected response to LNCV request";
case NoResponse:
return "No response to LNCV request";
case Rejected:
return "LNCV request rejected";
case NotFound:
return "LNCV not found";
case ReadOnly:
return "LNCV is read only";
default:
return "Unknown LNCV error";
}
}
std::error_condition default_error_condition(int ev) const noexcept final
{
switch (static_cast<LocoNet::LNCV::Errc>(ev))
{
using enum LocoNet::LNCV::Errc;
case NoResponse:
return std::errc::timed_out;
case Rejected:
return std::errc::operation_not_permitted;
case NotFound:
return std::errc::no_such_file_or_directory;
case ReadOnly:
return std::errc::read_only_file_system;
default:
return std::error_condition(ev, *this);
}
}
};
const Category category{};
}
namespace LocoNet::LNCV {
const std::error_category& errorCategory()
{
return category;
}
std::error_code makeErrorCode(Errc ec)
{
return {static_cast<int>(ec), errorCategory()};
}
}

Datei anzeigen

@ -0,0 +1,74 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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_LOCONET_LNCV_LNCVERROR_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LNCV_LNCVERROR_HPP
#include <system_error>
namespace LocoNet::LNCV {
enum class Errc
{
// zero means no error
UnexpectedResponse = 1,
NoResponse = 2,
Rejected = 3,
NotFound = 4,
ReadOnly = 5,
};
std::error_code makeErrorCode(Errc ec);
namespace Error
{
inline std::error_code unexpectedResponse()
{
return makeErrorCode(Errc::UnexpectedResponse);
}
inline std::error_code noResponse()
{
return makeErrorCode(Errc::NoResponse);
}
inline std::error_code rejected()
{
return makeErrorCode(Errc::Rejected);
}
inline std::error_code notFound()
{
return makeErrorCode(Errc::NotFound);
}
inline std::error_code readOnly()
{
return makeErrorCode(Errc::ReadOnly);
}
}
}
template<>
struct std::is_error_code_enum<LocoNet::LNCV::Errc> : std::true_type {};
#endif

Datei anzeigen

@ -0,0 +1,65 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025-2026 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_LOCONET_LNCV_LNCVUTILS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LNCV_LNCVUTILS_HPP
#include <cstdint>
#include "lncverror.hpp"
namespace LocoNet::LNCV {
/**
* @brief Interpret OPC_LONG_ACK status byte for LNCV operations.
*
* @note The status values used here are based on monitoring LocoNet traffic
* generated by a Uhlenbrock Intellibox performing LNCV operations against
* Uhlenbrock devices.
*
* @note LNCV was introduced by Uhlenbrock, but no public specification for
* OPC_LONG_ACK status values have been found. These mappings therefore
* reflect observed, de-facto behavior and may vary for non-Uhlenbrock
* devices.
*/
[[nodiscard]] inline std::error_code parseLongAck(uint8_t status) noexcept
{
switch(status)
{
case 0x00:
return Error::rejected();
case 0x01:
return Error::notFound();
case 0x03:
return Error::readOnly();
case 0x7F:
return {}; // no error
default:
return Error::unexpectedResponse();
}
}
}
#endif

Datei anzeigen

@ -33,12 +33,14 @@ namespace Category
constexpr std::string_view colorsAndAlignment = "category:colors_and_alignment";
constexpr std::string_view debug = "category:debug";
constexpr std::string_view developer = "category:developer";
constexpr std::string_view driver = "category:driver";
constexpr std::string_view general = "category:general";
constexpr std::string_view info = "category:info";
constexpr std::string_view input = "category:input";
constexpr std::string_view log = "category:log";
constexpr std::string_view network = "category:network";
constexpr std::string_view options = "category:options";
constexpr std::string_view status = "category:status";
constexpr std::string_view trains = "category:trains";
constexpr std::string_view zones = "category:zones";
}

37
server/src/utils/unit.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,37 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 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_UNIT_HPP
#define TRAINTASTIC_SERVER_UTILS_UNIT_HPP
#include <string_view>
struct Unit
{
static constexpr std::string_view ampere{"A"};
static constexpr std::string_view degreeCelcius{"\u00B0C"};
static constexpr std::string_view milliSeconds{"ms"};
static constexpr std::string_view percent{"%"};
static constexpr std::string_view seconds{"s"};
static constexpr std::string_view volt{"C"};
};
#endif

Datei anzeigen

@ -43,6 +43,8 @@
#include "../core/abstractvectorproperty.hpp"
#include "../core/controllerlist.hpp"
#include "../hardware/booster/booster.hpp"
#include "../hardware/booster/list/boosterlist.hpp"
#include "../hardware/input/input.hpp"
#include "../hardware/input/monitor/inputmonitor.hpp"
#include "../hardware/input/list/inputlist.hpp"
@ -123,12 +125,14 @@ void World::init(World& world)
world.outputControllers.setValueInternal(std::make_shared<ControllerList<OutputController>>(world, world.outputControllers.name()));
world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name()));
world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
world.outputs.setValueInternal(std::make_shared<OutputList>(world, world.outputs.name(), outputListColumns));
world.identifications.setValueInternal(std::make_shared<IdentificationList>(world, world.outputs.name(), identificationListColumns));
world.boosters.setValueInternal(std::make_shared<BoosterList>(world, world.boosters.name()));
world.boards.setValueInternal(std::make_shared<BoardList>(world, world.boards.name()));
world.zones.setValueInternal(std::make_shared<ZoneList>(world, world.zones.name()));
world.clock.setValueInternal(std::make_shared<Clock>(world, world.clock.name()));
@ -177,11 +181,13 @@ World::World(Private /*unused*/) :
outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
identificationControllers{this, "identification_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
lncvProgrammingControllers{this, "lncv_programming_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
loconetInterfaces{this, "loconet_interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
interfaces{this, "interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
identifications{this, "identifications", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
boosters{this, "boosters", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
zones{this, "zones", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
@ -381,6 +387,8 @@ World::World(Private /*unused*/) :
m_interfaceItems.add(identificationControllers);
Attributes::addObjectEditor(lncvProgrammingControllers, false);
m_interfaceItems.add(lncvProgrammingControllers);
Attributes::addObjectEditor(loconetInterfaces, false);
m_interfaceItems.add(loconetInterfaces);
Attributes::addObjectEditor(interfaces, false);
m_interfaceItems.add(interfaces);
@ -392,6 +400,8 @@ World::World(Private /*unused*/) :
m_interfaceItems.add(outputs);
Attributes::addObjectEditor(identifications, false);
m_interfaceItems.add(identifications);
Attributes::addObjectEditor(boosters, false);
m_interfaceItems.add(boosters);
Attributes::addObjectEditor(throttles, false);
m_interfaceItems.add(throttles);
Attributes::addObjectEditor(boards, false);

Datei anzeigen

@ -44,11 +44,13 @@ class InputController;
class OutputController;
class IdentificationController;
class LNCVProgrammingController;
class LocoNetInterface;
class InterfaceList;
class DecoderList;
class InputList;
class OutputList;
class IdentificationList;
class BoosterList;
class BoardList;
class ZoneList;
class BlockRailTileList;
@ -121,12 +123,14 @@ class World : public Object
ObjectProperty<ControllerList<OutputController>> outputControllers;
ObjectProperty<ControllerList<IdentificationController>> identificationControllers;
ObjectProperty<ControllerList<LNCVProgrammingController>> lncvProgrammingControllers;
ObjectProperty<ControllerList<LocoNetInterface>> loconetInterfaces;
ObjectProperty<InterfaceList> interfaces;
ObjectProperty<DecoderList> decoders;
ObjectProperty<InputList> inputs;
ObjectProperty<OutputList> outputs;
ObjectProperty<IdentificationList> identifications;
ObjectProperty<BoosterList> boosters;
ObjectProperty<BoardList> boards;
ObjectProperty<ZoneList> zones;
ObjectProperty<Clock> clock;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -40,6 +40,7 @@
#include "../hardware/decoder/decoder.hpp"
#include "../hardware/decoder/decoderfunction.hpp"
#include "../hardware/identification/identification.hpp"
#include "../hardware/booster/booster.hpp"
#include "../vehicle/rail/railvehicles.hpp"
#include "../vehicle/rail/freightwagon.hpp" //! \todo Remove in v0.4
#include "../train/train.hpp"
@ -364,6 +365,10 @@ void WorldLoader::createObject(ObjectData& objectData)
}
else if(classId == Identification::classId)
objectData.object = Identification::create(*m_world, id);
else if(classId == Booster::classId)
{
objectData.object = Booster::create(*m_world, id);
}
else if(classId == Board::classId)
objectData.object = Board::create(*m_world, id);
else if(startsWith(classId, Tiles::classIdPrefix))

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2024 Reinder Feenstra
* Copyright (C) 2021-2026 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -100,6 +100,7 @@ enum class LogMessage : uint32_t
I2003_FIRMWARE_VERSION_X = LogMessageOffset::info + 2003,
I2004_HSI_88_X = LogMessageOffset::info + 2004,
I2005_X = LogMessageOffset::info + 2005,
I2006_BOOSTER_X_SOFTWARE_VERSION_X = LogMessageOffset::info + 2006,
I3001_THROTTLE_X_ACQUIRED_TRAIN_X = LogMessageOffset::info + 3001,
I3002_THROTTLE_X_RELEASED_TRAIN_X = LogMessageOffset::info + 3002,
I9001_STOPPED_SCRIPT = LogMessageOffset::info + 9001,
@ -167,6 +168,9 @@ enum class LogMessage : uint32_t
W2018_TIMEOUT_NO_ECHO_WITHIN_X_MS = LogMessageOffset::warning + 2018,
W2019_Z21_BROADCAST_FLAG_MISMATCH = LogMessageOffset::warning + 2019,
W2020_DCCEXT_RCN213_IS_NOT_SUPPORTED = LogMessageOffset::warning + 2020,
// RESERVED: W2025_READING_BOOSTER_X_SOFTWARE_VERSION_FAILED_X = LogMessageOffset::warning + 2025,
W2026_READING_BOOSTER_X_TEMPERATURE_FAILED_X = LogMessageOffset::warning + 2026,
W2027_READING_BOOSTER_X_LOAD_FAILED_X = LogMessageOffset::warning + 2027,
W3001_NX_BUTTON_CONNECTED_TO_TWO_BLOCKS = LogMessageOffset::warning + 3001,
W3002_NX_BUTTON_NOT_CONNECTED_TO_ANY_BLOCK = LogMessageOffset::warning + 3002,
W3003_LOCKED_TURNOUT_CHANGED = LogMessageOffset::warning + 3003,
@ -207,6 +211,9 @@ enum class LogMessage : uint32_t
E2022_SOCKET_CREATE_FAILED_X = LogMessageOffset::error + 2022,
E2023_SOCKET_IOCTL_FAILED_X = LogMessageOffset::error + 2023,
E2024_UNKNOWN_LOCOMOTIVE_MFX_UID_X = LogMessageOffset::error + 2024,
E2025_READING_BOOSTER_X_SOFTWARE_VERSION_FAILED_X = LogMessageOffset::error + 2025,
E2026_READING_BOOSTER_X_TEMPERATURE_FAILED_X = LogMessageOffset::error + 2026,
E2027_READING_BOOSTER_X_LOAD_FAILED_X = LogMessageOffset::error + 2027,
E3001_CANT_DELETE_RAIL_VEHICLE_WHEN_IN_ACTIVE_TRAIN = LogMessageOffset::error + 3001,
E3002_CANT_DELETE_ACTIVE_TRAIN = LogMessageOffset::error + 3002,
E3003_TRAIN_STOPPED_ON_TURNOUT_X_CHANGED = LogMessageOffset::error + 3003,

Datei anzeigen

@ -3306,5 +3306,65 @@
{
"term": "zone:speed_limit",
"definition": "Speed limit"
},
{
"term": "hardware:boosters",
"definition": "Boosters"
},
{
"term": "booster:type",
"definition": "Type"
},
{
"term": "booster:load",
"definition": "Load"
},
{
"term": "booster:temperature",
"definition": "Temperature"
},
{
"term": "booster:current",
"definition": "Current"
},
{
"term": "booster:voltage",
"definition": "Voltage"
},
{
"term": "booster:input_voltage",
"definition": "Input voltage"
},
{
"term": "booster_driver:poll_interval",
"definition": "Poll interval"
},
{
"term": "category:status",
"definition": "Status"
},
{
"term": "message:I2006",
"definition": "Booster '%1' software version: %2"
},
{
"term": "message:W2026",
"definition": "Reading booster '%1' temperature failed: %2"
},
{
"term": "message:W2027",
"definition": "Reading booster '%1' load failed: %2"
},
{
"term": "message:E2025",
"definition": "Reading booster '%1' software version failed: %2"
},
{
"term": "message:E2026",
"definition": "Reading booster '%1' temperature failed: %2"
},
{
"term": "message:E2027",
"definition": "Reading booster '%1' load failed: %2"
}
]