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; break;
case OutputType::Aspect: /*[[unlikely]]*/ case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false); assert(false);
break; break;
} }
@ -118,6 +119,7 @@ void OutputKeyboard::created()
break; break;
case OutputType::Aspect: /*[[unlikely]]*/ case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false); assert(false);
break; break;
} }

Datei anzeigen

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

Datei anzeigen

@ -54,6 +54,7 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_object{std::move(object)} , m_object{std::move(object)}
, m_addresses{m_object->getVectorProperty("addresses")} , m_addresses{m_object->getVectorProperty("addresses")}
, m_ecosObject{dynamic_cast<Property*>(m_object->getProperty("ecos_object"))}
, m_items{m_object->getObjectVectorProperty("items")} , m_items{m_object->getObjectVectorProperty("items")}
, m_table{new QTableWidget(this)} , 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)); 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); 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); l->addLayout(form);
m_table->setColumnCount(columnCountNonOutput); m_table->setColumnCount(columnCountNonOutput);
@ -135,18 +141,25 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
void OutputMapWidget::updateTableOutputColumns() 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))));
}
} }
else if(m_ecosObject && m_ecosObject->getAttributeBool(AttributeName::Visible, true))
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->setColumnCount(columnCountNonOutput + 1);
m_table->setHorizontalHeaderItem(column, new QTableWidgetItem(QString("#%1").arg(m_addresses->getInt(i)))); 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)); 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++; column++;
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -58,6 +58,22 @@ struct Attributes
property.setAttribute(AttributeName::AliasValues, values); 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) static inline void addCategory(InterfaceItem& item, std::string_view value)
{ {
item.addAttribute(AttributeName::Category, value); item.addAttribute(AttributeName::Category, value);

Datei anzeigen

@ -30,6 +30,7 @@
#include "../protocol/ecos/messages.hpp" #include "../protocol/ecos/messages.hpp"
#include "../protocol/ecos/iohandler/tcpiohandler.hpp" #include "../protocol/ecos/iohandler/tcpiohandler.hpp"
#include "../protocol/ecos/iohandler/simulationiohandler.hpp" #include "../protocol/ecos/iohandler/simulationiohandler.hpp"
#include "../protocol/ecos/object/switch.hpp"
#include "../../core/attributes.hpp" #include "../../core/attributes.hpp"
#include "../../core/method.tpp" #include "../../core/method.tpp"
#include "../../core/objectproperty.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 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; 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 return
m_kernel && m_kernel &&
inRange(address, outputAddressMinMax(channel)) && isOutputId(channel, outputId) &&
m_kernel->setOutput(channel, static_cast<uint16_t>(address), std::get<OutputPairValue>(value)); m_kernel->setOutput(channel, outputId, value);
} }
bool ECoSInterface::setOnline(bool& value, bool simulation) 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)) if(!contains(m_world.state.value(), WorldState::Run))
m_world.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->setDecoderController(this);
m_kernel->setInputController(this); m_kernel->setInputController(this);
m_kernel->setOutputController(this); m_kernel->setOutputController(this);
@ -187,6 +238,10 @@ bool ECoSInterface::setOnline(bool& value, bool simulation)
m_kernel->setConfig(ecos->config()); m_kernel->setConfig(ecos->config());
}); });
// Reset output object list:
m_outputECoSObjectIds.assign({0});
m_outputECoSObjectNames.assign({{}});
Attributes::setEnabled(hostname, false); Attributes::setEnabled(hostname, false);
} }
catch(const LogMessageException& e) catch(const LogMessageException& e)

Datei anzeigen

@ -53,6 +53,8 @@ class ECoSInterface final
std::unique_ptr<ECoS::Kernel> m_kernel; std::unique_ptr<ECoS::Kernel> m_kernel;
boost::signals2::connection m_ecosPropertyChanged; boost::signals2::connection m_ecosPropertyChanged;
ECoS::Simulation m_simulation; ECoS::Simulation m_simulation;
std::vector<uint16_t> m_outputECoSObjectIds;
std::vector<std::string> m_outputECoSObjectNames;
void addToWorld() final; void addToWorld() final;
void destroying() final; void destroying() final;
@ -85,7 +87,9 @@ class ECoSInterface final
// OutputController: // OutputController:
tcb::span<const OutputChannel> outputChannels() const final; 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 #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" #include "../../utils/inrange.hpp"
AspectOutput::AspectOutput(std::shared_ptr<OutputController> outputController, OutputChannel channel_, uint32_t address_) 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} , value{this, "value", -1, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, setValue{*this, "set_value", MethodFlags::ScriptCallable, , setValue{*this, "set_value", MethodFlags::ScriptCallable,
[this](int16_t newValue) [this](int16_t newValue)

Datei anzeigen

@ -23,11 +23,11 @@
#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ASPECTOUTPUT_HPP #ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_ASPECTOUTPUT_HPP
#define 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/method.hpp"
#include "../../core/event.hpp" #include "../../core/event.hpp"
class AspectOutput final : public Output class AspectOutput final : public AddressOutput
{ {
friend class OutputController; 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) 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; return states;

Datei anzeigen

@ -30,7 +30,7 @@ SingleOutputKeyboard::SingleOutputKeyboard(OutputController& controller, OutputC
, setOutputValue(*this, "set_output_value", , setOutputValue(*this, "set_output_value",
[this](uint32_t address, bool 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) , outputValueChanged(*this, "output_value_changed", EventFlags::Public)
{ {
@ -45,7 +45,8 @@ std::vector<OutputKeyboard::OutputInfo> SingleOutputKeyboard::getOutputInfo() co
{ {
if(it.second->channel == channel) 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; return states;

Datei anzeigen

@ -22,6 +22,7 @@
#include "outputlisttablemodel.hpp" #include "outputlisttablemodel.hpp"
#include "outputlist.hpp" #include "outputlist.hpp"
#include "../addressoutput.hpp"
#include "../outputcontroller.hpp" #include "../outputcontroller.hpp"
#include "../../../core/objectproperty.tpp" #include "../../../core/objectproperty.tpp"
#include "../../../utils/displayname.hpp" #include "../../../utils/displayname.hpp"
@ -95,7 +96,11 @@ std::string OutputListTableModel::getText(uint32_t column, uint32_t row) const
break; break;
case OutputListColumn::Address: 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); assert(false);
} }

Datei anzeigen

@ -26,7 +26,9 @@
#include "outputmapsingleoutputaction.hpp" #include "outputmapsingleoutputaction.hpp"
#include "outputmappairoutputaction.hpp" #include "outputmappairoutputaction.hpp"
#include "outputmapaspectoutputaction.hpp" #include "outputmapaspectoutputaction.hpp"
#include "outputmapecosstateoutputaction.hpp"
#include "../outputcontroller.hpp" #include "../outputcontroller.hpp"
#include "../../interface/interface.hpp"
#include "../../../core/attributes.hpp" #include "../../../core/attributes.hpp"
#include "../../../core/method.tpp" #include "../../../core/method.tpp"
#include "../../../core/objectproperty.tpp" #include "../../../core/objectproperty.tpp"
@ -46,8 +48,19 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
}, },
[this](const std::shared_ptr<OutputController>& newValue) [this](const std::shared_ptr<OutputController>& newValue)
{ {
m_interfaceDestroying.disconnect();
if(newValue) if(newValue)
{ {
if(auto* object = dynamic_cast<Object*>(newValue.get())) /*[[likely]]*/
{
m_interfaceDestroying = object->onDestroying.connect(
[this](Object& /*object*/)
{
interface = nullptr;
});
}
if(!interface) if(!interface)
{ {
// No interface was assigned. // No interface was assigned.
@ -81,21 +94,36 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
{ {
assert(addresses.empty()); assert(addresses.empty());
assert(m_outputs.empty()); assert(m_outputs.empty());
const uint32_t address = newValue->getUnusedOutputAddress(channel);
addresses.appendInternal(address); switch(channel)
m_outputs.emplace_back(newValue->getOutput(channel, address, parent()));
const auto outputType = newValue->outputType(channel);
for(auto& item : items)
{ {
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 else // no interface
{ {
for(auto& output : m_outputs) for(auto& output : m_outputs)
{ {
if(output) if(output) /*[[likely]]*/
{ {
interface->releaseOutput(*output, parent()); interface->releaseOutput(*output, parent());
} }
@ -120,9 +148,28 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
} }
// Get outputs for current channel: // 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(); channelChanged();
@ -160,7 +207,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
return false; // Duplicate addresses aren't allowed. return false; // Duplicate addresses aren't allowed.
} }
auto output = interface->getOutput(channel, value, parent()); auto output = getOutput(channel, value, *interface);
if(!output) /*[[unlikely]]*/ if(!output) /*[[unlikely]]*/
{ {
return false; // Output doesn't exist. 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]) if(index < m_outputs.size() && m_outputs[index])
{ {
interface->releaseOutput(*m_outputs[index], parent()); releaseOutput(*m_outputs[index]);
} }
m_outputs[index] = output; m_outputs[index] = output;
return true; 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} , items{*this, "items", {}, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, addAddress{*this, "add_address", MethodFlags::NoScript, , addAddress{*this, "add_address", MethodFlags::NoScript,
[this]() [this]()
@ -189,7 +262,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
} }
const uint32_t address = getUnusedAddress(); const uint32_t address = getUnusedAddress();
addresses.appendInternal(address); addresses.appendInternal(address);
m_outputs.emplace_back(interface->getOutput(channel, address, parent())); addOutput(channel, address);
addressesSizeChanged(); addressesSizeChanged();
} }
}} }}
@ -201,7 +274,7 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
addresses.eraseInternal(addresses.size() - 1); addresses.eraseInternal(addresses.size() - 1);
if(m_outputs.back()) if(m_outputs.back())
{ {
interface->releaseOutput(*m_outputs.back(), parent()); releaseOutput(*m_outputs.back());
} }
m_outputs.pop_back(); m_outputs.pop_back();
addressesSizeChanged(); addressesSizeChanged();
@ -228,6 +301,12 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
Attributes::addMinMax<uint32_t>(addresses, 0, 0); Attributes::addMinMax<uint32_t>(addresses, 0, 0);
m_interfaceItems.add(addresses); 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); m_interfaceItems.add(items);
Attributes::addEnabled(addAddress, false); Attributes::addEnabled(addAddress, false);
@ -241,26 +320,41 @@ OutputMap::OutputMap(Object& _parent, std::string_view parentPropertyName)
updateEnabled(); updateEnabled();
} }
OutputMap::~OutputMap()
{
m_interfaceDestroying.disconnect();
m_outputECoSObjectsChanged.disconnect();
}
void OutputMap::load(WorldLoader& loader, const nlohmann::json& data) void OutputMap::load(WorldLoader& loader, const nlohmann::json& data)
{ {
SubObject::load(loader, data); SubObject::load(loader, data);
if(interface) 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); updateOutputActions();
const auto addressCount = addresses.size();
for(auto& item : items)
{
for(size_t i = 0; i < addressCount; i++)
{
item->outputActions.appendInternal(createOutputAction(outputType, i));
}
}
} }
} }
@ -282,11 +376,22 @@ void OutputMap::worldEvent(WorldState state, WorldEvent event)
void OutputMap::interfaceChanged() 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(channel, interface);
Attributes::setVisible(addresses, interface);
Attributes::setVisible(addAddress, interface); m_outputECoSObjectsChanged.disconnect();
Attributes::setVisible(removeAddress, interface);
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(); channelChanged();
} }
@ -295,29 +400,68 @@ void OutputMap::channelChanged()
{ {
if(interface) if(interface)
{ {
const auto addressRange = interface->outputAddressMinMax(channel); switch(channel.value())
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); case OutputChannel::Output:
} case OutputChannel::Accessory:
case OutputChannel::AccessoryDCC:
// Make sure all addresses are in range: case OutputChannel::AccessoryMotorola:
for(size_t i = 0; i < addresses.size(); i++) case OutputChannel::DCCext:
{ case OutputChannel::Turnout:
if(!inRange(addresses[i], addressRange))
{ {
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 else
{ {
Attributes::setVisible({addresses, addAddress, removeAddress, ecosObject}, false);
Attributes::setMinMax(addresses, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max()); 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); Attributes::setDisplayName(addresses, addresses.size() == 1 ? DisplayName::Hardware::address : DisplayName::Hardware::addresses);
assert(addresses.size() == m_outputs.size()); 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) 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()); std::shared_ptr<OutputMapOutputAction> outputAction = createOutputAction(outputType, item->outputActions.size());
assert(outputAction); assert(outputAction);
item->outputActions.appendInternal(outputAction); item->outputActions.appendInternal(outputAction);
} }
while(addresses.size() < item->outputActions.size()) while(m_outputs.size() < item->outputActions.size())
{ {
item->outputActions.back()->destroy(); item->outputActions.back()->destroy();
item->outputActions.removeInternal(item->outputActions.back()); item->outputActions.removeInternal(item->outputActions.back());
} }
assert(addresses.size() == item->outputActions.size()); assert(m_outputs.size() == item->outputActions.size());
} }
updateEnabled();
} }
void OutputMap::updateEnabled() void OutputMap::updateEnabled()
@ -357,6 +511,7 @@ void OutputMap::updateEnabled()
Attributes::setEnabled(addresses, editable); Attributes::setEnabled(addresses, editable);
Attributes::setEnabled(addAddress, editable && addresses.size() < addressesSizeMax); Attributes::setEnabled(addAddress, editable && addresses.size() < addressesSizeMax);
Attributes::setEnabled(removeAddress, editable && addresses.size() > addressesSizeMin); Attributes::setEnabled(removeAddress, editable && addresses.size() > addressesSizeMin);
Attributes::setEnabled(ecosObject, editable);
} }
uint32_t OutputMap::getUnusedAddress() const uint32_t OutputMap::getUnusedAddress() const
@ -390,7 +545,34 @@ std::shared_ptr<OutputMapOutputAction> OutputMap::createOutputAction(OutputType
case OutputType::Aspect: case OutputType::Aspect:
return std::make_shared<OutputMapAspectOutputAction>(*this, index); return std::make_shared<OutputMapAspectOutputAction>(*this, index);
case OutputType::ECoSState:
return std::make_shared<OutputMapECoSStateOutputAction>(*this, index);
} }
assert(false); assert(false);
return {}; 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 addressesSizeMin = 1;
static constexpr size_t addressesSizeMax = 8; 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: protected:
Outputs m_outputs; Outputs m_outputs;
@ -57,6 +65,8 @@ class OutputMap : public SubObject
void interfaceChanged(); void interfaceChanged();
void channelChanged(); void channelChanged();
void addressesSizeChanged(); void addressesSizeChanged();
void updateOutputActions();
void updateOutputActions(OutputType outputType);
void updateEnabled(); void updateEnabled();
uint32_t getUnusedAddress() const; uint32_t getUnusedAddress() const;
@ -66,11 +76,13 @@ class OutputMap : public SubObject
ObjectProperty<OutputController> interface; ObjectProperty<OutputController> interface;
Property<OutputChannel> channel; Property<OutputChannel> channel;
VectorProperty<uint32_t> addresses; VectorProperty<uint32_t> addresses;
Property<uint16_t> ecosObject;
ObjectVectorProperty<OutputMapItem> items; ObjectVectorProperty<OutputMapItem> items;
Method<void()> addAddress; Method<void()> addAddress;
Method<void()> removeAddress; Method<void()> removeAddress;
OutputMap(Object& _parent, std::string_view parentPropertyName); OutputMap(Object& _parent, std::string_view parentPropertyName);
~OutputMap() override;
const std::shared_ptr<Output>& output(size_t index) const 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 "../../log/log.hpp"
#include "../../utils/displayname.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} : interface{this, "interface", std::move(outputController), PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, channel{this, "channel", channel_, 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} , type{this, "type", type_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, address{this, "address", address_, PropertyFlags::Constant | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
{ {
m_interfaceItems.add(interface); m_interfaceItems.add(interface);
Attributes::addValues(channel, outputChannelValues); Attributes::addValues(channel, outputChannelValues);
m_interfaceItems.add(channel); m_interfaceItems.add(channel);
m_interfaceItems.add(address);
} }
std::string Output::getObjectId() const std::string Output::getObjectId() const

Datei anzeigen

@ -41,13 +41,10 @@ class Output : public Object
friend class OutputController; friend class OutputController;
private: 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. std::set<std::shared_ptr<Object>> m_usedBy; //!< Objects that use the output.
protected: 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: public:
static constexpr uint32_t invalidAddress = std::numeric_limits<uint32_t>::max(); static constexpr uint32_t invalidAddress = std::numeric_limits<uint32_t>::max();
@ -55,7 +52,13 @@ class Output : public Object
ObjectProperty<OutputController> interface; ObjectProperty<OutputController> interface;
Property<OutputChannel> channel; Property<OutputChannel> channel;
Property<OutputType> type; 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; std::string getObjectId() const final;
}; };

Datei anzeigen

@ -24,6 +24,7 @@
#include "singleoutput.hpp" #include "singleoutput.hpp"
#include "pairoutput.hpp" #include "pairoutput.hpp"
#include "aspectoutput.hpp" #include "aspectoutput.hpp"
#include "ecosstateoutput.hpp"
#include "list/outputlist.hpp" #include "list/outputlist.hpp"
#include "list/outputlisttablemodel.hpp" #include "list/outputlisttablemodel.hpp"
#include "keyboard/singleoutputkeyboard.hpp" #include "keyboard/singleoutputkeyboard.hpp"
@ -59,6 +60,9 @@ OutputType OutputController::outputType(OutputChannel channel) const
case OutputChannel::DCCext: case OutputChannel::DCCext:
return OutputType::Aspect; return OutputType::Aspect;
case OutputChannel::ECoSObject:
return OutputType::ECoSState;
} }
assert(false); assert(false);
return static_cast<OutputType>(0); return static_cast<OutputType>(0);
@ -80,11 +84,20 @@ std::pair<uint32_t, uint32_t> OutputController::outputAddressMinMax(OutputChanne
case OutputChannel::Accessory: case OutputChannel::Accessory:
case OutputChannel::Turnout: case OutputChannel::Turnout:
break; break;
case OutputChannel::ECoSObject:
return noAddressMinMax;
} }
assert(false); assert(false);
return {0, 0}; 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 bool OutputController::isOutputChannel(OutputChannel channel) const
{ {
const auto channels = outputChannels(); const auto channels = outputChannels();
@ -99,12 +112,31 @@ bool OutputController::isOutputChannel(OutputChannel channel) const
return false; 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)); assert(isOutputChannel(channel));
return return
inRange(address, outputAddressMinMax(channel)) && isOutputId(channel, id) &&
m_outputs.find({channel, address}) == m_outputs.end(); m_outputs.find({channel, id}) == m_outputs.end();
} }
uint32_t OutputController::getUnusedOutputAddress(OutputChannel channel) const 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++) for(uint32_t address = range.first; address < range.second; address++)
if(m_outputs.find({channel, address}) == end) if(m_outputs.find({channel, address}) == end)
return address; 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 {}; return {};
} }
// Check if already exists: // 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()); it->second->m_usedBy.emplace(usedBy.shared_from_this());
return it->second; return it->second;
@ -137,20 +169,24 @@ std::shared_ptr<Output> OutputController::getOutput(OutputChannel channel, uint3
switch(outputType(channel)) switch(outputType(channel))
{ {
case OutputType::Single: case OutputType::Single:
output = std::make_shared<SingleOutput>(shared_ptr(), channel, address); output = std::make_shared<SingleOutput>(shared_ptr(), channel, id);
break; break;
case OutputType::Pair: case OutputType::Pair:
output = std::make_shared<PairOutput>(shared_ptr(), channel, address); output = std::make_shared<PairOutput>(shared_ptr(), channel, id);
break; break;
case OutputType::Aspect: 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; break;
} }
assert(output); assert(output);
output->m_usedBy.emplace(usedBy.shared_from_this()); 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); outputs->addObject(output);
getWorld(outputs.object()).outputs->addObject(output); getWorld(outputs.object()).outputs->addObject(output);
return output; return output;
@ -162,7 +198,7 @@ void OutputController::releaseOutput(Output& output, Object& usedBy)
output.m_usedBy.erase(usedBy.shared_from_this()); output.m_usedBy.erase(usedBy.shared_from_this());
if(output.m_usedBy.empty()) 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); outputs->removeObject(outputShared);
getWorld(outputs.object()).outputs->removeObject(outputShared); getWorld(outputs.object()).outputs->removeObject(outputShared);
outputShared->destroy(); 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)); 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())) 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)); 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()) 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; return true;
case OutputType::Aspect: case OutputType::Aspect:
case OutputType::ECoSState:
return false; return false;
} }
assert(false); assert(false);
@ -228,6 +269,7 @@ std::shared_ptr<OutputKeyboard> OutputController::outputKeyboard(OutputChannel c
break; break;
case OutputType::Aspect: /*[[unlikely]]*/ case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
break; // not supported (yet) break; // not supported (yet)
} }
assert(keyboard); assert(keyboard);
@ -250,7 +292,8 @@ void OutputController::destroying()
{ {
const auto& output = outputs->front(); const auto& output = outputs->front();
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(object.shared_from_this())); 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())); object.world().outputControllers->remove(std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
} }

Datei anzeigen

@ -27,6 +27,7 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <memory> #include <memory>
#include <boost/signals2/signal.hpp>
#include <tcb/span.hpp> #include <tcb/span.hpp>
#include <traintastic/enum/outputchannel.hpp> #include <traintastic/enum/outputchannel.hpp>
#include <traintastic/enum/outputtype.hpp> #include <traintastic/enum/outputtype.hpp>
@ -46,14 +47,16 @@ enum class OutputListColumn;
class OutputController class OutputController
{ {
public: public:
static constexpr std::pair<uint32_t, uint32_t> noAddressMinMax{std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::min()};
struct OutputMapKey struct OutputMapKey
{ {
OutputChannel channel; OutputChannel channel;
uint32_t address; uint32_t id;
inline bool operator ==(const OutputMapKey other) const noexcept 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)); static_assert(sizeof(OutputMapKey) == sizeof(uint64_t));
@ -62,7 +65,7 @@ class OutputController
{ {
std::size_t operator()(const OutputMapKey& value) const noexcept 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(); void destroying();
public: public:
boost::signals2::signal<void()> outputECoSObjectsChanged;
ObjectProperty<OutputList> outputs; ObjectProperty<OutputList> outputs;
/** /**
@ -99,6 +104,11 @@ class OutputController
*/ */
bool isOutputChannel(OutputChannel channel) const; 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 * \brief Get the next unused output address
@ -125,18 +140,18 @@ class OutputController
/** /**
* \brief Get an output. * \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. * 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, * Once the object the uses the output is no longer using it,
* it must be released using \ref releaseOutput . * it must be released using \ref releaseOutput .
* The output object will be destroyed when the are zero users. * The output object will be destroyed when the are zero users.
* *
* \param[in] channel The output channel. * \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. * \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. * \brief Release an output.
@ -150,7 +165,7 @@ class OutputController
/** /**
* @brief ... * @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 * @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. * This function should be called by the hardware layer whenever the output value changes.
* *
* @param[in] channel Output channel * @param[in] channel Output channel
* @param[in] address Output address * @param[in] id Output id
* @param[in] value New output value * @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. * \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/tristate.hpp>
#include <traintastic/enum/outputpairvalue.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 #endif

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -23,12 +23,12 @@
#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_SINGLEOUTPUT_HPP #ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_SINGLEOUTPUT_HPP
#define 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/method.hpp"
#include "../../core/event.hpp" #include "../../core/event.hpp"
#include "../../enum/tristate.hpp" #include "../../enum/tristate.hpp"
class SingleOutput : public Output class SingleOutput : public AddressOutput
{ {
friend class OutputController; friend class OutputController;

Datei anzeigen

@ -22,6 +22,7 @@
#include "kernel.hpp" #include "kernel.hpp"
#include <algorithm> #include <algorithm>
#include <typeinfo>
#include "messages.hpp" #include "messages.hpp"
#include "simulation.hpp" #include "simulation.hpp"
#include "object/ecos.hpp" #include "object/ecos.hpp"
@ -104,6 +105,17 @@ void Kernel::setOnGo(std::function<void()> callback)
m_onGo = std::move(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) void Kernel::setDecoderController(DecoderController* decoderController)
{ {
assert(!m_started); 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) switch(channel)
{ {
case OutputChannel::AccessoryDCC: case OutputChannel::AccessoryDCC:
switchProtocol = SwitchProtocol::DCC;
break;
case OutputChannel::AccessoryMotorola: 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]]*/ default: /*[[unlikely]]*/
assert(false); assert(false);
return false; break;
} }
assert(switchProtocol != SwitchProtocol::Unknown); return false;
m_ioContext.post(
[this, switchProtocol, address, value]()
{
switchManager().setSwitch(switchProtocol, address, value == OutputPairValue::Second);
});
return true;
} }
void Kernel::simulateInputChange(uint32_t channel, uint32_t address, SimulateInputAction action) 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) void Kernel::feedbackStateChanged(Feedback& object, uint8_t port, TriState value)
{ {
if(!m_inputController) if(!m_inputController)
@ -563,6 +600,46 @@ void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
m_ioHandler = std::move(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) void Kernel::send(std::string_view message)
{ {
if(m_ioHandler->send(message)) if(m_ioHandler->send(message))

Datei anzeigen

@ -55,6 +55,9 @@ class Kernel : public ::KernelBase
friend class ECoS; friend class ECoS;
public: 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 s88AddressMin = 1;
static constexpr uint16_t s88AddressMax = 1000; //!< \todo what is the maximum static constexpr uint16_t s88AddressMax = 1000; //!< \todo what is the maximum
static constexpr uint16_t ecosDetectorAddressMin = 1; static constexpr uint16_t ecosDetectorAddressMin = 1;
@ -95,6 +98,8 @@ class Kernel : public ::KernelBase
std::function<void()> m_onGo; std::function<void()> m_onGo;
std::function<void()> m_onEmergencyStop; std::function<void()> m_onEmergencyStop;
OnObjectChanged m_onObjectChanged;
OnObjectRemoved m_onObjectRemoved;
DecoderController* m_decoderController; DecoderController* m_decoderController;
InputController* m_inputController; InputController* m_inputController;
@ -106,6 +111,12 @@ class Kernel : public ::KernelBase
void setIOHandler(std::unique_ptr<IOHandler> handler); 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(); ECoS& ecos();
void ecosGoChanged(TriState value); void ecosGoChanged(TriState value);
@ -183,6 +194,9 @@ class Kernel : public ::KernelBase
*/ */
void setOnGo(std::function<void()> callback); void setOnGo(std::function<void()> callback);
void setOnObjectChanged(OnObjectChanged callback);
void setOnObjectRemoved(OnObjectRemoved callback);
/** /**
* @brief Set the decoder controller * @brief Set the decoder controller
* @param[in] decoderController The decoder controller * @param[in] decoderController The decoder controller
@ -246,15 +260,16 @@ class Kernel : public ::KernelBase
/** /**
* @brief ... * @brief ...
* @param[in] channel Channel * @param[in] channel Channel
* @param[in] address Output address * @param[in] id Output id/address
* @param[in] value Output value * @param[in] value Output value
* @return \c true if send successful, \c false otherwise. * @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 simulateInputChange(uint32_t channel, uint32_t address, SimulateInputAction action);
void switchManagerSwitched(SwitchProtocol protocol, uint16_t address, OutputPairValue value); 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); 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 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) 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) 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) void Object::update(const std::vector<std::string_view>& lines)

Datei anzeigen

@ -48,6 +48,7 @@ class Object
bool objectExists(uint16_t objectId) const; bool objectExists(uint16_t objectId) const;
void addObject(std::unique_ptr<Object> object); void addObject(std::unique_ptr<Object> object);
void nameChanged();
void removeObject(uint16_t objectId); void removeObject(uint16_t objectId);
virtual void update(std::string_view /*option*/, std::string_view /*value*/){}// = 0; 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); 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) void Switch::setState(uint8_t value)
{ {
send(set(m_id, Option::state, 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) if(option == Option::name1)
{ {
m_name1 = value; m_name1 = value;
nameChanged();
} }
else if(option == Option::name2) 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) else if(option == Option::addr)
{ {
fromChars(value, m_address); fromChars(value, m_address);
nameChanged();
} }
else if(option == Option::addrext) 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) else if(option == Option::protocol)
{ {
fromString(value, m_protocol); fromString(value, m_protocol);
nameChanged();
} }
else if(option == Option::state) else if(option == Option::state)
{ {
fromChars(value, m_state); fromChars(value, m_state);
m_kernel.switchStateChanged(m_id, m_state);
if(m_state < m_addrext.size()) if(m_state < m_addrext.size())
{ {
const auto& port = m_addrext[m_state]; 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 receiveReply(const Reply& reply) final;
bool receiveEvent(const Event& event) final; bool receiveEvent(const Event& event) final;
std::string nameUI() const;
const std::string& name1() const { return m_name1; } const std::string& name1() const { return m_name1; }
const std::string& name2() const { return m_name2; } const std::string& name2() const { return m_name2; }
const std::string& name3() const { return m_name3; } const std::string& name3() const { return m_name3; }

Datei anzeigen

@ -373,7 +373,7 @@ void Kernel::receive(const Message& message)
[this]() [this]()
{ {
for(const auto& it : m_outputController->outputMap()) 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; 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))); auto outputController = std::dynamic_pointer_cast<::OutputController>(check<::Interface>(L, lua_upvalueindex(1)));
assert(outputController); assert(outputController);
auto channel = check<::OutputChannel>(L, 1); auto channel = check<::OutputChannel>(L, 1);
auto address = check<uint32_t>(L, 2); auto id = check<uint32_t>(L, 2);
auto& stateData = Lua::Sandbox::getStateData(L); auto& stateData = Lua::Sandbox::getStateData(L);
auto output = outputController->getOutput(channel, address, stateData.script()); auto output = outputController->getOutput(channel, id, stateData.script());
if(output) if(output)
{ {
stateData.registerOutput(outputController, output); stateData.registerOutput(outputController, output);

Datei anzeigen

@ -590,6 +590,7 @@ bool Session::processMessage(const Message& message)
break; break;
case OutputType::Aspect: /*[[unlikely]]*/ case OutputType::Aspect: /*[[unlikely]]*/
case OutputType::ECoSState: /*[[unlikely]]*/
assert(false); assert(false);
break; 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, AccessoryMotorola = 4,
DCCext = 5, //!< DCCext, see RCN-213 DCCext = 5, //!< DCCext, see RCN-213
Turnout = 6, //!< DCC-EX turnout 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::Output, "output"},
{OutputChannel::Accessory, "accessory"}, {OutputChannel::Accessory, "accessory"},
@ -45,15 +46,17 @@ TRAINTASTIC_ENUM(OutputChannel, "output_channel", 6,
{OutputChannel::AccessoryMotorola, "accessory_motorola"}, {OutputChannel::AccessoryMotorola, "accessory_motorola"},
{OutputChannel::DCCext, "dcc_ext"}, {OutputChannel::DCCext, "dcc_ext"},
{OutputChannel::Turnout, "turnout"}, {OutputChannel::Turnout, "turnout"},
{OutputChannel::ECoSObject, "ecos_object"},
}); });
inline constexpr std::array<OutputChannel, 6> outputChannelValues{{ inline constexpr std::array<OutputChannel, 7> outputChannelValues{{
OutputChannel::Output, OutputChannel::Output,
OutputChannel::Accessory, OutputChannel::Accessory,
OutputChannel::AccessoryDCC, OutputChannel::AccessoryDCC,
OutputChannel::AccessoryMotorola, OutputChannel::AccessoryMotorola,
OutputChannel::DCCext, OutputChannel::DCCext,
OutputChannel::Turnout, OutputChannel::Turnout,
OutputChannel::ECoSObject,
}}; }};
constexpr bool isAccessory(OutputChannel value) constexpr bool isAccessory(OutputChannel value)

Datei anzeigen

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

Datei anzeigen

@ -4466,5 +4466,25 @@
{ {
"term": "output_channel:accessory_motorola", "term": "output_channel:accessory_motorola",
"definition": "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"
} }
] ]