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

traintastic / traintastic / 23695207116

28 Mar 2026 09:57PM UTC coverage: 26.14% (-0.006%) from 26.146%
23695207116

push

github

reinder
[cbus] added mode and type to node list

0 of 7 new or added lines in 3 files covered. (0.0%)

4 existing lines in 1 file now uncovered.

8256 of 31584 relevant lines covered (26.14%)

182.15 hits per line

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

0.0
/server/src/hardware/interface/cbusinterface.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 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 "cbusinterface.hpp"
23
#include "cbus/cbusnodelist.hpp"
24
#include "cbus/cbussettings.hpp"
25
#include "../decoder/decoderchangeflags.hpp"
26
#include "../decoder/list/decoderlist.hpp"
27
#include "../decoder/list/decoderlisttablemodel.hpp"
28
#include "../input/list/inputlist.hpp"
29
#include "../output/list/outputlist.hpp"
30
#include "../protocol/cbus/cbusconst.hpp"
31
#include "../protocol/cbus/cbuskernel.hpp"
32
#include "../protocol/cbus/iohandler/cbuscanusbiohandler.hpp"
33
#include "../protocol/cbus/iohandler/cbuscanetheriohandler.hpp"
34
#include "../protocol/cbus/iohandler/cbussimulationiohandler.hpp"
35
#ifdef __linux__
36
  #include "../protocol/cbus/iohandler/cbussocketcaniohandler.hpp"
37
#endif
38
#include "../protocol/cbus/simulator/cbussimulator.hpp"
39
#include "../protocol/cbus/simulator/module/cbuscancmd.hpp"
40
#include "../../core/attributes.hpp"
41
#include "../../core/eventloop.hpp"
42
#include "../../core/method.tpp"
43
#include "../../core/objectproperty.tpp"
44
#include "../../log/log.hpp"
45
#include "../../log/logmessageexception.hpp"
46
#include "../../utils/displayname.hpp"
47
#include "../../utils/inrange.hpp"
48
#include "../../world/world.hpp"
49

50
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
51
constexpr auto inputListColumns = InputListColumn::Channel | InputListColumn::Node | InputListColumn::Address;
52
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Node | OutputListColumn::Address;
53

54
constexpr auto nodeNumberRange = std::make_pair(std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
55
constexpr auto eventNumberRange = std::make_pair(std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
56

57
CREATE_IMPL(CBUSInterface)
×
58

59
CBUSInterface::CBUSInterface(World& world, std::string_view _id)
×
60
  : Interface(world, _id)
61
  , DecoderController(*this, decoderListColumns)
62
  , InputController(static_cast<IdObject&>(*this))
63
  , OutputController(static_cast<IdObject&>(*this))
64
  , type{this, "type", CBUSInterfaceType::CANUSB, PropertyFlags::ReadWrite | PropertyFlags::Store,
×
65
      [this](CBUSInterfaceType /*value*/)
×
66
      {
67
        updateVisible();
×
68
      }}
×
69
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
70
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
71
  , port{this, "port", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
×
72
  , interface{this, "interface", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
73
  , canId{this, "can_id", 0x7D, PropertyFlags::ReadWrite | PropertyFlags::Store}
×
74
  , cbus{this, "cbus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
×
75
  , cbusNodeList{this, "cbus_node_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
×
76
{
77
  name = "CBUS/VLCB";
×
78
  cbus.setValueInternal(std::make_shared<CBUSSettings>(*this, cbus.name()));
×
79
  cbusNodeList.setValueInternal(std::make_shared<CBUSNodeList>(*this, cbusNodeList.name()));
×
80

81
  Attributes::addDisplayName(type, DisplayName::Interface::type);
×
82
  Attributes::addEnabled(type, !online);
×
83
  Attributes::addValues(type, CBUSInterfaceTypeValues);
×
84
  m_interfaceItems.insertBefore(type, notes);
×
85

86
  Attributes::addEnabled(device, !online);
×
87
  Attributes::addVisible(device, false);
×
88
  m_interfaceItems.insertBefore(device, notes);
×
89

90
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
×
91
  Attributes::addEnabled(hostname, !online);
×
92
  Attributes::addVisible(hostname, false);
×
93
  m_interfaceItems.insertBefore(hostname, notes);
×
94

95
  Attributes::addDisplayName(port, DisplayName::IP::port);
×
96
  Attributes::addEnabled(port, !online);
×
97
  Attributes::addVisible(port, false);
×
98
  m_interfaceItems.insertBefore(port, notes);
×
99

100
  Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
×
101
  Attributes::addEnabled(interface, !online);
×
102
  Attributes::addVisible(interface, false);
×
103
  m_interfaceItems.insertBefore(interface, notes);
×
104

105
  Attributes::addEnabled(canId, !online);
×
106
  Attributes::addMinMax(canId, CBUS::canIdMin, CBUS::canIdMax);
×
107
  Attributes::addVisible(canId, false);
×
108
  m_interfaceItems.insertBefore(canId, notes);
×
109

110
  m_interfaceItems.insertBefore(cbus, notes);
×
111

112
  m_interfaceItems.insertBefore(cbusNodeList, notes);
×
113

114
  m_interfaceItems.insertBefore(decoders, notes);
×
115

116
  m_interfaceItems.insertBefore(inputs, notes);
×
117

118
  m_interfaceItems.insertBefore(outputs, notes);
×
119

120
  m_cbusPropertyChanged = cbus->propertyChanged.connect(
×
121
    [this](BaseProperty& /*property*/)
×
122
    {
123
      if(m_kernel)
×
124
      {
125
        m_kernel->setConfig(cbus->config());
×
126
      }
127
    });
×
128

129
  updateVisible();
×
130
}
×
131

132
CBUSInterface::~CBUSInterface() = default;
×
133

134
bool CBUSInterface::send(std::vector<uint8_t> message)
×
135
{
136
  if(m_kernel)
×
137
  {
138
    return m_kernel->send(std::move(message));
×
139
  }
140
  return false;
×
141
}
142

143
bool CBUSInterface::sendDCC(std::vector<uint8_t> dccPacket, uint8_t repeat)
×
144
{
145
  if(m_kernel)
×
146
  {
147
    return m_kernel->sendDCC(std::move(dccPacket), repeat);
×
148
  }
149
  return false;
×
150
}
151

152
std::span<const DecoderProtocol> CBUSInterface::decoderProtocols() const
×
153
{
154
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
155
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
×
156
}
157

158
void CBUSInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
159
{
160
  if(m_kernel)
×
161
  {
162
    const bool longAddress = (decoder.protocol == DecoderProtocol::DCCLong);
×
163

164
    if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= CBUS::engineFunctionMax)
×
165
    {
166
      m_kernel->setEngineFunction(
×
167
        decoder.address,
168
        longAddress,
169
        static_cast<uint8_t>(functionNumber),
170
        decoder.getFunctionValue(functionNumber));
×
171
    }
172
    else
173
    {
174
      m_kernel->setEngineSpeedDirection(
×
175
        decoder.address,
176
        longAddress,
177
        Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, decoder.speedSteps),
×
178
        decoder.speedSteps,
179
        decoder.emergencyStop,
180
        decoder.direction == Direction::Forward);
×
181
    }
182
  }
183
}
×
184

185
std::span<const InputChannel> CBUSInterface::inputChannels() const
×
186
{
187
  static const std::array<InputChannel, 2> channels{
188
    InputChannel::ShortEvent,
189
    InputChannel::LongEvent,
190
  };
191
  return channels;
×
192
}
193

194
bool CBUSInterface::isInputLocation(InputChannel channel, const InputLocation& location) const
×
195
{
196
  if(hasNodeAddressLocation(channel))
×
197
  {
198
    return
199
      inRange<uint32_t>(std::get<InputNodeAddress>(location).node, nodeNumberRange) &&
×
200
      inRange<uint32_t>(std::get<InputNodeAddress>(location).address, inputAddressMinMax(channel));
×
201
  }
202
  return InputController::isInputLocation(channel, location);
×
203
}
204

205
std::pair<uint32_t, uint32_t> CBUSInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
206
{
207
  return eventNumberRange;
×
208
}
209

210
void CBUSInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
211
{
212
  if(m_simulator)
×
213
  {
214
    boost::asio::post(m_kernel->ioContext(),
×
215
      [this, channel, location, action]
×
216
      {
217
        switch(channel)
×
218
        {
219
          case InputChannel::ShortEvent:
×
220
            m_simulator->shortEvent(std::get<InputAddress>(location).address, action);
×
221
            break;
×
222

223
          case InputChannel::LongEvent:
×
224
            m_simulator->longEvent(std::get<InputNodeAddress>(location).node, std::get<InputNodeAddress>(location).address, action);
×
225
            break;
×
226

227
          default: [[unlikely]]
×
228
            assert(false);
×
229
            break;
230
        }
231
      });
×
232
  }
233
}
×
234

235
std::span<const OutputChannel> CBUSInterface::outputChannels() const
×
236
{
237
  static const std::array<OutputChannel, 4> channels{
238
    OutputChannel::ShortEvent,
239
    OutputChannel::LongEvent,
240
    OutputChannel::AccessoryDCC,
241
    OutputChannel::DCCext,
242
  };
243
  return channels;
×
244
}
245

246
std::pair<uint32_t, uint32_t> CBUSInterface::outputNodeMinMax(OutputChannel /*channel*/) const
×
247
{
248
  return eventNumberRange;
×
249
}
250

251
std::pair<uint32_t, uint32_t> CBUSInterface::outputAddressMinMax(OutputChannel channel) const
×
252
{
253
  switch(channel)
×
254
  {
255
    case OutputChannel::LongEvent:
×
256
    case OutputChannel::ShortEvent:
257
      return {std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()};
×
258

259
    default:
×
260
      return OutputController::outputAddressMinMax(channel);
×
261
  }
262
}
263

264
bool CBUSInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
265
{
266
  if(m_kernel)
×
267
  {
268
    switch(channel)
×
269
    {
270
      case OutputChannel::ShortEvent:
×
271
        assert(std::holds_alternative<TriState>(value));
×
272
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
273
        {
274
          assert(std::holds_alternative<OutputAddress>(location));
×
275
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
276
          m_kernel->setAccessoryShort(address, v == TriState::True);
×
277
          return true;
×
278
        }
279
        break;
×
280

281
      case OutputChannel::LongEvent:
×
282
        assert(std::holds_alternative<TriState>(value));
×
283
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
284
        {
285
          assert(std::holds_alternative<OutputNodeAddress>(location));
×
286
          const auto node = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).node);
×
287
          const auto address = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).address);
×
288
          m_kernel->setAccessory(node, address, v == TriState::True);
×
289
          return true;
×
290
        }
291
        break;
×
292

293
      case OutputChannel::AccessoryDCC:
×
294
        assert(std::holds_alternative<OutputPairValue>(value));
×
295
        if(const auto v = std::get<OutputPairValue>(value); v == OutputPairValue::First || v == OutputPairValue::Second)
×
296
        {
297
          assert(std::holds_alternative<OutputAddress>(location));
×
298
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
299
          m_kernel->setDccAccessory(address, v == OutputPairValue::Second);
×
300
          return true;
×
301
        }
302
        break;
×
303

304
      case OutputChannel::DCCext:
×
305
        assert(std::holds_alternative<int16_t>(value));
×
306
        if(const auto v = std::get<int16_t>(value); inRange<int16_t>(v, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()))
×
307
        {
308
          assert(std::holds_alternative<OutputAddress>(location));
×
309
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
310
          m_kernel->setDccAdvancedAccessoryValue(address, static_cast<uint8_t>(v));
×
311
          return true;
×
312
        }
313
        break;
×
314

315
      default: [[unlikely]]
×
316
        assert(false);
×
317
        break;
318
    }
319
  }
320
  return false;
×
321
}
322

323
void CBUSInterface::addToWorld()
×
324
{
325
  Interface::addToWorld();
×
326
  DecoderController::addToWorld();
×
327
  InputController::addToWorld(inputListColumns);
×
328
  OutputController::addToWorld(outputListColumns);
×
329
}
×
330

331
void CBUSInterface::loaded()
×
332
{
333
  Interface::loaded();
×
334

335
  updateVisible();
×
336
}
×
337

338
void CBUSInterface::destroying()
×
339
{
340
  m_cbusPropertyChanged.disconnect();
×
341
  OutputController::destroying();
×
342
  InputController::destroying();
×
343
  DecoderController::destroying();
×
344
  Interface::destroying();
×
345
}
×
346

347
void CBUSInterface::worldEvent(WorldState state, WorldEvent event)
×
348
{
349
  Interface::worldEvent(state, event);
×
350

351
  switch(event)
×
352
  {
353
    case WorldEvent::PowerOff:
×
354
      if(m_kernel)
×
355
      {
356
        m_kernel->trackOff();
×
357
      }
358
      break;
×
359

360
    case WorldEvent::PowerOn:
×
361
      if(m_kernel)
×
362
      {
363
        m_kernel->trackOn();
×
364
      }
365
      break;
×
366

367
    case WorldEvent::Stop:
×
368
      if(m_kernel)
×
369
      {
370
        m_kernel->requestEmergencyStop();
×
371
      }
372
      break;
×
373

374
    case WorldEvent::Run:
×
375
      if(m_kernel)
×
376
      {
377
        // TODO: send all known speed values
378
      }
379
      break;
×
380

381
    default:
×
382
      break;
×
383
  }
384
}
×
385

386
bool CBUSInterface::setOnline(bool& value, bool simulation)
×
387
{
388
  if(!m_kernel && value)
×
389
  {
390
    try
391
    {
392
      if(simulation)
×
393
      {
394
        m_simulator = std::make_unique<CBUS::Simulator>();
×
395
        m_simulator->addModule(std::make_unique<CBUS::Module::CANCMD>(*m_simulator));
×
396
        m_kernel = CBUS::Kernel::create<CBUS::SimulationIOHandler>(id.value(), cbus->config(), std::ref(*m_simulator));
×
397
      }
398
      else
399
      {
400
        switch(type)
×
401
        {
402
          case CBUSInterfaceType::CANUSB:
×
403
            m_kernel = CBUS::Kernel::create<CBUS::CANUSBIOHandler>(id.value(), cbus->config(), device.value());
×
404
            break;
×
405

406
          case CBUSInterfaceType::CANEther:
×
407
            m_kernel = CBUS::Kernel::create<CBUS::CANEtherIOHandler>(id.value(), cbus->config(), hostname.value(), port.value());
×
408
            break;
×
409

410
          case CBUSInterfaceType::SocketCAN:
×
411
#ifdef __linux__
412
            m_kernel = CBUS::Kernel::create<CBUS::SocketCANIOHandler>(id.value(), cbus->config(), interface.value(), canId.value());
×
413
            break;
×
414
#else
415
            setState(InterfaceState::Error);
416
            Log::log(*this, LogMessage::C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX);
417
            return false;
418
#endif
419
        }
420
      }
421

422
      setState(InterfaceState::Initializing);
×
423

424
      m_kernel->setOnStarted(
×
425
        [this]()
×
426
        {
427
          setState(InterfaceState::Online);
×
428
        });
×
429
      m_kernel->setOnError(
×
430
        [this]()
×
431
        {
432
          setState(InterfaceState::Error);
×
433
          online = false; // communication no longer possible
×
434
        });
×
435
      m_kernel->onPresenceOfNode =
×
NEW
436
        [this](uint8_t canId_, uint16_t nodeNumber, uint8_t manufacturerId, uint8_t moduleId, bool flimMode, bool supportsServiceDiscovery)
×
437
        {
438
          cbusNodeList->add(CBUSNodeList::Node{
×
439
            .nodeNumber = nodeNumber,
440
            .canId = canId_,
441
            .manufacturerId = manufacturerId,
442
            .moduleId = moduleId,
443
            .flim = flimMode,
444
            .vlcb = supportsServiceDiscovery,
445
            .parameters = {},
446
          });
447
        };
×
448
      m_kernel->onNodeParameterResponse =
×
449
        [this](uint8_t canId_, uint16_t nodeNumber, CBUS::NodeParameter parameter, uint8_t parameterValue)
×
450
        {
451
          auto& nodes = cbusNodeList->m_nodes;
×
452
          if(auto it = std::find_if(nodes.begin(), nodes.end(),
×
453
              [canId_, nodeNumber](const auto& item)
×
454
              {
455
                return item.canId == canId_ && item.nodeNumber == nodeNumber;
×
456
              }); it != nodes.end())
×
457
          {
458
            switch(parameter)
×
459
            {
460
              using enum CBUS::NodeParameter;
461

462
              case VersionMajor:
×
463
                it->parameters.versionMajor = parameterValue;
×
464
                break;
×
465

466
              case VersionMinor:
×
467
                it->parameters.versionMinor = parameterValue;
×
468
                break;
×
469

470
              case BetaReleaseCode:
×
471
                it->parameters.betaReleaseCode = parameterValue;
×
472
                break;
×
473

474
              default:
×
475
                return;
×
476
            }
477
            cbusNodeList->rowChanged(static_cast<uint32_t>(std::distance(nodes.begin(), it)));
×
478
          }
479
        };
×
480
      m_kernel->onTrackOff =
×
481
        [this]()
×
482
        {
483
          if(contains(m_world.state, WorldState::PowerOn))
×
484
          {
485
            m_world.powerOff();
×
486
          }
487
        };
×
488
      m_kernel->onTrackOn =
×
489
        [this]()
×
490
        {
491
          if(!contains(m_world.state, WorldState::PowerOn))
×
492
          {
493
            m_world.powerOn();
×
494
          }
495
        };
×
496
      m_kernel->onEmergencyStop =
×
497
        [this]()
×
498
        {
499
          if(contains(m_world.state, WorldState::Run))
×
500
          {
501
            m_world.stop();
×
502
          }
503
        };
×
504
      m_kernel->onShortEvent =
×
505
        [this](uint16_t eventNumber, bool on)
×
506
        {
507
          updateInputValue(InputChannel::ShortEvent, InputAddress(eventNumber), toTriState(on));
×
508
          updateOutputValue(OutputChannel::ShortEvent, OutputAddress(eventNumber), toTriState(on));
×
509
        };
×
510
      m_kernel->onLongEvent =
×
511
        [this](uint16_t nodeNumber, uint16_t eventNumber, bool on)
×
512
        {
513
          updateInputValue(InputChannel::LongEvent, InputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
514
          updateOutputValue(OutputChannel::LongEvent, OutputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
515
        };
×
516

517
      m_kernel->start();
×
518

519
      Attributes::setEnabled({type, device, hostname, port, interface, canId}, false);
×
520
    }
521
    catch(const LogMessageException& e)
×
522
    {
523
      setState(InterfaceState::Offline);
×
524
      Log::log(*this, e.message(), e.args());
×
525
      return false;
×
526
    }
×
527
  }
528
  else if(m_kernel && !value)
×
529
  {
530
    Attributes::setEnabled({type, device, hostname, port, interface, canId}, true);
×
531

532
    m_kernel->stop();
×
533
    EventLoop::deleteLater(m_kernel.release());
×
534
    EventLoop::deleteLater(m_simulator.release());
×
535

536
    cbusNodeList->clear();
×
537

538
    if(status->state != InterfaceState::Error)
×
539
    {
540
      setState(InterfaceState::Offline);
×
541
    }
542
  }
543
  return true;
×
544
}
545

546
void CBUSInterface::updateVisible()
×
547
{
548
  Attributes::setVisible(device, (type == CBUSInterfaceType::CANUSB));
×
549
  Attributes::setVisible({hostname, port}, (type == CBUSInterfaceType::CANEther));
×
550
  Attributes::setVisible({interface, canId}, (type == CBUSInterfaceType::SocketCAN));
×
551
}
×
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