OutputController: Added support for ECoS object, see #95

Dieser Commit ist enthalten in:
Reinder Feenstra 2024-03-13 23:45:23 +01:00
Ursprung 64d64e909f
Commit 5f0d78ae14
44 geänderte Dateien mit 1129 neuen und 146 gelöschten Zeilen

Datei anzeigen

@ -54,6 +54,7 @@ OutputKeyboard::OutputKeyboard(std::shared_ptr<Connection> connection, Handle ha
break;
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false);
break;
}
@ -118,6 +119,7 @@ void OutputKeyboard::created()
break;
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false);
break;
}

Datei anzeigen

@ -171,6 +171,7 @@ OutputKeyboardWidget::OutputKeyboardWidget(std::shared_ptr<OutputKeyboard> objec
break;
case OutputType::Aspect:
case OutputType::ECoSState:
assert(false); // not (yet) supported
break;
}
@ -210,6 +211,7 @@ OutputKeyboardWidget::OutputKeyboardWidget(std::shared_ptr<OutputKeyboard> objec
break;
}
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false);
break;
}
@ -350,6 +352,7 @@ void OutputKeyboardWidget::updateLEDs()
break;
}
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false);
break;
}

Datei anzeigen

@ -54,6 +54,7 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
: QWidget(parent)
, m_object{std::move(object)}
, m_addresses{m_object->getVectorProperty("addresses")}
, m_ecosObject{dynamic_cast<Property*>(m_object->getProperty("ecos_object"))}
, m_items{m_object->getObjectVectorProperty("items")}
, m_table{new QTableWidget(this)}
{
@ -73,6 +74,11 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
form->addRow(new InterfaceItemNameLabel(*m_addresses, this), new PropertyAddresses(*m_addresses, m_object->getMethod("add_address"), m_object->getMethod("remove_address"), this));
connect(m_addresses, &AbstractVectorProperty::valueChanged, this, &OutputMapWidget::updateTableOutputColumns);
}
if(m_ecosObject)
{
form->addRow(new InterfaceItemNameLabel(*m_ecosObject, this), new PropertyComboBox(*m_ecosObject, this));
connect(m_ecosObject, &AbstractVectorProperty::valueChanged, this, &OutputMapWidget::updateTableOutputColumns);
}
l->addLayout(form);
m_table->setColumnCount(columnCountNonOutput);
@ -135,18 +141,25 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
void OutputMapWidget::updateTableOutputColumns()
{
if(!m_addresses) /*[[unlikely]]*/
if(m_addresses && m_addresses->getAttributeBool(AttributeName::Visible, true))
{
return;
const auto size = m_addresses->size();
m_table->setColumnCount(columnCountNonOutput + size);
for(int i = 0; i < size; i++)
{
const int column = columnCountNonOutput + i;
m_table->setHorizontalHeaderItem(column, new QTableWidgetItem(QString("#%1").arg(m_addresses->getInt(i))));
}
}
const auto size = m_addresses->size();
m_table->setColumnCount(columnCountNonOutput + size);
for(int i = 0; i < size; i++)
else if(m_ecosObject && m_ecosObject->getAttributeBool(AttributeName::Visible, true))
{
const int column = columnCountNonOutput + i;
m_table->setHorizontalHeaderItem(column, new QTableWidgetItem(QString("#%1").arg(m_addresses->getInt(i))));
m_table->setColumnCount(columnCountNonOutput + 1);
m_table->setHorizontalHeaderItem(columnCountNonOutput, new QTableWidgetItem(Locale::tr("output.ecos_object:state")));
}
else
{
m_table->setColumnCount(columnCountNonOutput);
}
}
@ -171,6 +184,10 @@ void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, i
{
m_table->setCellWidget(row, column, new PropertySpinBox(*aspect, this));
}
else if(auto* state = dynamic_cast<Property*>(object->getProperty("state")))
{
m_table->setCellWidget(row, column, new PropertySpinBox(*state, this));
}
}
column++;
}

Datei anzeigen

@ -29,8 +29,10 @@
class QTableWidget;
class Method;
class MethodAction;
class AbstractProperty;
class AbstractVectorProperty;
class ObjectVectorProperty;
class Property;
class OutputMapWidget : public QWidget
{
@ -39,6 +41,7 @@ class OutputMapWidget : public QWidget
protected:
ObjectPtr m_object;
AbstractVectorProperty* m_addresses;
Property* m_ecosObject;
ObjectVectorProperty* m_items;
QTableWidget* m_table;
std::vector<ObjectPtr> m_itemObjects;

Datei anzeigen

@ -0,0 +1,3 @@
{
"address": {}
}

Datei anzeigen

@ -2,6 +2,5 @@
"name": {},
"type": {},
"interface": {},
"channel": {},
"address": {}
"channel": {}
}

Datei anzeigen

@ -58,6 +58,22 @@ struct Attributes
property.setAttribute(AttributeName::AliasValues, values);
}
template<class T>
static inline void addAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
{
assert(keys.size() == values.size());
property.addAttribute(AttributeName::AliasKeys, keys);
property.addAttribute(AttributeName::AliasValues, values);
}
template<class T>
static inline void setAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
{
assert(keys.size() == values.size());
property.setAttribute(AttributeName::AliasKeys, keys);
property.setAttribute(AttributeName::AliasValues, values);
}
static inline void addCategory(InterfaceItem& item, std::string_view value)
{
item.addAttribute(AttributeName::Category, value);

Datei anzeigen

@ -30,6 +30,7 @@
#include "../protocol/ecos/messages.hpp"
#include "../protocol/ecos/iohandler/tcpiohandler.hpp"
#include "../protocol/ecos/iohandler/simulationiohandler.hpp"
#include "../protocol/ecos/object/switch.hpp"
#include "../../core/attributes.hpp"
#include "../../core/method.tpp"
#include "../../core/objectproperty.tpp"
@ -119,16 +120,34 @@ void ECoSInterface::inputSimulateChange(uint32_t channel, uint32_t address, Simu
tcb::span<const OutputChannel> ECoSInterface::outputChannels() const
{
static const auto values = makeArray(OutputChannel::AccessoryDCC, OutputChannel::AccessoryMotorola);
static const auto values = makeArray(OutputChannel::AccessoryDCC, OutputChannel::AccessoryMotorola, OutputChannel::ECoSObject);
return values;
}
bool ECoSInterface::setOutputValue(OutputChannel channel, uint32_t address, OutputValue value)
std::pair<tcb::span<const uint16_t>, tcb::span<const std::string>> ECoSInterface::getOutputECoSObjects(OutputChannel channel) const
{
if(channel == OutputChannel::ECoSObject) /*[[likely]]*/
{
return {m_outputECoSObjectIds, m_outputECoSObjectNames};
}
return OutputController::getOutputECoSObjects(channel);
}
bool ECoSInterface::isOutputId(OutputChannel channel, uint32_t outputId) const
{
if(channel == OutputChannel::ECoSObject)
{
return inRange<uint32_t>(outputId, ECoS::ObjectId::switchMin, ECoS::ObjectId::switchMax);
}
return OutputController::isOutputId(channel, outputId);
}
bool ECoSInterface::setOutputValue(OutputChannel channel, uint32_t outputId, OutputValue value)
{
return
m_kernel &&
inRange(address, outputAddressMinMax(channel)) &&
m_kernel->setOutput(channel, static_cast<uint16_t>(address), std::get<OutputPairValue>(value));
isOutputId(channel, outputId) &&
m_kernel->setOutput(channel, outputId, value);
}
bool ECoSInterface::setOnline(bool& value, bool simulation)
@ -176,6 +195,38 @@ bool ECoSInterface::setOnline(bool& value, bool simulation)
if(!contains(m_world.state.value(), WorldState::Run))
m_world.run();
});
m_kernel->setOnObjectChanged(
[this](std::size_t typeHash, uint16_t objectId, const std::string& objectName)
{
if(typeHash == typeid(ECoS::Switch).hash_code())
{
if(auto it = std::find(m_outputECoSObjectIds.begin(), m_outputECoSObjectIds.end(), objectId); it != m_outputECoSObjectIds.end())
{
const std::size_t index = std::distance(m_outputECoSObjectIds.begin(), it);
m_outputECoSObjectNames[index] = objectName;
}
else
{
m_outputECoSObjectIds.emplace_back(objectId);
m_outputECoSObjectNames.emplace_back(objectName);
}
assert(m_outputECoSObjectIds.size() == m_outputECoSObjectNames.size());
outputECoSObjectsChanged();
}
});
m_kernel->setOnObjectRemoved(
[this](uint16_t objectId)
{
assert(objectId == 0);
if(auto it = std::find(m_outputECoSObjectIds.begin(), m_outputECoSObjectIds.end(), objectId); it != m_outputECoSObjectIds.end())
{
const std::size_t index = std::distance(m_outputECoSObjectIds.begin(), it);
m_outputECoSObjectIds.erase(it);
m_outputECoSObjectNames.erase(std::next(m_outputECoSObjectNames.begin(), index));
assert(m_outputECoSObjectIds.size() == m_outputECoSObjectNames.size());
outputECoSObjectsChanged();
}
});
m_kernel->setDecoderController(this);
m_kernel->setInputController(this);
m_kernel->setOutputController(this);
@ -187,6 +238,10 @@ bool ECoSInterface::setOnline(bool& value, bool simulation)
m_kernel->setConfig(ecos->config());
});
// Reset output object list:
m_outputECoSObjectIds.assign({0});
m_outputECoSObjectNames.assign({{}});
Attributes::setEnabled(hostname, false);
}
catch(const LogMessageException& e)

Datei anzeigen

@ -53,6 +53,8 @@ class ECoSInterface final
std::unique_ptr<ECoS::Kernel> m_kernel;
boost::signals2::connection m_ecosPropertyChanged;
ECoS::Simulation m_simulation;
std::vector<uint16_t> m_outputECoSObjectIds;
std::vector<std::string> m_outputECoSObjectNames;
void addToWorld() final;
void destroying() final;
@ -85,7 +87,9 @@ class ECoSInterface final
// OutputController:
tcb::span<const OutputChannel> outputChannels() const final;
[[nodiscard]] bool setOutputValue(OutputChannel channel, uint32_t address, OutputValue value) final;
std::pair<tcb::span<const uint16_t>, tcb::span<const std::string>> getOutputECoSObjects(OutputChannel channel) const final;
bool isOutputId(OutputChannel channel, uint32_t id) const final;
[[nodiscard]] bool setOutputValue(OutputChannel channel, uint32_t outputId, OutputValue value) final;
};
#endif

Datei anzeigen

@ -0,0 +1,30 @@
/**
* server/src/hardware/output/output.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2022,2024 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 "addressoutput.hpp"
AddressOutput::AddressOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_, uint32_t address_)
: Output(std::move(outputController), channel_, type_)
, address{this, "address", address_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
{
m_interfaceItems.add(address);
}

Datei anzeigen

@ -0,0 +1,44 @@
/**
* server/src/hardware/output/addressoutput.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_OUTPUT_ADDRESSOUTPUT_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ADDRESSOUTPUT_HPP
#include "output.hpp"
class AddressOutput : public Output
{
friend class OutputController;
protected:
AddressOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_, uint32_t address_);
public:
Property<uint32_t> address;
uint32_t id() const final
{
return address.value();
}
};
#endif

Datei anzeigen

@ -28,7 +28,7 @@
#include "../../utils/inrange.hpp"
AspectOutput::AspectOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint32_t address_)
: Output(std::move(outputController), channel_, OutputType::Pair, address_)
: AddressOutput(std::move(outputController), channel_, OutputType::Pair, address_)
, value{this, "value", -1, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, setValue{*this, "set_value", MethodFlags::ScriptCallable,
[this](int16_t newValue)

Datei anzeigen

@ -23,11 +23,11 @@
#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ASPECTOUTPUT_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ASPECTOUTPUT_HPP
#include "output.hpp"
#include "addressoutput.hpp"
#include "../../core/method.hpp"
#include "../../core/event.hpp"
class AspectOutput final : public Output
class AspectOutput final : public AddressOutput
{
friend class OutputController;

Datei anzeigen

@ -0,0 +1,56 @@
/**
* server/src/hardware/output/ecosstateoutput.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "ecosstateoutput.hpp"
#include "outputcontroller.hpp"
#include "../../core/attributes.hpp"
#include "../../core/method.tpp"
#include "../../core/objectproperty.tpp"
#include "../../utils/inrange.hpp"
ECoSStateOutput::ECoSStateOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint16_t ecosObjectId_)
: Output(std::move(outputController), channel_, OutputType::ECoSState)
, ecosObjectId{this, "ecos_object_id", ecosObjectId_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, value{this, "value", 0, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, setValue{*this, "set_value", MethodFlags::ScriptCallable,
[this](uint8_t newValue)
{
assert(interface);
return interface->setOutputValue(channel, ecosObjectId, newValue);
}}
, onValueChanged{*this, "on_value_changed", EventFlags::Scriptable}
{
Attributes::addObjectEditor(value, false);
Attributes::addMinMax<uint8_t>(value, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max());
m_interfaceItems.add(value);
Attributes::addObjectEditor(setValue, false);
m_interfaceItems.add(setValue);
m_interfaceItems.add(onValueChanged);
}
void ECoSStateOutput::updateValue(uint8_t newValue)
{
value.setValueInternal(newValue);
fireEvent(onValueChanged, newValue, shared_ptr<ECoSStateOutput>());
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/hardware/output/ecosstateoutput.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_OUTPUT_ECOSSTATEOUTPUT_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ECOSSTATEOUTPUT_HPP
#include "output.hpp"
#include "../../core/method.hpp"
#include "../../core/event.hpp"
class ECoSStateOutput final : public Output
{
friend class OutputController;
CLASS_ID("output.ecos_state")
protected:
void updateValue(uint8_t newValue);
public:
Property<uint16_t> ecosObjectId;
Property<uint8_t> value;
Method<bool(uint8_t)> setValue;
Event<uint8_t, const std::shared_ptr<ECoSStateOutput>&> onValueChanged;
ECoSStateOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint16_t ecosObjectId_);
uint32_t id() const final
{
return ecosObjectId.value();
}
};
#endif

Datei anzeigen

@ -45,7 +45,8 @@ std::vector<OutputKeyboard::OutputInfo> PairOutputKeyboard::getOutputInfo() cons
{
if(it.second->channel == channel)
{
states.emplace_back(OutputInfo{it.second->address.value(), true, static_cast<const PairOutput&>(*it.second).value.value()});
const auto& output = static_cast<const PairOutput&>(*it.second);
states.emplace_back(OutputInfo{output.address.value(), true, output.value.value()});
}
}
return states;

Datei anzeigen

@ -30,7 +30,7 @@ SingleOutputKeyboard::SingleOutputKeyboard(OutputController& controller, OutputC
, setOutputValue(*this, "set_output_value",
[this](uint32_t address, bool value)
{
return m_controller.setOutputValue(channel, address, value);
return m_controller.setOutputValue(channel, address, toTriState(value));
})
, outputValueChanged(*this, "output_value_changed", EventFlags::Public)
{
@ -45,7 +45,8 @@ std::vector<OutputKeyboard::OutputInfo> SingleOutputKeyboard::getOutputInfo() co
{
if(it.second->channel == channel)
{
states.emplace_back(OutputInfo{it.second->address.value(), true, static_cast<const SingleOutput&>(*it.second).value.value()});
const auto& output = static_cast<const SingleOutput&>(*it.second);
states.emplace_back(OutputInfo{output.address.value(), true, output.value.value()});
}
}
return states;

Datei anzeigen

@ -22,6 +22,7 @@
#include "outputlisttablemodel.hpp"
#include "outputlist.hpp"
#include "../addressoutput.hpp"
#include "../outputcontroller.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../utils/displayname.hpp"
@ -95,7 +96,11 @@ std::string OutputListTableModel::getText(uint32_t column, uint32_t row) const
break;
case OutputListColumn::Address:
return std::to_string(output.address.value());
if(auto* addressOutput = dynamic_cast<const AddressOutput*>(&output))
{
return std::to_string(addressOutput->address.value());
}
return {};
}
assert(false);
}

Datei anzeigen

@ -26,7 +26,9 @@
#include "outputmapsingleoutputaction.hpp"
#include "outputmappairoutputaction.hpp"
#include "outputmapaspectoutputaction.hpp"
#include "outputmapecosstateoutputaction.hpp"
#include "../outputcontroller.hpp"
#include "../../interface/interface.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/method.tpp"
#include "../../../core/objectproperty.tpp"
@ -46,8 +48,19 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
},
[this](const std::shared_ptr<OutputController>& newValue)
{
m_interfaceDestroying.disconnect();
if(newValue)
{
if(auto* object = dynamic_cast<Object*>(newValue.get())) /*[[likely]]*/
{
m_interfaceDestroying = object->onDestroying.connect(
[this](Object& /*object*/)
{
interface = nullptr;
});
}
if(!interface)
{
// No interface was assigned.
@ -81,21 +94,36 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
{
assert(addresses.empty());
assert(m_outputs.empty());
const uint32_t address = newValue->getUnusedOutputAddress(channel);
addresses.appendInternal(address);
m_outputs.emplace_back(newValue->getOutput(channel, address, parent()));
const auto outputType = newValue->outputType(channel);
for(auto& item : items)
switch(channel)
{
item->outputActions.appendInternal(createOutputAction(outputType, 0));
case OutputChannel::Output:
case OutputChannel::Accessory:
case OutputChannel::AccessoryDCC:
case OutputChannel::AccessoryMotorola:
case OutputChannel::DCCext:
case OutputChannel::Turnout:
{
const uint32_t address = newValue->getUnusedOutputAddress(channel);
addresses.appendInternal(address);
addOutput(channel, address, *newValue);
break;
}
case OutputChannel::ECoSObject:
if(newValue->isOutputId(channel, ecosObject))
{
addOutput(channel, ecosObject, *newValue);
}
break;
}
updateOutputActions(newValue->outputType(channel));
}
}
else // no interface
{
for(auto& output : m_outputs)
{
if(output)
if(output) /*[[likely]]*/
{
interface->releaseOutput(*output, parent());
}
@ -120,9 +148,28 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
}
// Get outputs for current channel:
for(uint32_t address : addresses)
switch(newValue)
{
m_outputs.emplace_back(interface->getOutput(newValue, address, parent()));
case OutputChannel::Output:
case OutputChannel::Accessory:
case OutputChannel::AccessoryDCC:
case OutputChannel::AccessoryMotorola:
case OutputChannel::DCCext:
case OutputChannel::Turnout:
ecosObject.setValueInternal(0);
for(uint32_t address : addresses)
{
addOutput(newValue, address);
}
break;
case OutputChannel::ECoSObject:
addresses.clearInternal();
if(interface->isOutputId(newValue, ecosObject))
{
addOutput(newValue, ecosObject);
}
break;
}
channelChanged();
@ -160,7 +207,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
return false; // Duplicate addresses aren't allowed.
}
auto output = interface->getOutput(channel, value, parent());
auto output = getOutput(channel, value, *interface);
if(!output) /*[[unlikely]]*/
{
return false; // Output doesn't exist.
@ -168,13 +215,39 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
if(index < m_outputs.size() && m_outputs[index])
{
interface->releaseOutput(*m_outputs[index], parent());
releaseOutput(*m_outputs[index]);
}
m_outputs[index] = output;
return true;
}}
, ecosObject{this, "ecos_object", 0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
nullptr,
[this](uint16_t& value)
{
if(!interface) /*[[unlikely]]*/
{
return false;
}
if(value == 0 || interface->isOutputId(channel, value)) // 0 = no object
{
if(!m_outputs.empty())
{
releaseOutput(*m_outputs.front());
m_outputs.clear();
}
if(value != 0)
{
addOutput(channel, value);
}
updateOutputActions();
return true;
}
return false;
}}
, items{*this, "items", {}, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, addAddress{*this, "add_address", MethodFlags::NoScript,
[this]()
@ -189,7 +262,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
}
const uint32_t address = getUnusedAddress();
addresses.appendInternal(address);
m_outputs.emplace_back(interface->getOutput(channel, address, parent()));
addOutput(channel, address);
addressesSizeChanged();
}
}}
@ -201,7 +274,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
addresses.eraseInternal(addresses.size() - 1);
if(m_outputs.back())
{
interface->releaseOutput(*m_outputs.back(), parent());
releaseOutput(*m_outputs.back());
}
m_outputs.pop_back();
addressesSizeChanged();
@ -228,6 +301,12 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
Attributes::addMinMax<uint32_t>(addresses, 0, 0);
m_interfaceItems.add(addresses);
Attributes::addAliases(ecosObject, tcb::span<const uint16_t>{}, tcb::span<const std::string>{});
Attributes::addEnabled(ecosObject, editable);
Attributes::addValues(ecosObject, tcb::span<const uint16_t>{});
Attributes::addVisible(ecosObject, false);
m_interfaceItems.add(ecosObject);
m_interfaceItems.add(items);
Attributes::addEnabled(addAddress, false);
@ -241,26 +320,41 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
updateEnabled();
}
OutputMap::~OutputMap()
{
m_interfaceDestroying.disconnect();
m_outputECoSObjectsChanged.disconnect();
}
void OutputMap::load(WorldLoader& loader, const nlohmann::json& data)
{
SubObject::load(loader, data);
if(interface)
{
for(uint32_t address : addresses)
switch(channel)
{
m_outputs.emplace_back(interface->getOutput(channel, address, parent()));
case OutputChannel::Output:
case OutputChannel::Accessory:
case OutputChannel::AccessoryDCC:
case OutputChannel::AccessoryMotorola:
case OutputChannel::DCCext:
case OutputChannel::Turnout:
for(uint32_t address : addresses)
{
addOutput(channel, address);
}
break;
case OutputChannel::ECoSObject:
if(interface->isOutputId(channel, ecosObject))
{
addOutput(channel, ecosObject);
}
break;
}
const auto outputType = interface->outputType(channel);
const auto addressCount = addresses.size();
for(auto& item : items)
{
for(size_t i = 0; i < addressCount; i++)
{
item->outputActions.appendInternal(createOutputAction(outputType, i));
}
}
updateOutputActions();
}
}
@ -282,11 +376,22 @@ void OutputMap::worldEvent(WorldState state, WorldEvent event)
void OutputMap::interfaceChanged()
{
Attributes::setValues(channel, interface ? interface->outputChannels() : tcb::span<const OutputChannel>{});
const auto outputChannels = interface ? interface->outputChannels() : tcb::span<const OutputChannel>{};
Attributes::setValues(channel, outputChannels);
Attributes::setVisible(channel, interface);
Attributes::setVisible(addresses, interface);
Attributes::setVisible(addAddress, interface);
Attributes::setVisible(removeAddress, interface);
m_outputECoSObjectsChanged.disconnect();
if(std::find(outputChannels.begin(), outputChannels.end(), OutputChannel::ECoSObject))
{
m_outputECoSObjectsChanged = interface->outputECoSObjectsChanged.connect(
[this]()
{
const auto aliases = interface->getOutputECoSObjects(OutputChannel::ECoSObject);
Attributes::setAliases(ecosObject, aliases.first, aliases.second);
Attributes::setValues(ecosObject, aliases.first);
});
}
channelChanged();
}
@ -295,29 +400,68 @@ void OutputMap::channelChanged()
{
if(interface)
{
const auto addressRange = interface->outputAddressMinMax(channel);
const uint32_t addressCount = (addressRange.second - addressRange.first + 1);
Attributes::setMinMax(addresses, addressRange);
while(addressCount < addresses.size()) // Reduce number of addresses if larger than address space.
switch(channel.value())
{
addresses.eraseInternal(addresses.size() - 1);
}
// Make sure all addresses are in range:
for(size_t i = 0; i < addresses.size(); i++)
{
if(!inRange(addresses[i], addressRange))
case OutputChannel::Output:
case OutputChannel::Accessory:
case OutputChannel::AccessoryDCC:
case OutputChannel::AccessoryMotorola:
case OutputChannel::DCCext:
case OutputChannel::Turnout:
{
addresses.setValueInternal(i, getUnusedAddress());
Attributes::setVisible({addresses, addAddress, removeAddress}, true);
Attributes::setVisible(ecosObject, false);
Attributes::setAliases(ecosObject, tcb::span<const uint16_t>{}, tcb::span<const std::string>{});
Attributes::setValues(ecosObject, tcb::span<const uint16_t>{});
if(addresses.empty())
{
const auto address = interface->getUnusedOutputAddress(channel);
addresses.appendInternal(address);
addOutput(channel, address);
updateOutputActions();
}
const auto addressRange = interface->outputAddressMinMax(channel);
const uint32_t addressCount = (addressRange.second - addressRange.first + 1);
Attributes::setMinMax(addresses, addressRange);
while(addressCount < addresses.size()) // Reduce number of addresses if larger than address space.
{
addresses.eraseInternal(addresses.size() - 1);
}
// Make sure all addresses are in range:
for(size_t i = 0; i < addresses.size(); i++)
{
if(!inRange(addresses[i], addressRange))
{
addresses.setValueInternal(i, getUnusedAddress());
}
}
addressesSizeChanged();
break;
}
case OutputChannel::ECoSObject:
{
Attributes::setVisible({addresses, addAddress, removeAddress}, false);
Attributes::setVisible(ecosObject, true);
const auto aliases = interface->getOutputECoSObjects(channel);
Attributes::setAliases(ecosObject, aliases.first, aliases.second);
Attributes::setValues(ecosObject, aliases.first);
updateOutputActions();
break;
}
}
addressesSizeChanged();
}
else
{
Attributes::setVisible({addresses, addAddress, removeAddress, ecosObject}, false);
Attributes::setMinMax(addresses, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max());
Attributes::setAliases(ecosObject, tcb::span<const uint16_t>{}, tcb::span<const std::string>{});
Attributes::setValues(ecosObject, tcb::span<const uint16_t>{});
}
}
@ -326,26 +470,36 @@ void OutputMap::addressesSizeChanged()
Attributes::setDisplayName(addresses, addresses.size() == 1 ? DisplayName::Hardware::address : DisplayName::Hardware::addresses);
assert(addresses.size() == m_outputs.size());
const auto outputType = interface->outputType(channel);
updateOutputActions();
updateEnabled();
}
void OutputMap::updateOutputActions()
{
assert(interface);
updateOutputActions(interface->outputType(channel));
}
void OutputMap::updateOutputActions(OutputType outputType)
{
for(const auto& item : items)
{
while(addresses.size() > item->outputActions.size())
while(m_outputs.size() > item->outputActions.size())
{
std::shared_ptr<OutputMapOutputAction> outputAction = createOutputAction(outputType, item->outputActions.size());
assert(outputAction);
item->outputActions.appendInternal(outputAction);
}
while(addresses.size() < item->outputActions.size())
while(m_outputs.size() < item->outputActions.size())
{
item->outputActions.back()->destroy();
item->outputActions.removeInternal(item->outputActions.back());
}
assert(addresses.size() == item->outputActions.size());
assert(m_outputs.size() == item->outputActions.size());
}
updateEnabled();
}
void OutputMap::updateEnabled()
@ -357,6 +511,7 @@ void OutputMap::updateEnabled()
Attributes::setEnabled(addresses, editable);
Attributes::setEnabled(addAddress, editable && addresses.size() < addressesSizeMax);
Attributes::setEnabled(removeAddress, editable && addresses.size() > addressesSizeMin);
Attributes::setEnabled(ecosObject, editable);
}
uint32_t OutputMap::getUnusedAddress() const
@ -390,7 +545,34 @@ std::shared_ptr<OutputMapOutputAction> OutputMap::createOutputAction(OutputType
case OutputType::Aspect:
return std::make_shared<OutputMapAspectOutputAction>(*this, index);
case OutputType::ECoSState:
return std::make_shared<OutputMapECoSStateOutputAction>(*this, index);
}
assert(false);
return {};
}
void OutputMap::addOutput(OutputChannel ch, uint32_t id)
{
addOutput(ch, id, *interface);
}
void OutputMap::addOutput(OutputChannel ch, uint32_t id, OutputController& outputController)
{
m_outputs.emplace_back(getOutput(ch, id, outputController));
assert(m_outputs.back());
}
std::shared_ptr<Output> OutputMap::getOutput(OutputChannel ch, uint32_t id, OutputController& outputController)
{
auto output = outputController.getOutput(ch, id, parent());
// TODO: connect output value changed
return output;
}
void OutputMap::releaseOutput(Output& output)
{
// TODO: disconnect output value changed
interface->releaseOutput(output, parent());
}

Datei anzeigen

@ -47,6 +47,14 @@ class OutputMap : public SubObject
static constexpr size_t addressesSizeMin = 1;
static constexpr size_t addressesSizeMax = 8;
boost::signals2::connection m_interfaceDestroying;
boost::signals2::connection m_outputECoSObjectsChanged;
void addOutput(OutputChannel ch, uint32_t id);
void addOutput(OutputChannel ch, uint32_t id, OutputController& outputController);
std::shared_ptr<Output> getOutput(OutputChannel ch, uint32_t id, OutputController& outputController);
void releaseOutput(Output& output);
protected:
Outputs m_outputs;
@ -57,6 +65,8 @@ class OutputMap : public SubObject
void interfaceChanged();
void channelChanged();
void addressesSizeChanged();
void updateOutputActions();
void updateOutputActions(OutputType outputType);
void updateEnabled();
uint32_t getUnusedAddress() const;
@ -66,11 +76,13 @@ class OutputMap : public SubObject
ObjectProperty<OutputController> interface;
Property<OutputChannel> channel;
VectorProperty<uint32_t> addresses;
Property<uint16_t> ecosObject;
ObjectVectorProperty<OutputMapItem> items;
Method<void()> addAddress;
Method<void()> removeAddress;
OutputMap(Object& _parent, std::string_view parentPropertyName);
~OutputMap() override;
const std::shared_ptr<Output>& output(size_t index) const
{

Datei anzeigen

@ -0,0 +1,57 @@
/**
* server/src/hardware/output/map/outputmapecosstateoutputaction.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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 "outputmapecosstateoutputaction.hpp"
#include "../ecosstateoutput.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/method.tpp"
#include "../../../world/world.hpp"
OutputMapECoSStateOutputAction::OutputMapECoSStateOutputAction(OutputMap& parent_, size_t outputIndex)
: OutputMapOutputAction(parent_, outputIndex)
, state{this, "state", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
{
const bool editable = contains(world().state.value(), WorldState::Edit);
Attributes::addEnabled(state, editable);
m_interfaceItems.add(state);
}
void OutputMapECoSStateOutputAction::execute()
{
ecosStateOutput().setValue(state);
}
void OutputMapECoSStateOutputAction::worldEvent(WorldState worldState, WorldEvent event)
{
OutputMapOutputAction::worldEvent(worldState, event);
const bool editable = contains(worldState, WorldState::Edit);
Attributes::setEnabled(state, editable);
}
ECoSStateOutput& OutputMapECoSStateOutputAction::ecosStateOutput()
{
assert(dynamic_cast<ECoSStateOutput*>(&output()));
return static_cast<ECoSStateOutput&>(output());
}

Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/hardware/output/map/outputmapecosstateoutputaction.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 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_OUTPUT_MAP_OUTPUTMAPECOSSTATEOUTPUTACTION_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_MAP_OUTPUTMAPECOSSTATEOUTPUTACTION_HPP
#include "outputmapoutputaction.hpp"
class ECoSStateOutput;
class OutputMapECoSStateOutputAction final : public OutputMapOutputAction
{
CLASS_ID("output_map_output_action.aspect")
private:
ECoSStateOutput& ecosStateOutput();
protected:
void worldEvent(WorldState worldState, WorldEvent event) final;
public:
Property<uint8_t> state;
OutputMapECoSStateOutputAction(OutputMap& _parent, size_t outputIndex);
void execute() final;
};
#endif

Datei anzeigen

@ -31,18 +31,15 @@
#include "../../log/log.hpp"
#include "../../utils/displayname.hpp"
Output::Output(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_, uint32_t address_)
Output::Output(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_)
: interface{this, "interface", std::move(outputController), PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, channel{this, "channel", channel_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, type{this, "type", type_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, address{this, "address", address_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
{
m_interfaceItems.add(interface);
Attributes::addValues(channel, outputChannelValues);
m_interfaceItems.add(channel);
m_interfaceItems.add(address);
}
std::string Output::getObjectId() const

Datei anzeigen

@ -41,13 +41,10 @@ class Output : public Object
friend class OutputController;
private:
static constexpr uint32_t addressMinDefault = 0;
static constexpr uint32_t addressMaxDefault = 1'000'000;
std::set<std::shared_ptr<Object>> m_usedBy; //!< Objects that use the output.
protected:
Output(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_, uint32_t address_);
Output(std::shared_ptr<OutputController> outputController, OutputChannel channel_, OutputType type_);
public:
static constexpr uint32_t invalidAddress = std::numeric_limits<uint32_t>::max();
@ -55,7 +52,13 @@ class Output : public Object
ObjectProperty<OutputController> interface;
Property<OutputChannel> channel;
Property<OutputType> type;
Property<uint32_t> address;
/**
* \brief Unique identifier for the output within the channel.
*
* \return Unique identifier, can be any number/mask.
*/
virtual uint32_t id() const = 0;
std::string getObjectId() const final;
};

Datei anzeigen

@ -24,6 +24,7 @@
#include "singleoutput.hpp"
#include "pairoutput.hpp"
#include "aspectoutput.hpp"
#include "ecosstateoutput.hpp"
#include "list/outputlist.hpp"
#include "list/outputlisttablemodel.hpp"
#include "keyboard/singleoutputkeyboard.hpp"
@ -59,6 +60,9 @@ OutputType OutputController::outputType(OutputChannel channel) const
case OutputChannel::DCCext:
return OutputType::Aspect;
case OutputChannel::ECoSObject:
return OutputType::ECoSState;
}
assert(false);
return static_cast<OutputType>(0);
@ -80,11 +84,20 @@ std::pair<uint32_t, uint32_t> OutputController::outputAddressMinMax(OutputChanne
case OutputChannel::Accessory:
case OutputChannel::Turnout:
break;
case OutputChannel::ECoSObject:
return noAddressMinMax;
}
assert(false);
return {0, 0};
}
std::pair<tcb::span<const uint16_t>, tcb::span<const std::string>> OutputController::getOutputECoSObjects(OutputChannel /*channel*/) const
{
assert(false);
return {{}, {}};
}
bool OutputController::isOutputChannel(OutputChannel channel) const
{
const auto channels = outputChannels();
@ -99,12 +112,31 @@ bool OutputController::isOutputChannel(OutputChannel channel) const
return false;
}
bool OutputController::isOutputAddressAvailable(OutputChannel channel, uint32_t address) const
bool OutputController::isOutputId(OutputChannel channel, uint32_t id) const
{
switch(channel)
{
case OutputChannel::AccessoryDCC:
case OutputChannel::DCCext:
case OutputChannel::AccessoryMotorola:
case OutputChannel::Output:
case OutputChannel::Accessory:
case OutputChannel::Turnout:
return inRange(id, outputAddressMinMax(channel)); // id == address
case OutputChannel::ECoSObject:
assert(false);
break;
}
return false;
}
bool OutputController::isOutputAvailable(OutputChannel channel, uint32_t id) const
{
assert(isOutputChannel(channel));
return
inRange(address, outputAddressMinMax(channel)) &&
m_outputs.find({channel, address}) == m_outputs.end();
isOutputId(channel, id) &&
m_outputs.find({channel, id}) == m_outputs.end();
}
uint32_t OutputController::getUnusedOutputAddress(OutputChannel channel) const
@ -115,18 +147,18 @@ uint32_t OutputController::getUnusedOutputAddress(OutputChannel channel) const
for(uint32_t address = range.first; address < range.second; address++)
if(m_outputs.find({channel, address}) == end)
return address;
return Output::invalidAddress;
return AddressOutput::invalidAddress;
}
std::shared_ptr<Output> OutputController::getOutput(OutputChannel channel, uint32_t address, Object& usedBy)
std::shared_ptr<Output> OutputController::getOutput(OutputChannel channel, uint32_t id, Object& usedBy)
{
if(!isOutputChannel(channel) || !inRange(address, outputAddressMinMax(channel)))
if(!isOutputChannel(channel) || !isOutputId(channel, id))
{
return {};
}
// Check if already exists:
if(auto it = m_outputs.find({channel, address}); it != m_outputs.end())
if(auto it = m_outputs.find({channel, id}); it != m_outputs.end())
{
it->second->m_usedBy.emplace(usedBy.shared_from_this());
return it->second;
@ -137,20 +169,24 @@ std::shared_ptr<Output> OutputController::getOutput(OutputChannel channel, uint3
switch(outputType(channel))
{
case OutputType::Single:
output = std::make_shared<SingleOutput>(shared_ptr(), channel, address);
output = std::make_shared<SingleOutput>(shared_ptr(), channel, id);
break;
case OutputType::Pair:
output = std::make_shared<PairOutput>(shared_ptr(), channel, address);
output = std::make_shared<PairOutput>(shared_ptr(), channel, id);
break;
case OutputType::Aspect:
output = std::make_shared<AspectOutput>(shared_ptr(), channel, address);
output = std::make_shared<AspectOutput>(shared_ptr(), channel, id);
break;
case OutputType::ECoSState:
output = std::make_shared<ECoSStateOutput>(shared_ptr(), channel, id);
break;
}
assert(output);
output->m_usedBy.emplace(usedBy.shared_from_this());
m_outputs.emplace(OutputMapKey{channel, address}, output);
m_outputs.emplace(OutputMapKey{channel, id}, output);
outputs->addObject(output);
getWorld(outputs.object()).outputs->addObject(output);
return output;
@ -162,7 +198,7 @@ void OutputController::releaseOutput(Output& output, Object& usedBy)
output.m_usedBy.erase(usedBy.shared_from_this());
if(output.m_usedBy.empty())
{
m_outputs.erase({output.channel.value(), output.address.value()});
m_outputs.erase({output.channel.value(), output.id()});
outputs->removeObject(outputShared);
getWorld(outputs.object()).outputs->removeObject(outputShared);
outputShared->destroy();
@ -170,10 +206,10 @@ void OutputController::releaseOutput(Output& output, Object& usedBy)
}
}
void OutputController::updateOutputValue(OutputChannel channel, uint32_t address, OutputValue value)
void OutputController::updateOutputValue(OutputChannel channel, uint32_t id, OutputValue value)
{
assert(isOutputChannel(channel));
if(auto it = m_outputs.find({channel, address}); it != m_outputs.end())
if(auto it = m_outputs.find({channel, id}); it != m_outputs.end())
{
if(auto* single = dynamic_cast<SingleOutput*>(it->second.get()))
{
@ -187,11 +223,15 @@ void OutputController::updateOutputValue(OutputChannel channel, uint32_t address
{
aspect->updateValue(std::get<int16_t>(value));
}
else if(auto* ecosState = dynamic_cast<ECoSStateOutput*>(it->second.get()))
{
ecosState->updateValue(std::get<uint8_t>(value));
}
}
if(auto keyboard = m_outputKeyboards[channel].lock())
{
keyboard->fireOutputValueChanged(address, value);
keyboard->fireOutputValueChanged(id, value);
}
}
@ -205,6 +245,7 @@ bool OutputController::hasOutputKeyboard(OutputChannel channel) const
return true;
case OutputType::Aspect:
case OutputType::ECoSState:
return false;
}
assert(false);
@ -228,6 +269,7 @@ std::shared_ptr<OutputKeyboard> OutputController::outputKeyboard(OutputChannel c
break;
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
break; // not supported (yet)
}
assert(keyboard);
@ -250,7 +292,8 @@ void OutputController::destroying()
{
const auto& output = outputs->front();
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
output->interface = nullptr; // removes object form the list
output->interface.setValueInternal(nullptr);
outputs->removeObject(output);
}
object.world().outputControllers->remove(std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
}

Datei anzeigen

@ -27,6 +27,7 @@
#include <vector>
#include <unordered_map>
#include <memory>
#include <boost/signals2/signal.hpp>
#include <tcb/span.hpp>
#include <traintastic/enum/outputchannel.hpp>
#include <traintastic/enum/outputtype.hpp>
@ -46,14 +47,16 @@ enum class OutputListColumn;
class OutputController
{
public:
static constexpr std::pair<uint32_t, uint32_t> noAddressMinMax{std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::min()};
struct OutputMapKey
{
OutputChannel channel;
uint32_t address;
uint32_t id;
inline bool operator ==(const OutputMapKey other) const noexcept
{
return channel == other.channel && address == other.address;
return channel == other.channel && id == other.id;
}
};
static_assert(sizeof(OutputMapKey) == sizeof(uint64_t));
@ -62,7 +65,7 @@ class OutputController
{
std::size_t operator()(const OutputMapKey& value) const noexcept
{
return (static_cast<uint64_t>(value.channel) << (8 * sizeof(value.address))) | value.address;
return (static_cast<uint64_t>(value.channel) << (8 * sizeof(value.id))) | value.id;
}
};
@ -82,6 +85,8 @@ class OutputController
void destroying();
public:
boost::signals2::signal<void()> outputECoSObjectsChanged;
ObjectProperty<OutputList> outputs;
/**
@ -99,6 +104,11 @@ class OutputController
*/
bool isOutputChannel(OutputChannel channel) const;
/**
*
*/
virtual bool isOutputId(OutputChannel channel, uint32_t id) const;
/**
*
*/
@ -112,7 +122,12 @@ class OutputController
/**
*
*/
[[nodiscard]] virtual bool isOutputAddressAvailable(OutputChannel channel, uint32_t address) const;
virtual std::pair<tcb::span<const uint16_t>, tcb::span<const std::string>> getOutputECoSObjects(OutputChannel channel) const;
/**
*
*/
[[nodiscard]] virtual bool isOutputAvailable(OutputChannel channel, uint32_t id) const;
/**
* \brief Get the next unused output address
@ -125,18 +140,18 @@ class OutputController
/**
* \brief Get an output.
*
* For each channel/address combination an output object is create once,
* For each channel/id combination an output object is create once,
* if an output is requested multiple time they all share the same instance.
* Once the object the uses the output is no longer using it,
* it must be released using \ref releaseOutput .
* The output object will be destroyed when the are zero users.
*
* \param[in] channel The output channel.
* \param[in] address The output address.
* \param[in] id The output id.
* \param[in] usedBy The object the will use the output.
* \return An output object if the channel/address combination is valid, \c nullptr otherwise.
* \return An output object if the channel/id combination is valid, \c nullptr otherwise.
*/
std::shared_ptr<Output> getOutput(OutputChannel channel, uint32_t address, Object& usedBy);
std::shared_ptr<Output> getOutput(OutputChannel channel, uint32_t id, Object& usedBy);
/**
* \brief Release an output.
@ -150,7 +165,7 @@ class OutputController
/**
* @brief ...
*/
[[nodiscard]] virtual bool setOutputValue(OutputChannel /*channel*/, uint32_t /*address*/, OutputValue /*value*/) = 0;
[[nodiscard]] virtual bool setOutputValue(OutputChannel /*channel*/, uint32_t /*id*/, OutputValue /*value*/) = 0;
/**
* @brief Update the output value
@ -158,10 +173,10 @@ class OutputController
* This function should be called by the hardware layer whenever the output value changes.
*
* @param[in] channel Output channel
* @param[in] address Output address
* @param[in] id Output id
* @param[in] value New output value
*/
void updateOutputValue(OutputChannel channel, uint32_t address, OutputValue value);
void updateOutputValue(OutputChannel channel, uint32_t id, OutputValue value);
/**
* \brief Check is there is an output keyboard available for the channel.

Datei anzeigen

@ -28,6 +28,6 @@
#include <traintastic/enum/tristate.hpp>
#include <traintastic/enum/outputpairvalue.hpp>
using OutputValue = std::variant<TriState, OutputPairValue, int16_t>;
using OutputValue = std::variant<TriState, OutputPairValue, uint8_t, int16_t>;
#endif

Datei anzeigen

@ -27,7 +27,7 @@
#include "../../core/objectproperty.tpp"
PairOutput::PairOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint32_t address_)
: Output(std::move(outputController), channel_, OutputType::Pair, address_)
: AddressOutput(std::move(outputController), channel_, OutputType::Pair, address_)
, value{this, "value", OutputPairValue::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, setValue{*this, "set_value", MethodFlags::ScriptCallable,
[this](OutputPairValue newValue)

Datei anzeigen

@ -23,12 +23,12 @@
#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_PAIROUTPUT_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_PAIROUTPUT_HPP
#include "output.hpp"
#include "addressoutput.hpp"
#include <traintastic/enum/outputpairvalue.hpp>
#include "../../core/method.hpp"
#include "../../core/event.hpp"
class PairOutput final : public Output
class PairOutput final : public AddressOutput
{
friend class OutputController;

Datei anzeigen

@ -27,14 +27,14 @@
#include "../../core/objectproperty.tpp"
SingleOutput::SingleOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint32_t address_)
: Output(std::move(outputController), channel_, OutputType::Pair, address_)
: AddressOutput(std::move(outputController), channel_, OutputType::Pair, address_)
, value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, setValue{*this, "set_value", MethodFlags::ScriptCallable,
[this](bool newValue)
{
return
interface &&
interface->setOutputValue(channel, address, newValue);
interface->setOutputValue(channel, address, toTriState(newValue));
}}
, onValueChanged{*this, "on_value_changed", EventFlags::Scriptable}
{

Datei anzeigen

@ -23,12 +23,12 @@
#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_SINGLEOUTPUT_HPP
#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_SINGLEOUTPUT_HPP
#include "output.hpp"
#include "addressoutput.hpp"
#include "../../core/method.hpp"
#include "../../core/event.hpp"
#include "../../enum/tristate.hpp"
class SingleOutput : public Output
class SingleOutput : public AddressOutput
{
friend class OutputController;

Datei anzeigen

@ -22,6 +22,7 @@
#include "kernel.hpp"
#include <algorithm>
#include <typeinfo>
#include "messages.hpp"
#include "simulation.hpp"
#include "object/ecos.hpp"
@ -104,6 +105,17 @@ void Kernel::setOnGo(std::function<void()> callback)
m_onGo = std::move(callback);
}
void Kernel::setOnObjectChanged(OnObjectChanged callback)
{
assert(!m_started);
m_onObjectChanged = std::move(callback);
}
void Kernel::setOnObjectRemoved(OnObjectRemoved callback)
{
assert(!m_started);
m_onObjectRemoved = std::move(callback);
}
void Kernel::setDecoderController(DecoderController* decoderController)
{
assert(!m_started);
@ -379,33 +391,44 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
}
}
bool Kernel::setOutput(OutputChannel channel, uint16_t address, OutputPairValue value)
bool Kernel::setOutput(OutputChannel channel, uint32_t id, OutputValue value)
{
auto switchProtocol = SwitchProtocol::Unknown;
assert(isEventLoopThread());
switch(channel)
{
case OutputChannel::AccessoryDCC:
switchProtocol = SwitchProtocol::DCC;
break;
case OutputChannel::AccessoryMotorola:
switchProtocol = SwitchProtocol::Motorola;
break;
{
const auto switchProtocol = (channel == OutputChannel::AccessoryDCC) ? SwitchProtocol::DCC : SwitchProtocol::Motorola;
m_ioContext.post(
[this, switchProtocol, address=id, port=(std::get<OutputPairValue>(value) == OutputPairValue::Second)]()
{
switchManager().setSwitch(switchProtocol, address, port);
});
return true;
}
case OutputChannel::ECoSObject:
{
m_ioContext.post(
[this, id, state=std::get<uint8_t>(value)]()
{
if(auto it = m_objects.find(id); it != m_objects.end())
{
if(auto* sw = dynamic_cast<Switch*>(it->second.get()))
{
sw->setState(state);
}
}
});
return true;
}
default: /*[[unlikely]]*/
assert(false);
return false;
break;
}
assert(switchProtocol != SwitchProtocol::Unknown);
m_ioContext.post(
[this, switchProtocol, address, value]()
{
switchManager().setSwitch(switchProtocol, address, value == OutputPairValue::Second);
});
return true;
return false;
}
void Kernel::simulateInputChange(uint32_t channel, uint32_t address, SimulateInputAction action)
@ -510,6 +533,20 @@ void Kernel::switchManagerSwitched(SwitchProtocol protocol, uint16_t address, Ou
}
}
void Kernel::switchStateChanged(uint16_t objectId, uint8_t state)
{
ASSERT_IS_KERNEL_THREAD;
if(!m_outputController)
return;
EventLoop::call(
[this, objectId, state]()
{
m_outputController->updateOutputValue(OutputChannel::ECoSObject, objectId, state);
});
}
void Kernel::feedbackStateChanged(Feedback& object, uint8_t port, TriState value)
{
if(!m_inputController)
@ -563,6 +600,46 @@ void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
m_ioHandler = std::move(handler);
}
bool Kernel::objectExists(uint16_t objectId) const
{
return m_objects.find(objectId) != m_objects.end();
}
void Kernel::addObject(std::unique_ptr<Object> object)
{
objectChanged(*object);
m_objects.add(std::move(object));
}
void Kernel::objectChanged(Object& object)
{
if(!m_onObjectChanged) /*[[unlikely]]*/
{
return;
}
std::string objectName;
if(auto* sw = dynamic_cast<const Switch*>(&object))
{
objectName = sw->nameUI();
}
EventLoop::call(
[this, typeHash=typeid(object).hash_code(), objectId=object.id(), objectName]()
{
m_onObjectChanged(typeHash, objectId, objectName);
});
}
void Kernel::removeObject(uint16_t objectId)
{
m_objects.erase(objectId);
if(m_onObjectRemoved) /*[[likely]]*/
{
m_onObjectRemoved(objectId);
}
}
void Kernel::send(std::string_view message)
{
if(m_ioHandler->send(message))

Datei anzeigen

@ -55,6 +55,9 @@ class Kernel : public ::KernelBase
friend class ECoS;
public:
using OnObjectChanged = std::function<void(std::size_t, uint16_t, const std::string&)>;
using OnObjectRemoved = std::function<void(uint16_t)>;
static constexpr uint16_t s88AddressMin = 1;
static constexpr uint16_t s88AddressMax = 1000; //!< \todo what is the maximum
static constexpr uint16_t ecosDetectorAddressMin = 1;
@ -95,6 +98,8 @@ class Kernel : public ::KernelBase
std::function<void()> m_onGo;
std::function<void()> m_onEmergencyStop;
OnObjectChanged m_onObjectChanged;
OnObjectRemoved m_onObjectRemoved;
DecoderController* m_decoderController;
InputController* m_inputController;
@ -106,6 +111,12 @@ class Kernel : public ::KernelBase
void setIOHandler(std::unique_ptr<IOHandler> handler);
bool objectExists(uint16_t objectId) const;
void addObject(std::unique_ptr<Object> object);
void objectChanged(Object& object);
void removeObject(uint16_t objectId);
//const std::unique_ptr<ECoS&> ecos();
ECoS& ecos();
void ecosGoChanged(TriState value);
@ -183,6 +194,9 @@ class Kernel : public ::KernelBase
*/
void setOnGo(std::function<void()> callback);
void setOnObjectChanged(OnObjectChanged callback);
void setOnObjectRemoved(OnObjectRemoved callback);
/**
* @brief Set the decoder controller
* @param[in] decoderController The decoder controller
@ -246,15 +260,16 @@ class Kernel : public ::KernelBase
/**
* @brief ...
* @param[in] channel Channel
* @param[in] address Output address
* @param[in] id Output id/address
* @param[in] value Output value
* @return \c true if send successful, \c false otherwise.
*/
bool setOutput(OutputChannel channel, uint16_t address, OutputPairValue value);
bool setOutput(OutputChannel channel, uint32_t id, OutputValue value);
void simulateInputChange(uint32_t channel, uint32_t address, SimulateInputAction action);
void switchManagerSwitched(SwitchProtocol protocol, uint16_t address, OutputPairValue value);
void switchStateChanged(uint16_t objectId, uint8_t state);
void feedbackStateChanged(Feedback& object, uint8_t port, TriState value);
};

Datei anzeigen

@ -75,17 +75,22 @@ void Object::send(std::string_view message)
bool Object::objectExists(uint16_t objectId) const
{
return m_kernel.m_objects.find(objectId) != m_kernel.m_objects.end();
return m_kernel.objectExists(objectId);
}
void Object::addObject(std::unique_ptr<Object> object)
{
m_kernel.m_objects.add(std::move(object));
m_kernel.addObject(std::move(object));
}
void Object::nameChanged()
{
m_kernel.objectChanged(*this);
}
void Object::removeObject(uint16_t objectId)
{
m_kernel.m_objects.erase(objectId);
m_kernel.removeObject(objectId);
}
void Object::update(const std::vector<std::string_view>& lines)

Datei anzeigen

@ -48,6 +48,7 @@ class Object
bool objectExists(uint16_t objectId) const;
void addObject(std::unique_ptr<Object> object);
void nameChanged();
void removeObject(uint16_t objectId);
virtual void update(std::string_view /*option*/, std::string_view /*value*/){}// = 0;

Datei anzeigen

@ -79,6 +79,17 @@ bool Switch::receiveEvent(const Event& event)
return Object::receiveEvent(event);
}
std::string Switch::nameUI() const
{
return
std::string(m_name1)
.append(" (")
.append(toString(m_protocol))
.append(" ")
.append(std::to_string(m_address))
.append(")");
}
void Switch::setState(uint8_t value)
{
send(set(m_id, Option::state, value));
@ -89,6 +100,7 @@ void Switch::update(std::string_view option, std::string_view value)
if(option == Option::name1)
{
m_name1 = value;
nameChanged();
}
else if(option == Option::name2)
{
@ -101,6 +113,7 @@ void Switch::update(std::string_view option, std::string_view value)
else if(option == Option::addr)
{
fromChars(value, m_address);
nameChanged();
}
else if(option == Option::addrext)
{
@ -141,11 +154,14 @@ void Switch::update(std::string_view option, std::string_view value)
else if(option == Option::protocol)
{
fromString(value, m_protocol);
nameChanged();
}
else if(option == Option::state)
{
fromChars(value, m_state);
m_kernel.switchStateChanged(m_id, m_state);
if(m_state < m_addrext.size())
{
const auto& port = m_addrext[m_state];

Datei anzeigen

@ -77,6 +77,7 @@ class Switch final : public Object
bool receiveReply(const Reply& reply) final;
bool receiveEvent(const Event& event) final;
std::string nameUI() const;
const std::string& name1() const { return m_name1; }
const std::string& name2() const { return m_name2; }
const std::string& name3() const { return m_name3; }

Datei anzeigen

@ -373,7 +373,7 @@ void Kernel::receive(const Message& message)
[this]()
{
for(const auto& it : m_outputController->outputMap())
postSend(GetOutputState(static_cast<uint16_t>(it.first.address)));
postSend(GetOutputState(static_cast<uint16_t>(it.first.id)));
});
break;
}

Datei anzeigen

@ -78,9 +78,9 @@ int Interface::get_output(lua_State* L)
auto outputController = std::dynamic_pointer_cast<::OutputController>(check<::Interface>(L, lua_upvalueindex(1)));
assert(outputController);
auto channel = check<::OutputChannel>(L, 1);
auto address = check<uint32_t>(L, 2);
auto& stateData = Lua::Sandbox::getStateData(L);
auto output = outputController->getOutput(channel, address, stateData.script());
auto id = check<uint32_t>(L, 2);
auto& stateData = Lua::Sandbox::getStateData(L);
auto output = outputController->getOutput(channel, id, stateData.script());
if(output)
{
stateData.registerOutput(outputController, output);

Datei anzeigen

@ -590,6 +590,7 @@ bool Session::processMessage(const Message& message)
break;
case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false);
break;
}

Datei anzeigen

@ -0,0 +1,192 @@
/**
* server/test/hardware/outputmap.cpp
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2024 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 <catch2/catch.hpp>
#include "../../src/core/objectproperty.tpp"
#include "../../src/world/world.hpp"
#include "../../src/hardware/interface/interfacelist.hpp"
#include "../../src/hardware/interface/ecosinterface.hpp"
#include "../../src/hardware/output/map/outputmappairoutputaction.hpp"
#include "../../src/hardware/output/map/outputmapecosstateoutputaction.hpp"
#include "../../src/board/board.hpp"
#include "../../src/board/boardlist.hpp"
#include "../../src/board/tile/rail/signal/signal3aspectrailtile.hpp"
#include "../../src/board/tile/rail/turnout/turnoutleft45railtile.hpp"
TEST_CASE("OutputMap: Channel: Accessory -> ECoSObject -> Accessory", "[outputmap]")
{
// Create world:
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE(worldWeak.lock()->interfaces->length == 0);
REQUIRE(worldWeak.lock()->boards->length == 0);
// Create interface:
std::weak_ptr<ECoSInterface> interfaceWeak = std::dynamic_pointer_cast<ECoSInterface>(world->interfaces->create(ECoSInterface::classId));
REQUIRE_FALSE(interfaceWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 1);
// Create board:
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
// Create signal:
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, Signal3AspectRailTile::classId, false));
std::weak_ptr<Signal3AspectRailTile> signalWeak = std::dynamic_pointer_cast<Signal3AspectRailTile>(boardWeak.lock()->getTile({0, 0}));
std::weak_ptr<OutputMap> outputMapWeak = signalWeak.lock()->outputMap.value();
REQUIRE_FALSE(signalWeak.expired());
REQUIRE_FALSE(outputMapWeak.lock()->interface);
// Assign interface:
outputMapWeak.lock()->interface = interfaceWeak.lock();
REQUIRE(outputMapWeak.lock()->interface.value() == interfaceWeak.lock());
REQUIRE(isAccessory(outputMapWeak.lock()->channel.value()));
REQUIRE(outputMapWeak.lock()->addresses.size() == 1);
REQUIRE(outputMapWeak.lock()->ecosObject == 0);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 1);
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[0]));
}
// Add address:
outputMapWeak.lock()->addAddress();
REQUIRE(outputMapWeak.lock()->addresses.size() == 2);
REQUIRE(outputMapWeak.lock()->addresses[0] != outputMapWeak.lock()->addresses[1]);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 2);
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[0]));
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[1]));
}
// Change channel to ECoSObject:
outputMapWeak.lock()->channel = OutputChannel::ECoSObject;
REQUIRE(outputMapWeak.lock()->channel.value() == OutputChannel::ECoSObject);
REQUIRE(outputMapWeak.lock()->addresses.size() == 0);
REQUIRE(outputMapWeak.lock()->ecosObject == 0);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.empty());
}
// Select ECoS object:
outputMapWeak.lock()->ecosObject = 20000;
REQUIRE(outputMapWeak.lock()->ecosObject == 20000);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 1);
REQUIRE(std::dynamic_pointer_cast<OutputMapECoSStateOutputAction>(item->outputActions[0]));
}
// Change channel to AccessoryMotorola:
outputMapWeak.lock()->channel = OutputChannel::AccessoryMotorola;
REQUIRE(outputMapWeak.lock()->channel.value() == OutputChannel::AccessoryMotorola);
REQUIRE(outputMapWeak.lock()->addresses.size() == 1);
REQUIRE(outputMapWeak.lock()->ecosObject == 0);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 1);
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[0]));
}
// cleanup:
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(interfaceWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(signalWeak.expired());
REQUIRE(outputMapWeak.expired());
}
TEST_CASE("OutputMap: Channel: preserve mapping", "[outputmap]")
{
// Create world:
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE(worldWeak.lock()->interfaces->length == 0);
REQUIRE(worldWeak.lock()->boards->length == 0);
// Create interface:
std::weak_ptr<ECoSInterface> interfaceWeak = std::dynamic_pointer_cast<ECoSInterface>(world->interfaces->create(ECoSInterface::classId));
REQUIRE_FALSE(interfaceWeak.expired());
REQUIRE(worldWeak.lock()->interfaces->length == 1);
// Create board:
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
// Create turnout:
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, TurnoutLeft45RailTile::classId, false));
std::weak_ptr<TurnoutLeft45RailTile> turnoutWeak = std::dynamic_pointer_cast<TurnoutLeft45RailTile>(boardWeak.lock()->getTile({0, 0}));
std::weak_ptr<OutputMap> outputMapWeak = turnoutWeak.lock()->outputMap.value();
REQUIRE_FALSE(turnoutWeak.expired());
REQUIRE_FALSE(outputMapWeak.lock()->interface);
// Assign interface:
outputMapWeak.lock()->interface = interfaceWeak.lock();
REQUIRE(outputMapWeak.lock()->interface.value() == interfaceWeak.lock());
REQUIRE(outputMapWeak.lock()->channel.value() == OutputChannel::AccessoryDCC);
REQUIRE(outputMapWeak.lock()->addresses.size() == 1);
REQUIRE(outputMapWeak.lock()->ecosObject == 0);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 1);
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[0]));
}
// Assign output actions:
std::weak_ptr<OutputMapPairOutputAction> outputActionWeakA = std::dynamic_pointer_cast<OutputMapPairOutputAction>(outputMapWeak.lock()->items[0]->outputActions[0]);
std::weak_ptr<OutputMapPairOutputAction> outputActionWeakB = std::dynamic_pointer_cast<OutputMapPairOutputAction>(outputMapWeak.lock()->items[1]->outputActions[0]);
outputActionWeakA.lock()->action = PairOutputAction::First;
outputActionWeakB.lock()->action = PairOutputAction::Second;
REQUIRE(outputActionWeakA.lock()->action.value() == PairOutputAction::First);
REQUIRE(outputActionWeakB.lock()->action.value() == PairOutputAction::Second);
// Change channel to AccessoryMotorola:
outputMapWeak.lock()->channel = OutputChannel::AccessoryMotorola;
REQUIRE(outputMapWeak.lock()->channel.value() == OutputChannel::AccessoryMotorola);
REQUIRE_FALSE(outputActionWeakA.expired());
REQUIRE_FALSE(outputActionWeakB.expired());
REQUIRE(outputActionWeakA.lock() == std::dynamic_pointer_cast<OutputMapPairOutputAction>(outputMapWeak.lock()->items[0]->outputActions[0]));
REQUIRE(outputActionWeakB.lock() == std::dynamic_pointer_cast<OutputMapPairOutputAction>(outputMapWeak.lock()->items[1]->outputActions[0]));
REQUIRE(outputActionWeakA.lock()->action.value() == PairOutputAction::First);
REQUIRE(outputActionWeakB.lock()->action.value() == PairOutputAction::Second);
REQUIRE(outputMapWeak.lock()->addresses.size() == 1);
REQUIRE(outputMapWeak.lock()->addresses[0] == 1);
REQUIRE(outputMapWeak.lock()->ecosObject == 0);
for(const auto& item : outputMapWeak.lock()->items)
{
REQUIRE(item->outputActions.size() == 1);
REQUIRE(std::dynamic_pointer_cast<OutputMapPairOutputAction>(item->outputActions[0]));
}
// cleanup:
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(interfaceWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(turnoutWeak.expired());
REQUIRE(outputMapWeak.expired());
}

Datei anzeigen

@ -35,9 +35,10 @@ enum class OutputChannel : uint16_t
AccessoryMotorola = 4,
DCCext = 5, //!< DCCext, see RCN-213
Turnout = 6, //!< DCC-EX turnout
ECoSObject = 7, //!< ECoS switch object
};
TRAINTASTIC_ENUM(OutputChannel, "output_channel", 6,
TRAINTASTIC_ENUM(OutputChannel, "output_channel", 7,
{
{OutputChannel::Output, "output"},
{OutputChannel::Accessory, "accessory"},
@ -45,15 +46,17 @@ TRAINTASTIC_ENUM(OutputChannel, "output_channel", 6,
{OutputChannel::AccessoryMotorola, "accessory_motorola"},
{OutputChannel::DCCext, "dcc_ext"},
{OutputChannel::Turnout, "turnout"},
{OutputChannel::ECoSObject, "ecos_object"},
});
inline constexpr std::array<OutputChannel, 6> outputChannelValues{{
inline constexpr std::array<OutputChannel, 7> outputChannelValues{{
OutputChannel::Output,
OutputChannel::Accessory,
OutputChannel::AccessoryDCC,
OutputChannel::AccessoryMotorola,
OutputChannel::DCCext,
OutputChannel::Turnout,
OutputChannel::ECoSObject,
}};
constexpr bool isAccessory(OutputChannel value)

Datei anzeigen

@ -33,19 +33,22 @@ enum class OutputType : uint8_t
Single = 1,
Pair = 2,
Aspect = 3,
ECoSState = 4,
};
TRAINTASTIC_ENUM(OutputType, "output_type", 3,
TRAINTASTIC_ENUM(OutputType, "output_type", 4,
{
{OutputType::Single, "single"},
{OutputType::Pair, "pair"},
{OutputType::Aspect, "aspect"},
{OutputType::ECoSState, "ecos_state"}
});
inline constexpr std::array<OutputType, 3> outputTypeValues{{
inline constexpr std::array<OutputType, 4> outputTypeValues{{
OutputType::Single,
OutputType::Pair,
OutputType::Aspect,
OutputType::ECoSState,
}};
#endif

Datei anzeigen

@ -4466,5 +4466,25 @@
{
"term": "output_channel:accessory_motorola",
"definition": "Accessory (Motorola)"
},
{
"term": "output_channel:ecos_object",
"definition": "ECoS object"
},
{
"term": "output_map.signal:add_address",
"definition": "Add address"
},
{
"term": "output_map.signal:remove_address",
"definition": "Remove address"
},
{
"term": "output_map.signal:ecos_object",
"definition": "ECoS object"
},
{
"term": "output.ecos_object:state",
"definition": "State"
}
]