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

traintastic / traintastic / 23088148108

14 Mar 2026 12:40PM UTC coverage: 26.661% (-0.2%) from 26.84%
23088148108

push

github

reinder
[cbus] added input support for short/long events

0 of 133 new or added lines in 4 files covered. (0.0%)

587 existing lines in 20 files now uncovered.

8246 of 30929 relevant lines covered (26.66%)

185.83 hits per line

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

39.37
/server/src/hardware/interface/dccexinterface.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2021-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 "dccexinterface.hpp"
23
#include "../decoder/list/decoderlist.hpp"
24
#include "../decoder/list/decoderlisttablemodel.hpp"
25
#include "../input/list/inputlist.hpp"
26
#include "../output/list/outputlist.hpp"
27
#include "../protocol/dcc/dcc.hpp"
28
#include "../protocol/dccex/kernel.hpp"
29
#include "../protocol/dccex/settings.hpp"
30
#include "../protocol/dccex/messages.hpp"
31
#include "../protocol/dccex/iohandler/serialiohandler.hpp"
32
#include "../protocol/dccex/iohandler/tcpiohandler.hpp"
33
#include "../protocol/dccex/iohandler/simulationiohandler.hpp"
34
#include "../../core/attributes.hpp"
35
#include "../../core/eventloop.hpp"
36
#include "../../core/method.tpp"
37
#include "../../core/objectproperty.tpp"
38
#include "../../log/log.hpp"
39
#include "../../log/logmessageexception.hpp"
40
#include "../../utils/displayname.hpp"
41
#include "../../utils/inrange.hpp"
42
#include "../../utils/makearray.hpp"
43
#include "../../utils/serialport.hpp"
44
#include "../../world/world.hpp"
45

46
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
47
constexpr auto inputListColumns = InputListColumn::Address;
48
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Address;
49

50
CREATE_IMPL(DCCEXInterface)
8✔
51

52
DCCEXInterface::DCCEXInterface(World& world, std::string_view _id)
8✔
53
  : Interface(world, _id)
54
  , DecoderController(*this, decoderListColumns)
55
  , InputController(static_cast<IdObject&>(*this))
56
  , OutputController(static_cast<IdObject&>(*this))
57
  , type{this, "type", DCCEXInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
16✔
58
      [this](DCCEXInterfaceType /*value*/)
8✔
59
      {
60
        updateVisible();
×
61
      }}
×
62
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
63
  , baudrate{this, "baudrate", 115200, PropertyFlags::ReadWrite | PropertyFlags::Store}
8✔
64
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
65
  , port{this, "port", 2560, PropertyFlags::ReadWrite | PropertyFlags::Store}
8✔
66
  , dccex{this, "dccex", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
16✔
67
{
68
  name = "DCC-EX";
8✔
69
  dccex.setValueInternal(std::make_shared<DCCEX::Settings>(*this, dccex.name()));
8✔
70

71
  Attributes::addDisplayName(type, DisplayName::Interface::type);
8✔
72
  Attributes::addEnabled(type, !online);
8✔
73
  Attributes::addValues(type, DCCEXInterfaceTypeValues);
8✔
74
  m_interfaceItems.insertBefore(type, notes);
8✔
75

76
  Attributes::addEnabled(device, !online);
8✔
77
  Attributes::addVisible(device, false);
8✔
78
  m_interfaceItems.insertBefore(device, notes);
8✔
79

80
  Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
8✔
81
  Attributes::addEnabled(baudrate, !online);
8✔
82
  Attributes::addVisible(baudrate, false);
8✔
83
  Attributes::addMinMax(baudrate, SerialPort::baudrateMin, SerialPort::baudrateMax);
8✔
84
  Attributes::addValues(baudrate, SerialPort::baudrateValues);
8✔
85
  m_interfaceItems.insertBefore(baudrate, notes);
8✔
86

87
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
8✔
88
  Attributes::addEnabled(hostname, !online);
8✔
89
  Attributes::addVisible(hostname, false);
8✔
90
  m_interfaceItems.insertBefore(hostname, notes);
8✔
91

92
  Attributes::addDisplayName(port, DisplayName::IP::port);
8✔
93
  Attributes::addEnabled(port, !online);
8✔
94
  Attributes::addVisible(port, false);
8✔
95
  m_interfaceItems.insertBefore(port, notes);
8✔
96

97
  Attributes::addDisplayName(dccex, DisplayName::Hardware::dccex);
8✔
98
  m_interfaceItems.insertBefore(dccex, notes);
8✔
99

100
  m_interfaceItems.insertBefore(decoders, notes);
8✔
101

102
  m_interfaceItems.insertBefore(inputs, notes);
8✔
103

104
  m_interfaceItems.insertBefore(outputs, notes);
8✔
105

106
  m_dccexPropertyChanged = dccex->propertyChanged.connect(
24✔
107
    [this](BaseProperty& property)
8✔
108
    {
109
      if(m_kernel && &property != &dccex->startupDelay)
×
110
        m_kernel->setConfig(dccex->config());
×
111

112
      if(&property == &dccex->speedSteps)
×
113
      {
114
        // update speedsteps of all decoders, DCC-EX only has a global speedsteps setting
115
        const auto values = decoderSpeedSteps(DecoderProtocol::DCCShort); // identical for DCCLong
×
116
        assert(values.size() == 1);
×
117
        for(const auto& decoder : *decoders)
×
118
        {
119
          Attributes::setValues(decoder->speedSteps, values);
×
120
          decoder->speedSteps.setValueInternal(values.front());
×
121
        }
122
      }
123
    });
8✔
124

125
  updateEnabled();
8✔
126
  updateVisible();
8✔
127
}
8✔
128

129
DCCEXInterface::~DCCEXInterface() = default;
8✔
130

131
std::span<const DecoderProtocol> DCCEXInterface::decoderProtocols() const
10✔
132
{
133
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
134
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
20✔
135
}
136

137
std::pair<uint16_t, uint16_t> DCCEXInterface::decoderAddressMinMax(DecoderProtocol protocol) const
5✔
138
{
139
  if(protocol == DecoderProtocol::DCCLong)
5✔
140
    return {DCC::addressLongStart, DCC::addressLongMax}; // DCC-EX considers all addresses below 128 as short.
×
141
  return DecoderController::decoderAddressMinMax(protocol);
5✔
142
}
143

144
std::span<const uint8_t> DCCEXInterface::decoderSpeedSteps(DecoderProtocol protocol) const
5✔
145
{
146
  (void)protocol; // silence unused warning for release build
147
  assert(protocol == DecoderProtocol::DCCShort || protocol == DecoderProtocol::DCCLong);
5✔
148
  const auto& speedStepValues = DCCEX::Settings::speedStepValues;
5✔
149
  // find value in array so we can create a span, using a span of a variable won't work due to the compare with prevous value in the attribute setter
150
  if(const auto it = std::find(speedStepValues.begin(), speedStepValues.end(), dccex->speedSteps); it != speedStepValues.end()) /*[[likely]]/*/ // NOLINT(readability-qualified-auto) windows requires const auto
5✔
151
    return {&(*it), 1};
5✔
152
  assert(false);
×
153
  return {};
154
}
155

156
void DCCEXInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
4✔
157
{
158
  if(m_kernel)
4✔
159
    m_kernel->decoderChanged(decoder, changes, functionNumber);
×
160
}
4✔
161

162
std::span<const InputChannel> DCCEXInterface::inputChannels() const
8✔
163
{
164
  static const auto values = makeArray(InputChannel::Input);
165
  return values;
8✔
166
}
167

168
std::pair<uint32_t, uint32_t> DCCEXInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
169
{
170
  return {DCCEX::Kernel::idMin, DCCEX::Kernel::idMax};
×
171
}
172

173
void DCCEXInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
174
{
175
  assert(std::holds_alternative<InputAddress>(location));
×
176
  const auto address = std::get<InputAddress>(location).address;
×
177
  if(m_kernel && inRange(address, inputAddressMinMax(channel)))
×
UNCOV
178
    m_kernel->simulateInputChange(address, action);
×
UNCOV
179
}
×
180

181
std::span<const OutputChannel> DCCEXInterface::outputChannels() const
40✔
182
{
183
  static const auto values = makeArray(OutputChannel::Accessory, OutputChannel::Turnout, OutputChannel::Output, OutputChannel::DCCext);
184
  return values;
40✔
185
}
186

UNCOV
187
std::pair<uint32_t, uint32_t> DCCEXInterface::outputAddressMinMax(OutputChannel channel) const
×
188
{
189
  using namespace DCCEX;
190

191
  switch(channel)
×
192
  {
UNCOV
193
    case OutputChannel::Accessory:
×
194
      return OutputController::outputAddressMinMax(OutputChannel::AccessoryDCC);
×
195

196
    case OutputChannel::Turnout:
×
197
    case OutputChannel::Output:
198
      return {Kernel::idMin, Kernel::idMax};
×
199

UNCOV
200
    default:
×
UNCOV
201
      return OutputController::outputAddressMinMax(channel);
×
202
  }
203
}
204

205
bool DCCEXInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
206
{
207
  const auto address = std::get<OutputAddress>(location).address;
×
208
  return
209
    m_kernel &&
×
UNCOV
210
    inRange(address, outputAddressMinMax(channel)) &&
×
UNCOV
211
    m_kernel->setOutput(channel, static_cast<uint16_t>(address), value);
×
212
}
213

214
bool DCCEXInterface::setOnline(bool& value, bool simulation)
×
215
{
UNCOV
216
  if(!m_kernel && value)
×
217
  {
218
    try
219
    {
220
      if(simulation)
×
221
      {
UNCOV
222
        m_kernel = DCCEX::Kernel::create<DCCEX::SimulationIOHandler>(id.value(), dccex->config());
×
223
      }
224
      else
225
      {
226
        switch(type)
×
227
        {
228
          case DCCEXInterfaceType::Serial:
×
UNCOV
229
            m_kernel = DCCEX::Kernel::create<DCCEX::SerialIOHandler>(id.value(), dccex->config(), device.value(), baudrate.value(), SerialFlowControl::None);
×
230
            break;
×
231

232
          case DCCEXInterfaceType::NetworkTCP:
×
UNCOV
233
            m_kernel = DCCEX::Kernel::create<DCCEX::TCPIOHandler>(id.value(), dccex->config(), hostname.value(), port.value());
×
UNCOV
234
            break;
×
235
        }
236

237
      }
238

239
      setState(InterfaceState::Initializing);
×
240

UNCOV
241
      m_kernel->setOnStarted(
×
242
        [this]()
×
243
        {
244
          setState(InterfaceState::Online);
×
245

246
          const bool powerOn = contains(m_world.state.value(), WorldState::PowerOn);
×
247

UNCOV
248
          if(!powerOn)
×
249
            m_kernel->powerOff();
×
250

251
          if(contains(m_world.state.value(), WorldState::Run))
×
252
          {
UNCOV
253
            m_kernel->clearEmergencyStop();
×
UNCOV
254
            restoreDecoderSpeed();
×
255
          }
256
          else
257
            m_kernel->emergencyStop();
×
258

259
          if(powerOn)
×
260
            m_kernel->powerOn();
×
261
        });
×
UNCOV
262
      m_kernel->setOnError(
×
263
        [this]()
×
264
        {
265
          setState(InterfaceState::Error);
×
266
          online = false; // communication no longer possible
×
267
        });
×
UNCOV
268
      m_kernel->setOnPowerOnChanged(
×
269
        [this](bool powerOn)
×
270
        {
271
          if(powerOn && !contains(m_world.state.value(), WorldState::PowerOn))
×
272
            m_world.powerOn();
×
273
          else if(!powerOn && contains(m_world.state.value(), WorldState::PowerOn))
×
274
            m_world.powerOff();
×
275
        });
×
276
      m_kernel->setDecoderController(this);
×
277
      m_kernel->setInputController(this);
×
UNCOV
278
      m_kernel->setOutputController(this);
×
279
      m_kernel->start();
×
280

281
      Attributes::setEnabled({type, device, baudrate, hostname, port}, false);
×
282
    }
283
    catch(const LogMessageException& e)
×
284
    {
285
      setState(InterfaceState::Offline);
×
286
      Log::log(*this, e.message(), e.args());
×
UNCOV
287
      return false;
×
288
    }
×
289
  }
290
  else if(m_kernel && !value)
×
291
  {
292
    Attributes::setEnabled({type, device, baudrate, hostname, port}, true);
×
293

UNCOV
294
    m_kernel->stop();
×
295
    EventLoop::deleteLater(m_kernel.release());
×
296

297
    if(status->state != InterfaceState::Error)
×
298
    {
UNCOV
299
      setState(InterfaceState::Offline);
×
300
    }
301
  }
UNCOV
302
  return true;
×
303
}
304

305
void DCCEXInterface::addToWorld()
8✔
306
{
307
  Interface::addToWorld();
8✔
308
  DecoderController::addToWorld();
8✔
309
  InputController::addToWorld(inputListColumns);
8✔
310
  OutputController::addToWorld(outputListColumns);
8✔
311
}
8✔
312

313
void DCCEXInterface::loaded()
×
314
{
315
  Interface::loaded();
×
316

317
  check();
×
318
  updateEnabled();
×
UNCOV
319
  updateVisible();
×
UNCOV
320
}
×
321

322
void DCCEXInterface::destroying()
8✔
323
{
324
  m_dccexPropertyChanged.disconnect();
8✔
325
  OutputController::destroying();
8✔
326
  InputController::destroying();
8✔
327
  DecoderController::destroying();
8✔
328
  Interface::destroying();
8✔
329
}
8✔
330

331
void DCCEXInterface::worldEvent(WorldState state, WorldEvent event)
×
332
{
333
  Interface::worldEvent(state, event);
×
334

335
  switch(event)
×
336
  {
337
    case WorldEvent::EditEnabled:
×
338
    case WorldEvent::EditDisabled:
UNCOV
339
      updateEnabled();
×
340
      break;
×
341

UNCOV
342
    case WorldEvent::PowerOff:
×
343
      if(m_kernel)
×
344
      {
UNCOV
345
        m_kernel->powerOff();
×
346
        m_kernel->emergencyStop();
×
347
      }
348
      break;
×
349

UNCOV
350
    case WorldEvent::PowerOn:
×
351
      if(m_kernel)
×
352
      {
353
        m_kernel->powerOn();
×
354
      }
355
      break;
×
356

UNCOV
357
    case WorldEvent::Stop:
×
358
      if(m_kernel)
×
359
      {
360
        m_kernel->emergencyStop();
×
361
      }
UNCOV
362
      updateEnabled();
×
363
      break;
×
364

UNCOV
365
    case WorldEvent::Run:
×
366
      if(m_kernel)
×
367
      {
UNCOV
368
        m_kernel->powerOn();
×
369
        m_kernel->clearEmergencyStop();
×
370
      }
371
      restoreDecoderSpeed();
×
UNCOV
372
      updateEnabled();
×
373
      break;
×
374

UNCOV
375
    default:
×
376
      break;
×
377
  }
378
}
×
379

380
void DCCEXInterface::check() const
×
381
{
382
  for(const auto& decoder : *decoders)
×
UNCOV
383
    checkDecoder(*decoder);
×
384
}
×
385

386
void DCCEXInterface::checkDecoder(const Decoder& decoder)
×
387
{
UNCOV
388
  for(const auto& function : *decoder.functions)
×
389
    if(function->number > DCCEX::Config::functionNumberMax)
×
390
    {
UNCOV
391
      Log::log(decoder, LogMessage::W2002_COMMAND_STATION_DOESNT_SUPPORT_FUNCTIONS_ABOVE_FX, DCCEX::Config::functionNumberMax);
×
392
      break;
×
393
    }
UNCOV
394
}
×
395

396
void DCCEXInterface::updateEnabled()
8✔
397
{
398
  const bool editable = contains(m_world.state, WorldState::Edit);
8✔
399
  const bool stopped = !contains(m_world.state, WorldState::Run);
8✔
400

401
  Attributes::setEnabled(dccex->speedSteps, editable && stopped);
8✔
402
}
8✔
403

404
void DCCEXInterface::updateVisible()
8✔
405
{
406
  const bool isSerial = (type == DCCEXInterfaceType::Serial);
8✔
407
  Attributes::setVisible(device, isSerial);
8✔
408
  Attributes::setVisible(baudrate, isSerial);
8✔
409

410
  const bool isNetwork = (type == DCCEXInterfaceType::NetworkTCP);
8✔
411
  Attributes::setVisible(hostname, isNetwork);
8✔
412
  Attributes::setVisible(port, isNetwork);
8✔
413
}
8✔
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