OutputController: Added support for ECoS object, see #95
Dieser Commit ist enthalten in:
Ursprung
64d64e909f
Commit
5f0d78ae14
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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++;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
3
manual/luadoc/object/addressoutput.json
Normale Datei
3
manual/luadoc/object/addressoutput.json
Normale Datei
@ -0,0 +1,3 @@
|
||||
{
|
||||
"address": {}
|
||||
}
|
||||
@ -2,6 +2,5 @@
|
||||
"name": {},
|
||||
"type": {},
|
||||
"interface": {},
|
||||
"channel": {},
|
||||
"address": {}
|
||||
"channel": {}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
30
server/src/hardware/output/addressoutput.cpp
Normale Datei
30
server/src/hardware/output/addressoutput.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
44
server/src/hardware/output/addressoutput.hpp
Normale Datei
44
server/src/hardware/output/addressoutput.hpp
Normale Datei
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
56
server/src/hardware/output/ecosstateoutput.cpp
Normale Datei
56
server/src/hardware/output/ecosstateoutput.cpp
Normale Datei
@ -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>());
|
||||
}
|
||||
53
server/src/hardware/output/ecosstateoutput.hpp
Normale Datei
53
server/src/hardware/output/ecosstateoutput.hpp
Normale Datei
@ -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
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
57
server/src/hardware/output/map/outputmapecosstateoutputaction.cpp
Normale Datei
57
server/src/hardware/output/map/outputmapecosstateoutputaction.cpp
Normale Datei
@ -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());
|
||||
}
|
||||
48
server/src/hardware/output/map/outputmapecosstateoutputaction.hpp
Normale Datei
48
server/src/hardware/output/map/outputmapecosstateoutputaction.hpp
Normale Datei
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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}
|
||||
{
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -590,6 +590,7 @@ bool Session::processMessage(const Message& message)
|
||||
break;
|
||||
|
||||
case OutputType::Aspect: /*[[unlikely]]*/
|
||||
case OutputType::ECoSState: /*[[unlikely]]*/
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
192
server/test/hardware/outputmap.cpp
Normale Datei
192
server/test/hardware/outputmap.cpp
Normale Datei
@ -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());
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren