From 260799d6dc9c58f013a46d1310ef30e43dd87c71 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Mon, 14 Feb 2022 22:34:51 +0100 Subject: [PATCH] z21: added output support --- .../src/hardware/interface/z21interface.cpp | 43 ++++++++++- .../src/hardware/interface/z21interface.hpp | 12 +++- .../hardware/protocol/z21/clientkernel.cpp | 14 ++++ .../hardware/protocol/z21/clientkernel.hpp | 11 +++ server/src/hardware/protocol/z21/messages.cpp | 18 +++++ server/src/hardware/protocol/z21/messages.hpp | 72 +++++++++++++++++++ 6 files changed, 168 insertions(+), 2 deletions(-) diff --git a/server/src/hardware/interface/z21interface.cpp b/server/src/hardware/interface/z21interface.cpp index e563b248..f0380821 100644 --- a/server/src/hardware/interface/z21interface.cpp +++ b/server/src/hardware/interface/z21interface.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,18 +33,22 @@ #include "../../utils/inrange.hpp" #include "../../world/world.hpp" +constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Address; + Z21Interface::Z21Interface(World& world, std::string_view _id) : Interface(world, _id) , hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store} , port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store} , z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} , hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} , serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} , firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} { z21.setValueInternal(std::make_shared(*this, z21.name())); decoders.setValueInternal(std::make_shared(*this, decoders.name())); + outputs.setValueInternal(std::make_shared(*this, outputs.name(), outputListColumns)); Attributes::addDisplayName(hostname, DisplayName::IP::hostname); Attributes::addEnabled(hostname, !online); @@ -60,6 +64,9 @@ Z21Interface::Z21Interface(World& world, std::string_view _id) Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); m_interfaceItems.insertBefore(decoders, notes); + Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs); + m_interfaceItems.insertBefore(outputs, notes); + Attributes::addCategory(hardwareType, Category::info); m_interfaceItems.insertBefore(hardwareType, notes); @@ -92,6 +99,30 @@ void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha m_kernel->decoderChanged(decoder, changes, functionNumber); } +bool Z21Interface::addOutput(Output& output) +{ + const bool success = OutputController::addOutput(output); + if(success) + outputs->addObject(output.shared_ptr()); + return success; +} + +bool Z21Interface::removeOutput(Output& output) +{ + const bool success = OutputController::removeOutput(output); + if(success) + outputs->removeObject(output.shared_ptr()); + return success; +} + +bool Z21Interface::setOutputValue(uint32_t channel, uint32_t address, bool value) +{ + return + m_kernel && + inRange(address, outputAddressMinMax(channel)) && + m_kernel->setOutput(static_cast(address), value); +} + bool Z21Interface::setOnline(bool& value, bool simulation) { if(!m_kernel && value) @@ -195,7 +226,9 @@ bool Z21Interface::setOnline(bool& value, bool simulation) void Z21Interface::addToWorld() { Interface::addToWorld(); + m_world.decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + m_world.outputControllers->add(std::dynamic_pointer_cast(shared_from_this())); } void Z21Interface::destroying() @@ -206,7 +239,15 @@ void Z21Interface::destroying() decoder->interface = nullptr; } + for(const auto& output : *outputs) + { + assert(output->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + output->interface = nullptr; + } + m_world.decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + m_world.outputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + Interface::destroying(); } diff --git a/server/src/hardware/interface/z21interface.hpp b/server/src/hardware/interface/z21interface.hpp index f10d8b36..01928082 100644 --- a/server/src/hardware/interface/z21interface.hpp +++ b/server/src/hardware/interface/z21interface.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +28,8 @@ #include "../protocol/z21/clientsettings.hpp" #include "../decoder/decodercontroller.hpp" #include "../decoder/decoderlist.hpp" +#include "../output/outputcontroller.hpp" +#include "../output/list/outputlist.hpp" #include "../../core/objectproperty.hpp" /** @@ -36,6 +38,7 @@ class Z21Interface final : public Interface , public DecoderController + , public OutputController { CLASS_ID("interface.z21") DEFAULT_ID("z21") @@ -61,6 +64,7 @@ class Z21Interface final Property port; ObjectProperty z21; ObjectProperty decoders; + ObjectProperty outputs; Property hardwareType; Property serialNumber; Property firmwareVersion; @@ -71,6 +75,12 @@ class Z21Interface final [[nodiscard]] bool addDecoder(Decoder& decoder) final; [[nodiscard]] bool removeDecoder(Decoder& decoder) final; void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; + + // OutputController: + std::pair outputAddressMinMax(uint32_t /*channel*/) const final { return {Z21::ClientKernel::outputAddressMin, Z21::ClientKernel::outputAddressMax}; } + [[nodiscard]] bool addOutput(Output& output) final; + [[nodiscard]] bool removeOutput(Output& output) final; + [[nodiscard]] bool setOutputValue(uint32_t channel, uint32_t address, bool value) final; }; #endif diff --git a/server/src/hardware/protocol/z21/clientkernel.cpp b/server/src/hardware/protocol/z21/clientkernel.cpp index 1a613c9a..f883d005 100644 --- a/server/src/hardware/protocol/z21/clientkernel.cpp +++ b/server/src/hardware/protocol/z21/clientkernel.cpp @@ -28,6 +28,7 @@ #include "../../input/inputcontroller.hpp" #include "../../../core/eventloop.hpp" #include "../../../log/log.hpp" +#include "../../../utils/inrange.hpp" namespace Z21 { @@ -279,6 +280,19 @@ void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha } } +bool ClientKernel::setOutput(uint16_t address, bool value) +{ + assert(inRange(address, outputAddressMin, outputAddressMax)); + + m_ioContext.post( + [this, address, value]() + { + send(LanXSetTurnout(address, value, true)); + }); + + return true; +} + void ClientKernel::onStart() { // reset all state values diff --git a/server/src/hardware/protocol/z21/clientkernel.hpp b/server/src/hardware/protocol/z21/clientkernel.hpp index d68a2cc9..2d248be5 100644 --- a/server/src/hardware/protocol/z21/clientkernel.hpp +++ b/server/src/hardware/protocol/z21/clientkernel.hpp @@ -72,6 +72,9 @@ class ClientKernel final : public Kernel void keepAliveTimerExpired(const boost::system::error_code& ec); public: + static constexpr uint32_t outputAddressMin = 1; + static constexpr uint32_t outputAddressMax = 4096; + /** * @brief Create kernel and IO handler * @param[in] config Z21 client configuration @@ -171,6 +174,14 @@ class ClientKernel final : public Kernel /** */ void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + /** + * + * @param[in] address Output address, \ref outputAddressMin .. \ref outputAddressMax + * @param[in] value Output value: \c true is on, \c false is off. + * @return \c true if send successful, \c false otherwise. + */ + bool setOutput(uint16_t address, bool value); }; } diff --git a/server/src/hardware/protocol/z21/messages.cpp b/server/src/hardware/protocol/z21/messages.cpp index c182815e..4d2bf199 100644 --- a/server/src/hardware/protocol/z21/messages.cpp +++ b/server/src/hardware/protocol/z21/messages.cpp @@ -89,6 +89,24 @@ std::string toString(const Message& message, bool raw) raw = true; break; + case 0x43: + { + const auto& getTurnoutInfo = static_cast(message); + s = "LAN_X_GET_TURNOUT_INFO"; + s.append(" address=").append(std::to_string(getTurnoutInfo.address())); + break; + } + case 0x53: + { + const auto& setTurnout = static_cast(message); + s = "LAN_X_SET_TURNOUT"; + s.append(" linear_address=").append(std::to_string(setTurnout.linearAddress())); + s.append(" address=").append(std::to_string(setTurnout.address())); + s.append(" port=").append(std::to_string(setTurnout.port())); + s.append(" activate=").append(setTurnout.activate() ? "yes" : "no"); + s.append(" queue=").append(setTurnout.queue() ? "yes" : "no"); + break; + } case 0x61: if(message == LanXBCTrackPowerOff()) s = "LAN_X_BC_TRACK_POWER_OFF"; diff --git a/server/src/hardware/protocol/z21/messages.hpp b/server/src/hardware/protocol/z21/messages.hpp index 1cc26450..4416e228 100644 --- a/server/src/hardware/protocol/z21/messages.hpp +++ b/server/src/hardware/protocol/z21/messages.hpp @@ -367,8 +367,80 @@ static_assert(sizeof(LanXSetTrackPowerOn) == 7); // LAN_X_MWRITE_BYTE // LAN_X_GET_TURNOUT_INFO +struct LanXGetTurnoutInfo : LanX +{ + uint8_t db0; + uint8_t db1; + uint8_t checksum; + + LanXGetTurnoutInfo(uint16_t address) + : LanX(sizeof(LanXGetTurnoutInfo), 0x43) + , db0(address >> 8) + , db1(address & 0xFF) + { + calcChecksum(); + } + + uint16_t address() const + { + return (static_cast(db0) << 8) | db1; + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXGetTurnoutInfo) == 8); // LAN_X_SET_TURNOUT +struct LanXSetTurnout : LanX +{ + static constexpr uint8_t db2Port = 0x01; + static constexpr uint8_t db2Activate = 0x08; + static constexpr uint8_t db2Queue = 0x20; + + uint8_t db0; + uint8_t db1; + uint8_t db2 = 0x80; + uint8_t checksum; + + LanXSetTurnout(uint16_t linearAddress, bool activate, bool queue = false) + : LanX(sizeof(LanXSetTurnout), 0x53) + , db0(linearAddress >> 9) + , db1((linearAddress >> 1) & 0xFF) + { + if(queue) + db2 |= db2Queue; + if(activate) + db2 |= db2Activate; + if(linearAddress & 0x0001) + db2 |= db2Port; + + calcChecksum(); + } + + uint16_t address() const + { + return (static_cast(db0) << 8) | db1; + } + + uint16_t linearAddress() const + { + return (address() << 1) | port(); + } + + bool activate() const + { + return db2 & db2Queue; + } + + bool queue() const + { + return db2 & db2Queue; + } + + uint8_t port() const + { + return db2 & db2Port; + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXSetTurnout) == 9); // LAN_X_SET_STOP struct LanXSetStop : LanX