• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

traintastic / traintastic / 22823086659

08 Mar 2026 02:30PM UTC coverage: 26.831% (+0.06%) from 26.774%
22823086659

push

github

reinder
[cbus] renamed CBUSAccessory(Short) to Long/Short event and added node setting for Long events.

0 of 15 new or added lines in 2 files covered. (0.0%)

1909 existing lines in 38 files now uncovered.

8230 of 30674 relevant lines covered (26.83%)

186.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.36
/server/src/hardware/interface/z21interface.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2019-2026 Reinder Feenstra
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
 */
21

22
#include "z21interface.hpp"
23
#include "../decoder/list/decoderlist.hpp"
24
#include "../input/list/inputlist.hpp"
25
#include "../output/list/outputlist.hpp"
26
#include "../protocol/dcc/dcc.hpp"
27
#include "../protocol/z21/clientkernel.hpp"
28
#include "../protocol/z21/clientsettings.hpp"
29
#include "../protocol/z21/messages.hpp"
30
#include "../protocol/z21/iohandler/simulationiohandler.hpp"
31
#include "../protocol/z21/iohandler/udpclientiohandler.hpp"
32
#include "../../core/attributes.hpp"
33
#include "../../core/eventloop.hpp"
34
#include "../../core/method.tpp"
35
#include "../../core/objectproperty.tpp"
36
#include "../../log/log.hpp"
37
#include "../../log/logmessageexception.hpp"
38
#include "../../utils/category.hpp"
39
#include "../../utils/displayname.hpp"
40
#include "../../utils/inrange.hpp"
41
#include "../../utils/makearray.hpp"
42
#include "../../world/world.hpp"
43

44
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Protocol | DecoderListColumn::Address;
45
constexpr auto inputListColumns = InputListColumn::Channel | InputListColumn::Address;
46
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Address;
47

48
CREATE_IMPL(Z21Interface)
8✔
49

50
Z21Interface::Z21Interface(World& world, std::string_view _id)
8✔
51
  : Interface(world, _id)
52
  , DecoderController(*this, decoderListColumns)
53
  , InputController(static_cast<IdObject&>(*this))
54
  , OutputController(static_cast<IdObject&>(*this))
55
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
56
  , port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store}
8✔
57
  , z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
8✔
58
  , hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
16✔
59
  , serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
16✔
60
  , firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
32✔
61
{
62
  name = "Z21";
8✔
63
  z21.setValueInternal(std::make_shared<Z21::ClientSettings>(*this, z21.name()));
8✔
64

65
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
8✔
66
  Attributes::addEnabled(hostname, !online);
8✔
67
  m_interfaceItems.insertBefore(hostname, notes);
8✔
68

69
  Attributes::addDisplayName(port, DisplayName::IP::port);
8✔
70
  Attributes::addEnabled(port, !online);
8✔
71
  m_interfaceItems.insertBefore(port, notes);
8✔
72

73
  Attributes::addDisplayName(z21, DisplayName::Hardware::z21);
8✔
74
  m_interfaceItems.insertBefore(z21, notes);
8✔
75

76
  m_interfaceItems.insertBefore(decoders, notes);
8✔
77

78
  m_interfaceItems.insertBefore(inputs, notes);
8✔
79

80
  m_interfaceItems.insertBefore(outputs, notes);
8✔
81

82
  Attributes::addCategory(hardwareType, Category::info);
8✔
83
  m_interfaceItems.insertBefore(hardwareType, notes);
8✔
84

85
  Attributes::addCategory(serialNumber, Category::info);
8✔
86
  m_interfaceItems.insertBefore(serialNumber, notes);
8✔
87

88
  Attributes::addCategory(firmwareVersion, Category::info);
8✔
89
  m_interfaceItems.insertBefore(firmwareVersion, notes);
8✔
90
}
8✔
91

92
Z21Interface::~Z21Interface() = default;
8✔
93

94
std::span<const DecoderProtocol> Z21Interface::decoderProtocols() const
10✔
95
{
96
  static constexpr std::array<DecoderProtocol, 3> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong, DecoderProtocol::Motorola};
97
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
20✔
98
}
99

100
std::pair<uint16_t, uint16_t> Z21Interface::decoderAddressMinMax(DecoderProtocol protocol) const
5✔
101
{
102
  if(protocol == DecoderProtocol::DCCLong)
5✔
103
  {
UNCOV
104
    return {DCC::addressLongStart, DCC::addressLongMax};
×
105
  }
106
  return DecoderController::decoderAddressMinMax(protocol);
5✔
107
}
108

109
void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
4✔
110
{
111
  if(m_kernel)
4✔
UNCOV
112
    m_kernel->decoderChanged(decoder, changes, functionNumber);
×
113
}
4✔
114

115
std::span<const InputChannel> Z21Interface::inputChannels() const
8✔
116
{
117
  static const auto values = makeArray(InputChannel::RBus, InputChannel::LocoNet);
118
  return values;
8✔
119
}
120

UNCOV
121
std::pair<uint32_t, uint32_t> Z21Interface::inputAddressMinMax(InputChannel channel) const
×
122
{
123
  using namespace Z21;
124

UNCOV
125
  switch(channel)
×
126
  {
UNCOV
127
    case InputChannel::RBus:
×
128
      return {ClientKernel::rbusAddressMin, ClientKernel::rbusAddressMax};
×
129

UNCOV
130
    case InputChannel::LocoNet:
×
131
      return {ClientKernel::loconetAddressMin, ClientKernel::loconetAddressMax};
×
132

UNCOV
133
    default: [[unlikely]]
×
134
      assert(false);
×
135
      return {0, 0};
136
  }
137
}
138

UNCOV
139
void Z21Interface::inputSimulateChange(InputChannel channel, uint32_t address, SimulateInputAction action)
×
140
{
UNCOV
141
  if(m_kernel && inRange(address, inputAddressMinMax(channel)))
×
142
    m_kernel->simulateInputChange(channel, address, action);
×
143
}
×
144

145
std::span<const OutputChannel> Z21Interface::outputChannels() const
24✔
146
{
147
  static const auto values = makeArray(OutputChannel::Accessory, OutputChannel::DCCext);
148
  return values;
24✔
149
}
150

UNCOV
151
std::pair<uint32_t, uint32_t> Z21Interface::outputAddressMinMax(OutputChannel channel) const
×
152
{
UNCOV
153
  if(channel == OutputChannel::Accessory)
×
154
  {
UNCOV
155
    return {Z21::ClientKernel::outputAddressMin, Z21::ClientKernel::outputAddressMax};
×
156
  }
UNCOV
157
  return OutputController::outputAddressMinMax(channel);
×
158
}
159

UNCOV
160
bool Z21Interface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
161
{
UNCOV
162
  const auto address = std::get<OutputAddress>(location).address;
×
163
  return
164
      m_kernel &&
×
165
      inRange(address, outputAddressMinMax(channel)) &&
×
166
      m_kernel->setOutput(channel, static_cast<uint16_t>(address), value);
×
167
}
168

169
bool Z21Interface::setOnline(bool& value, bool simulation)
×
170
{
171
  if(!m_kernel && value)
×
172
  {
173
    try
174
    {
175
      if(simulation)
×
176
        m_kernel = Z21::ClientKernel::create<Z21::SimulationIOHandler>(id.value(), z21->config());
×
177
      else
178
        m_kernel = Z21::ClientKernel::create<Z21::UDPClientIOHandler>(id.value(), z21->config(), hostname.value(), port.value());
×
179

180
      setState(InterfaceState::Initializing);
×
181

182
      m_kernel->setOnStarted(
×
183
        [this]()
×
184
        {
185
          setState(InterfaceState::Online);
×
186
        });
×
187
      m_kernel->setOnError(
×
188
        [this]()
×
189
        {
190
          setState(InterfaceState::Error);
×
191
          online = false; // communication no longer possible
×
192
        });
×
193
      m_kernel->setOnSerialNumberChanged(
×
194
        [this](uint32_t newValue)
×
195
        {
196
          serialNumber.setValueInternal(std::to_string(newValue));
×
197
        });
×
198
      m_kernel->setOnHardwareInfoChanged(
×
199
        [this](Z21::HardwareType type, uint8_t versionMajor, uint8_t versionMinor)
×
200
        {
201
          hardwareType.setValueInternal(std::string(Z21::toString(type)));
×
202
          Log::log(*this, LogMessage::I2002_HARDWARE_TYPE_X, hardwareType.value());
×
203

204
          if(versionMajor != 0 || versionMinor != 0)
×
205
          {
206
            firmwareVersion.setValueInternal(std::to_string(versionMajor).append(".").append(std::to_string(versionMinor)));
×
207
            Log::log(*this, LogMessage::I2003_FIRMWARE_VERSION_X, firmwareVersion.value());
×
208
          }
209
          else
210
            firmwareVersion.setValueInternal("");
×
211
        });
×
212
      m_kernel->setOnTrackPowerChanged(
×
213
        [this](bool powerOn, bool isStopped)
×
214
        {
215
          if(powerOn)
×
216
          {
217
            /* NOTE:
218
             * Setting stop and powerOn together is not an atomic operation,
219
             * so it would trigger 2 state changes with in the middle state.
220
             * Fortunately this does not happen because at least one of the state is already set.
221
             * Because if we are in Run state we go to PowerOn,
222
             * and if we are on PowerOff then we go to PowerOn.
223
             */
224

225
            // First of all, stop if we have to, otherwhise we might inappropiately run trains
226
            if(isStopped && contains(m_world.state.value(), WorldState::Run))
×
227
            {
228
              m_world.stop();
×
229
            }
230
            else if(!contains(m_world.state.value(), WorldState::Run) && !isStopped)
×
231
            {
232
              m_world.run(); // Run trains yay!
×
233
            }
234

235
            // EmergencyStop in Z21 also means power is still on
236
            if(!contains(m_world.state.value(), WorldState::PowerOn) && isStopped)
×
237
            {
238
              m_world.powerOn(); // Just power on but keep stopped
×
239
            }
240
          }
241
          else
242
          {
243
            // Power off regardless of stop state
244
            if(contains(m_world.state.value(), WorldState::PowerOn))
×
245
              m_world.powerOff();
×
246
          }
247
        });
×
248

249
      m_kernel->setDecoderController(this);
×
250
      m_kernel->setInputController(this);
×
251
      m_kernel->setOutputController(this);
×
252

253
      m_kernel->start();
×
254

255
      m_z21PropertyChanged = z21->propertyChanged.connect(
×
256
        [this](BaseProperty& /*property*/)
×
257
        {
258
          m_kernel->setConfig(z21->config());
×
259
        });
×
260

261
      // Avoid to set multiple power states in rapid succession
262
      if(contains(m_world.state.value(), WorldState::PowerOn))
×
263
      {
264
        if(contains(m_world.state.value(), WorldState::Run))
×
265
          m_kernel->trackPowerOn(); // Run trains
×
266
        else
267
          m_kernel->emergencyStop(); // Emergency stop with power on
×
268
      }
269
      else
270
      {
271
        m_kernel->trackPowerOff(); // Stop by powering off
×
272
      }
273

274
      Attributes::setEnabled({hostname, port}, false);
×
275
    }
276
    catch(const LogMessageException& e)
×
277
    {
278
      setState(InterfaceState::Offline);
×
279
      Log::log(*this, e.message(), e.args());
×
280
      return false;
×
281
    }
×
282
  }
283
  else if(m_kernel && !value)
×
284
  {
285
    Attributes::setEnabled({hostname, port}, true);
×
286

287
    m_z21PropertyChanged.disconnect();
×
288

289
    m_kernel->stop();
×
290
    EventLoop::deleteLater(m_kernel.release());
×
291

292
    setState(InterfaceState::Offline);
×
293
    hardwareType.setValueInternal("");
×
294
    serialNumber.setValueInternal("");
×
295
    firmwareVersion.setValueInternal("");
×
296
  }
297
  return true;
×
298
}
299

300
void Z21Interface::addToWorld()
8✔
301
{
302
  Interface::addToWorld();
8✔
303
  DecoderController::addToWorld();
8✔
304
  InputController::addToWorld(inputListColumns);
8✔
305
  OutputController::addToWorld(outputListColumns);
8✔
306
}
8✔
307

308
void Z21Interface::destroying()
8✔
309
{
310
  OutputController::destroying();
8✔
311
  InputController::destroying();
8✔
312
  DecoderController::destroying();
8✔
313
  Interface::destroying();
8✔
314
}
8✔
315

316
void Z21Interface::worldEvent(WorldState state, WorldEvent event)
×
317
{
318
  Interface::worldEvent(state, event);
×
319

320
  if(m_kernel)
×
321
  {
322
    // Avoid to set multiple power states in rapid succession
323
    switch(event)
×
324
    {
325
      case WorldEvent::PowerOff:
×
326
      {
327
        m_kernel->trackPowerOff();
×
328
        break;
×
329
      }
330
      case WorldEvent::PowerOn:
×
331
      {
332
        if(contains(state, WorldState::Run))
×
333
          m_kernel->trackPowerOn();
×
334
        else
335
          m_kernel->emergencyStop(); // In Z21 E-Stop means power on but not running
×
336
        break;
×
337
      }
338
      case WorldEvent::Stop:
×
339
      {
340
        if(contains(state, WorldState::PowerOn))
×
341
        {
342
          // In Z21 E-Stop means power is on but trains are not running
343
          m_kernel->emergencyStop();
×
344
        }
345
        else
346
        {
347
          // This Stops everything by removing power
348
          m_kernel->trackPowerOff();
×
349
        }
350
        break;
×
351
      }
352
      case WorldEvent::Run:
×
353
      {
354
        if(contains(state, WorldState::PowerOn))
×
355
          m_kernel->trackPowerOn();
×
356
        break;
×
357
      }
358
      default:
×
359
        break;
×
360
    }
361
  }
362
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc