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

traintastic / traintastic / 26121453997

19 May 2026 07:52PM UTC coverage: 25.063% (-0.6%) from 25.624%
26121453997

Pull #221

github

web-flow
Merge 598936246 into 15e38bcf7
Pull Request #221: Xpressnet new messages

49 of 1129 new or added lines in 16 files covered. (4.34%)

782 existing lines in 21 files now uncovered.

8483 of 33847 relevant lines covered (25.06%)

172.88 hits per line

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

40.59
/server/src/hardware/interface/xpressnetinterface.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 "xpressnetinterface.hpp"
23
#include "../decoder/list/decoderlist.hpp"
24
#include "../input/input.hpp"
25
#include "../input/list/inputlist.hpp"
26
#include "../output/list/outputlist.hpp"
27
#include "../protocol/xpressnet/kernel.hpp"
28
#include "../protocol/xpressnet/settings.hpp"
29
#include "../protocol/xpressnet/messages.hpp"
30
#include "../protocol/xpressnet/iohandler/serialiohandler.hpp"
31
#include "../protocol/xpressnet/iohandler/simulationiohandler.hpp"
32
#include "../protocol/xpressnet/iohandler/liusbiohandler.hpp"
33
#include "../protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp"
34
#include "../protocol/xpressnet/iohandler/tcpiohandler.hpp"
35
#include "../../core/attributes.hpp"
36
#include "../../core/eventloop.hpp"
37
#include "../../core/method.tpp"
38
#include "../../core/objectproperty.tpp"
39
#include "../../log/log.hpp"
40
#include "../../log/logmessageexception.hpp"
41
#include "../../utils/category.hpp"
42
#include "../../utils/displayname.hpp"
43
#include "../../utils/inrange.hpp"
44
#include "../../utils/makearray.hpp"
45
#include "../../world/world.hpp"
46

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

51
namespace {
52

NEW
53
inline XpressNet::Kernel::Locomotive::Flags getDecoderFlags(const Decoder &decoder)
×
54
{
NEW
55
  const uint8_t bothFuncFlags = (XpressNet::Kernel::Locomotive::Flags::HasF13F28 | XpressNet::Kernel::Locomotive::Flags::HasF29F68);
×
NEW
56
  uint8_t flags = XpressNet::Kernel::Locomotive::Flags::None;
×
NEW
57
  for(const auto& f : *decoder.functions)
×
58
  {
NEW
59
    if(f->number >= 29)
×
NEW
60
      flags |= XpressNet::Kernel::Locomotive::Flags::HasF29F68;
×
NEW
61
    else if(f->number >= 13)
×
NEW
62
      flags |= XpressNet::Kernel::Locomotive::Flags::HasF13F28;
×
63

NEW
64
    if((flags & bothFuncFlags) == bothFuncFlags)
×
NEW
65
      break;
×
66
  }
67

NEW
68
  return XpressNet::Kernel::Locomotive::Flags(flags);
×
69
}
70

71
}
72

73
CREATE_IMPL(XpressNetInterface)
9✔
74

75
XpressNetInterface::XpressNetInterface(World& world, std::string_view _id)
9✔
76
  : Interface(world, _id)
77
  , DecoderController(*this, decoderListColumns)
78
  , InputController(static_cast<IdObject&>(*this))
79
  , OutputController(static_cast<IdObject&>(*this))
80
  , type{this, "type", XpressNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
18✔
81
      [this](XpressNetInterfaceType /*value*/)
9✔
82
      {
83
        updateVisible();
×
84
      }}
×
85
  , serialInterfaceType{this, "interface", XpressNetSerialInterfaceType::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store,
18✔
86
      [this](XpressNetSerialInterfaceType value)
9✔
87
      {
88
        switch(value)
×
89
        {
90
          case XpressNetSerialInterfaceType::LenzLI100:
×
91
          case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
92
            baudrate.setValueInternal(9600);
×
93
            flowControl.setValueInternal(SerialFlowControl::Hardware);
×
94
            break;
×
95

96
          case XpressNetSerialInterfaceType::LenzLI100F:
×
97
          case XpressNetSerialInterfaceType::LenzLI101F:
98
            baudrate.setValueInternal(19200);
×
99
            flowControl.setValueInternal(SerialFlowControl::Hardware);
×
100
            break;
×
101

102
          case XpressNetSerialInterfaceType::LenzLIUSB:
×
103
            baudrate.setValueInternal(57600);
×
104
            flowControl.setValueInternal(SerialFlowControl::None);
×
105
            break;
×
106

107
          case XpressNetSerialInterfaceType::DigikeijsDR5000:
×
108
            baudrate.setValueInternal(115200);
×
109
            flowControl.setValueInternal(SerialFlowControl::None);
×
110
            break;
×
111
        }
112
        updateVisible();
×
113
      }}
×
114
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
18✔
115
  , baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
9✔
116
  , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
9✔
117
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
18✔
118
  , port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
9✔
119
  , s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
9✔
120
  , s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
9✔
121
  , xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
9✔
122
  , xbusVersion{this, "xbus_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
18✔
123
  , commandStationType{this, "command_station_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
36✔
124
{
125
  name = "XpressNet";
9✔
126
  xpressnet.setValueInternal(std::make_shared<XpressNet::Settings>(*this, xpressnet.name()));
9✔
127

128
  Attributes::addDisplayName(type, DisplayName::Interface::type);
9✔
129
  Attributes::addEnabled(type, !online);
9✔
130
  Attributes::addValues(type, xpressNetInterfaceTypeValues);
9✔
131
  m_interfaceItems.insertBefore(type, notes);
9✔
132

133
  Attributes::addValues(serialInterfaceType, XpressNetSerialInterfaceTypeValues);
9✔
134
  Attributes::addEnabled(serialInterfaceType, !online);
9✔
135
  Attributes::addVisible(serialInterfaceType, false);
9✔
136
  m_interfaceItems.insertBefore(serialInterfaceType, notes);
9✔
137

138
  Attributes::addEnabled(device, !online);
9✔
139
  Attributes::addVisible(device, false);
9✔
140
  m_interfaceItems.insertBefore(device, notes);
9✔
141

142
  Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
9✔
143
  Attributes::addEnabled(baudrate, !online);
9✔
144
  Attributes::addVisible(baudrate, false);
9✔
145
  m_interfaceItems.insertBefore(baudrate, notes);
9✔
146

147
  Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
9✔
148
  Attributes::addEnabled(flowControl, !online);
9✔
149
  Attributes::addValues(flowControl, SerialFlowControlValues);
9✔
150
  Attributes::addVisible(flowControl, false);
9✔
151
  m_interfaceItems.insertBefore(flowControl, notes);
9✔
152

153
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
9✔
154
  Attributes::addEnabled(hostname, !online);
9✔
155
  Attributes::addVisible(hostname, false);
9✔
156
  m_interfaceItems.insertBefore(hostname, notes);
9✔
157

158
  Attributes::addDisplayName(port, DisplayName::IP::port);
9✔
159
  Attributes::addEnabled(port, !online);
9✔
160
  Attributes::addVisible(port, false);
9✔
161
  m_interfaceItems.insertBefore(port, notes);
9✔
162

163
  Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax);
9✔
164
  Attributes::addEnabled(s88StartAddress, !online);
9✔
165
  Attributes::addVisible(s88StartAddress, false);
9✔
166
  m_interfaceItems.insertBefore(s88StartAddress, notes);
9✔
167

168
  Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax);
9✔
169
  Attributes::addEnabled(s88ModuleCount, !online);
9✔
170
  Attributes::addVisible(s88ModuleCount, false);
9✔
171
  m_interfaceItems.insertBefore(s88ModuleCount, notes);
9✔
172

173
  Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet);
9✔
174
  m_interfaceItems.insertBefore(xpressnet, notes);
9✔
175

176
  m_interfaceItems.insertBefore(decoders, notes);
9✔
177

178
  m_interfaceItems.insertBefore(inputs, notes);
9✔
179

180
  m_interfaceItems.insertBefore(outputs, notes);
9✔
181

182
  Attributes::addCategory(xbusVersion, Category::info);
9✔
183
  m_interfaceItems.insertBefore(xbusVersion, notes);
9✔
184

185
  Attributes::addCategory(commandStationType, Category::info);
9✔
186
  m_interfaceItems.insertBefore(commandStationType, notes);
9✔
187

188
  decoderAddedRemovedConn = std::static_pointer_cast<Object>(decoders.value())->propertyChanged.connect(
27✔
189
    [this](BaseProperty &)
18✔
190
    {
191
      updateKernelDecoderList();
10✔
192
    });
9✔
193

194
  updateVisible();
9✔
195
}
9✔
196

197
XpressNetInterface::~XpressNetInterface() = default;
9✔
198

199
std::span<const DecoderProtocol> XpressNetInterface::decoderProtocols() const
10✔
200
{
201
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
202
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
20✔
203
}
204

205
std::pair<uint16_t, uint16_t> XpressNetInterface::decoderAddressMinMax(DecoderProtocol protocol) const
5✔
206
{
207
  switch(protocol)
5✔
208
  {
209
    case DecoderProtocol::DCCShort:
5✔
210
      return {XpressNet::shortAddressMin, XpressNet::shortAddressMax};
5✔
211

212
    case DecoderProtocol::DCCLong:
×
213
      return {XpressNet::longAddressMin, XpressNet::longAddressMax};
×
214

215
    default: /*[[unlikely]]*/
×
216
      return DecoderController::decoderAddressMinMax(protocol);
×
217
  }
218
}
219

220
std::span<const uint8_t> XpressNetInterface::decoderSpeedSteps(DecoderProtocol protocol) const
5✔
221
{
222
  static constexpr std::array<uint8_t, 4> dccSpeedSteps{{14, 27, 28, 128}}; // XpressNet also support 27 steps
223

224
  switch(protocol)
5✔
225
  {
226
    case DecoderProtocol::DCCShort:
5✔
227
    case DecoderProtocol::DCCLong:
228
      return dccSpeedSteps;
5✔
229

230
    default: /*[[unlikely]]*/
×
231
      return DecoderController::decoderSpeedSteps(protocol);
×
232
  }
233
}
234

235
void XpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
4✔
236
{
237
  if(m_kernel)
4✔
238
    m_kernel->decoderChanged(decoder, changes, functionNumber);
×
239
}
4✔
240

241
void XpressNetInterface::decoderFunctionsChanged(const Decoder &decoder)
3✔
242
{
243
  if(!m_kernel)
3✔
244
    return;
3✔
245

NEW
246
  m_kernel->updateDecoder(decoder.address, 0, getDecoderFlags(decoder));
×
247
}
248

249
void XpressNetInterface::decoderAddressChanged(const Decoder &decoder, uint16_t oldAddress, uint16_t newAddress)
4✔
250
{
251
  if(!m_kernel)
4✔
252
    return;
4✔
253

NEW
254
  m_kernel->updateDecoder(oldAddress, newAddress, getDecoderFlags(decoder));
×
255
}
256

257
std::span<const InputChannel> XpressNetInterface::inputChannels() const
9✔
258
{
259
  static const auto values = makeArray(InputChannel::Input);
260
  return values;
9✔
261
}
262

263
std::pair<uint32_t, uint32_t> XpressNetInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
264
{
265
  return {XpressNet::Kernel::inputAddressMin, XpressNet::Kernel::inputAddressMax};
×
266
}
267

268
void XpressNetInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
269
{
270
  assert(std::holds_alternative<InputAddress>(location));
×
271
  const auto address = std::get<InputAddress>(location).address;
×
272
  if(m_kernel && inRange(address, inputAddressMinMax(channel)))
×
273
    m_kernel->simulateInputChange(address, action);
×
274
}
×
275

276
std::span<const OutputChannel> XpressNetInterface::outputChannels() const
20✔
277
{
278
  static const auto values = makeArray(OutputChannel::Accessory);
279
  return values;
20✔
280
}
281

282
std::pair<uint32_t, uint32_t> XpressNetInterface::outputAddressMinMax(OutputChannel /*channel*/) const
2✔
283
{
284
  return {XpressNet::Kernel::accessoryOutputAddressMin, XpressNet::Kernel::accessoryOutputAddressMax};
2✔
285
}
286

287
bool XpressNetInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
288
{
289
  assert(isOutputChannel(channel));
×
290
  const auto address = std::get<OutputAddress>(location).address;
×
NEW
291
  if(!m_kernel || !inRange(address, outputAddressMinMax(channel)))
×
NEW
292
    return false;
×
293

NEW
294
  if(m_kernel->getXBusVersion() < XpressNet::Kernel::XNet_3_8 && address > 1024)
×
295
  {
296
    // XBus only supports up to 1024 before V3.8
NEW
297
    Log::log(*this, LogMessage::W3005_XBUS_X_ACCESSORY_X_NOT_IN_1024, xbusVersion.value(), address);
×
NEW
298
    return false;
×
299
  }
NEW
300
  return m_kernel->setOutput(static_cast<uint16_t>(address), std::get<OutputPairValue>(value));
×
301
}
302

NEW
303
bool XpressNetInterface::send(std::vector<uint8_t> message)
×
304
{
NEW
305
  if(m_kernel)
×
306
  {
NEW
307
    return m_kernel->send(std::move(message));
×
308
  }
NEW
309
  return false;
×
310
}
311

312
bool XpressNetInterface::setOnline(bool& value, bool simulation)
×
313
{
314
  if(!m_kernel && value)
×
315
  {
316
    try
317
    {
318
      if(simulation)
×
319
      {
320
        m_kernel = XpressNet::Kernel::create<XpressNet::SimulationIOHandler>(id.value(), xpressnet->config());
×
321
      }
322
      else
323
      {
324
        switch(type)
×
325
        {
326
          case XpressNetInterfaceType::Serial:
×
327
            switch(serialInterfaceType)
×
328
            {
329
              case XpressNetSerialInterfaceType::LenzLI100:
×
330
              case XpressNetSerialInterfaceType::LenzLI100F:
331
              case XpressNetSerialInterfaceType::LenzLI101F:
332
                m_kernel = XpressNet::Kernel::create<XpressNet::SerialIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
×
333
                break;
×
334

335
              case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
×
336
                m_kernel = XpressNet::Kernel::create<XpressNet::RoSoftS88XPressNetLIIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value());
×
337
                break;
×
338

339
              case XpressNetSerialInterfaceType::LenzLIUSB:
×
340
              case XpressNetSerialInterfaceType::DigikeijsDR5000:
341
                m_kernel = XpressNet::Kernel::create<XpressNet::LIUSBIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
×
342
                break;
×
343
            }
344
            break;
×
345

346
          case XpressNetInterfaceType::Network:
×
347
            m_kernel = XpressNet::Kernel::create<XpressNet::TCPIOHandler>(id.value(), xpressnet->config(), hostname.value(), port.value());
×
348
            break;
×
349
        }
350
      }
351

352
      if(!m_kernel)
×
353
      {
354
        assert(false);
×
355
        return false;
356
      }
357

358
      setState(InterfaceState::Initializing);
×
359

360
      m_kernel->setOnStarted(
×
361
        [this]()
×
362
        {
363
          setState(InterfaceState::Online);
×
NEW
364
          updateKernelDecoderList();
×
365
        });
×
366
      m_kernel->setOnError(
×
367
        [this]()
×
368
        {
369
          setState(InterfaceState::Error);
×
370
          online = false; // communication no longer possible
×
371
        });
×
NEW
372
      m_kernel->setOnHardwareInfoChanged(
×
NEW
373
        [this](XpressNet::HardwareType hwType, uint8_t versionMajor, uint8_t versionMinor)
×
374
        {
NEW
375
          commandStationType.setValueInternal(std::string(XpressNet::toString(hwType)));
×
NEW
376
          Log::log(*this, LogMessage::I2002_HARDWARE_TYPE_X, commandStationType.value());
×
377

NEW
378
          if(versionMajor != 0)
×
379
          {
NEW
380
            xbusVersion.setValueInternal(std::to_string(versionMajor).append(".").append(std::to_string(versionMinor)));
×
NEW
381
            Log::log(*this, LogMessage::I2003_FIRMWARE_VERSION_X, xbusVersion.value());
×
382
          }
383
          else
NEW
384
            xbusVersion.setValueInternal("");
×
NEW
385
        });
×
386
      m_kernel->setOnTrackPowerChanged(
×
387
        [this](bool powerOn, bool isStopped)
×
388
        {
389
          if(powerOn)
×
390
          {
391
            /* NOTE:
392
             * Setting stop and powerOn together is not an atomic operation,
393
             * so it would trigger 2 state changes with in the middle state.
394
             * Fortunately this does not happen because at least one of the state is already set.
395
             * Because if we are in Run state we go to PowerOn,
396
             * and if we are on PowerOff then we go to PowerOn.
397
             */
398

399
            // First of all, stop if we have to, otherwhise we might inappropiately run trains
400
            if(isStopped && contains(m_world.state.value(), WorldState::Run))
×
401
            {
402
              m_world.stop();
×
403
            }
404
            else if(!contains(m_world.state.value(), WorldState::Run) && !isStopped)
×
405
            {
406
              m_world.run(); // Run trains yay!
×
407
            }
408

409
            // EmergencyStop in XpressNet also means power is still on
410
            if(!contains(m_world.state.value(), WorldState::PowerOn) && isStopped)
×
411
            {
412
              m_world.powerOn(); // Just power on but keep stopped
×
413
            }
414
          }
415
          else
416
          {
417
            // Power off regardless of stop state
418
            if(contains(m_world.state.value(), WorldState::PowerOn))
×
419
              m_world.powerOff();
×
420
          }
421
        });
×
422

423
      m_kernel->setDecoderController(this);
×
424
      m_kernel->setInputController(this);
×
425
      m_kernel->setOutputController(this);
×
426
      m_kernel->start();
×
427

428
      m_xpressnetPropertyChanged = xpressnet->propertyChanged.connect(
×
429
        [this](BaseProperty& /*property*/)
×
430
        {
431
          m_kernel->setConfig(xpressnet->config());
×
432
        });
×
433

434
      // Avoid to set multiple power states in rapid succession
435
      if(!contains(m_world.state.value(), WorldState::PowerOn))
×
436
        m_kernel->stopOperations(); // Stop by powering off
×
437
      else if(!contains(m_world.state.value(), WorldState::Run))
×
438
        m_kernel->stopAllLocomotives(); // Emergency stop with power on
×
439
      else
440
        m_kernel->resumeOperations(); // Run trains
×
441

442
      Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false);
×
443
    }
444
    catch(const LogMessageException& e)
×
445
    {
446
      setState(InterfaceState::Offline);
×
447
      Log::log(*this, e.message(), e.args());
×
448
      return false;
×
449
    }
×
450
  }
451
  else if(m_kernel && !value)
×
452
  {
453
    Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, true);
×
454

455
    m_xpressnetPropertyChanged.disconnect();
×
456

457
    m_kernel->stop();
×
458
    EventLoop::deleteLater(m_kernel.release());
×
459

460
    setState(InterfaceState::Offline);
×
NEW
461
    xbusVersion.setValueInternal("");
×
NEW
462
    commandStationType.setValueInternal("");
×
463
  }
464
  return true;
×
465
}
466

467
void XpressNetInterface::addToWorld()
9✔
468
{
469
  Interface::addToWorld();
9✔
470
  DecoderController::addToWorld();
9✔
471
  InputController::addToWorld(inputListColumns);
9✔
472
  OutputController::addToWorld(outputListColumns);
9✔
473
}
9✔
474

475
void XpressNetInterface::loaded()
×
476
{
477
  Interface::loaded();
×
478

479
  updateVisible();
×
480
}
×
481

482
void XpressNetInterface::destroying()
9✔
483
{
484
  OutputController::destroying();
9✔
485
  InputController::destroying();
9✔
486
  DecoderController::destroying();
9✔
487
  Interface::destroying();
9✔
488
}
9✔
489

490
void XpressNetInterface::worldEvent(WorldState state, WorldEvent event)
×
491
{
492
  Interface::worldEvent(state, event);
×
493

494
  if(m_kernel)
×
495
  {
496
    switch(event)
×
497
    {
498
      case WorldEvent::PowerOff:
×
499
      {
500
        m_kernel->stopOperations();
×
501
        break;
×
502
      }
503
      case WorldEvent::PowerOn:
×
504
      {
505
        if(contains(state, WorldState::Run))
×
506
          m_kernel->resumeOperations();
×
507
        else
508
          m_kernel->stopAllLocomotives(); // In XpressNet E-Stop means power on but not running
×
509
        break;
×
510
      }
511
      case WorldEvent::Stop:
×
512
      {
513
        if(contains(state, WorldState::PowerOn))
×
514
        {
515
          // In XpressNet E-Stop means power is on but trains are not running
516
          m_kernel->stopAllLocomotives();
×
517
        }
518
        else
519
        {
520
          // This Stops everything by removing power
521
          m_kernel->stopOperations();
×
522
        }
523
        break;
×
524
      }
525
      case WorldEvent::Run:
×
526
      {
527
        if(contains(state, WorldState::PowerOn))
×
528
          m_kernel->resumeOperations();
×
529
        break;
×
530
      }
531
      default:
×
532
        break;
×
533
    }
534
  }
535
}
×
536

537
void XpressNetInterface::updateVisible()
9✔
538
{
539
  const bool isSerial = (type == XpressNetInterfaceType::Serial);
9✔
540
  Attributes::setVisible(serialInterfaceType, isSerial);
9✔
541
  Attributes::setVisible(device, isSerial);
9✔
542
  Attributes::setVisible(baudrate, isSerial);
9✔
543
  Attributes::setVisible(flowControl, isSerial);
9✔
544

545
  const bool isNetwork = (type == XpressNetInterfaceType::Network);
9✔
546
  Attributes::setVisible(hostname, isNetwork);
9✔
547
  Attributes::setVisible(port, isNetwork);
9✔
548

549
  const bool isRoSoftS88XPressNetLI = isSerial && (serialInterfaceType == XpressNetSerialInterfaceType::RoSoftS88XPressNetLI);
9✔
550
  Attributes::setVisible(s88StartAddress, isRoSoftS88XPressNetLI);
9✔
551
  Attributes::setVisible(s88ModuleCount, isRoSoftS88XPressNetLI);
9✔
552
}
9✔
553

554
void XpressNetInterface::updateKernelDecoderList()
10✔
555
{
556
  if(!m_kernel)
10✔
557
    return;
10✔
558

NEW
559
  std::vector<XpressNet::Kernel::Locomotive> locoVec;
×
560

NEW
561
  for(const auto& decoder : *decoders.value().get())
×
562
  {
NEW
563
    XpressNet::Kernel::Locomotive loco;
×
NEW
564
    loco.address = decoder->address;
×
NEW
565
    loco.flags = getDecoderFlags(*decoder);
×
NEW
566
    locoVec.push_back(loco);
×
567
  }
568

NEW
569
  m_kernel->setDecoderList(locoVec);
×
NEW
570
}
×
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