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

traintastic / traintastic / 24288605255

11 Apr 2026 06:17PM UTC coverage: 25.599% (-2.4%) from 27.99%
24288605255

push

github

web-flow
Merge pull request #222 from traintastic/cbus

Added CBUS/VLCB hardware support

169 of 3369 new or added lines in 99 files covered. (5.02%)

5 existing lines in 4 files now uncovered.

8300 of 32423 relevant lines covered (25.6%)

178.31 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/cbussessionlist.hpp"
25
#include "cbus/cbussettings.hpp"
26
#include "../decoder/decoderchangeflags.hpp"
27
#include "../decoder/list/decoderlist.hpp"
28
#include "../decoder/list/decoderlisttablemodel.hpp"
29
#include "../input/input.hpp"
30
#include "../input/list/inputlist.hpp"
31
#include "../output/addressoutput.hpp"
32
#include "../output/list/outputlist.hpp"
33
#include "../protocol/cbus/cbusconst.hpp"
34
#include "../protocol/cbus/cbuskernel.hpp"
35
#include "../protocol/cbus/iohandler/cbuscanusbiohandler.hpp"
36
#include "../protocol/cbus/iohandler/cbuscanetheriohandler.hpp"
37
#include "../protocol/cbus/iohandler/cbussimulationiohandler.hpp"
38
#ifdef __linux__
39
  #include "../protocol/cbus/iohandler/cbussocketcaniohandler.hpp"
40
#endif
41
#include "../protocol/cbus/simulator/cbussimulator.hpp"
42
#include "../protocol/cbus/simulator/module/cbuscancab.hpp"
43
#include "../protocol/cbus/simulator/module/cbuscancmd.hpp"
44
#include "../../core/attributes.hpp"
45
#include "../../core/controllerlist.hpp"
46
#include "../../core/eventloop.hpp"
47
#include "../../core/method.tpp"
48
#include "../../core/objectproperty.tpp"
49
#include "../../log/log.hpp"
50
#include "../../log/logmessageexception.hpp"
51
#include "../../utils/displayname.hpp"
52
#include "../../utils/inrange.hpp"
53
#include "../../world/world.hpp"
54

55
namespace {
56

57
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
58
constexpr auto inputListColumns = InputListColumn::Channel | InputListColumn::Node | InputListColumn::Address;
59
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Node | OutputListColumn::Address;
60

61
constexpr auto nodeNumberRange = std::make_pair(std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
62
constexpr auto eventNumberRange = std::make_pair(std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max());
63

64
template<typename T>
NEW
65
void deduplicate(std::vector<T>& v)
×
66
{
NEW
67
  std::sort(v.begin(), v.end());
×
NEW
68
  v.erase(std::unique(v.begin(), v.end()), v.end());
×
NEW
69
}
×
70

71
}
72

NEW
73
CREATE_IMPL(CBUSInterface)
×
74

NEW
75
CBUSInterface::CBUSInterface(World& world, std::string_view _id)
×
76
  : Interface(world, _id)
77
  , DecoderController(*this, decoderListColumns)
78
  , InputController(static_cast<IdObject&>(*this))
79
  , OutputController(static_cast<IdObject&>(*this))
NEW
80
  , type{this, "type", CBUSInterfaceType::CANUSB, PropertyFlags::ReadWrite | PropertyFlags::Store,
×
NEW
81
      [this](CBUSInterfaceType /*value*/)
×
82
      {
NEW
83
        updateVisible();
×
NEW
84
      }}
×
NEW
85
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
NEW
86
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
NEW
87
  , port{this, "port", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
×
NEW
88
  , interface{this, "interface", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
×
NEW
89
  , canId{this, "can_id", 0x7D, PropertyFlags::ReadWrite | PropertyFlags::Store}
×
NEW
90
  , cbus{this, "cbus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
×
NEW
91
  , cbusNodeList{this, "cbus_node_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
×
NEW
92
  , cbusSessionList{this, "cbus_session_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
×
93
{
NEW
94
  name = "CBUS/VLCB";
×
NEW
95
  cbus.setValueInternal(std::make_shared<CBUSSettings>(*this, cbus.name()));
×
NEW
96
  cbusNodeList.setValueInternal(std::make_shared<CBUSNodeList>(*this, cbusNodeList.name()));
×
NEW
97
  cbusSessionList.setValueInternal(std::make_shared<CBUSSessionList>(*this, cbusSessionList.name()));
×
98

NEW
99
  Attributes::addDisplayName(type, DisplayName::Interface::type);
×
NEW
100
  Attributes::addEnabled(type, !online);
×
NEW
101
  Attributes::addValues(type, CBUSInterfaceTypeValues);
×
NEW
102
  m_interfaceItems.insertBefore(type, notes);
×
103

NEW
104
  Attributes::addEnabled(device, !online);
×
NEW
105
  Attributes::addVisible(device, false);
×
NEW
106
  m_interfaceItems.insertBefore(device, notes);
×
107

NEW
108
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
×
NEW
109
  Attributes::addEnabled(hostname, !online);
×
NEW
110
  Attributes::addVisible(hostname, false);
×
NEW
111
  m_interfaceItems.insertBefore(hostname, notes);
×
112

NEW
113
  Attributes::addDisplayName(port, DisplayName::IP::port);
×
NEW
114
  Attributes::addEnabled(port, !online);
×
NEW
115
  Attributes::addVisible(port, false);
×
NEW
116
  m_interfaceItems.insertBefore(port, notes);
×
117

NEW
118
  Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
×
NEW
119
  Attributes::addEnabled(interface, !online);
×
NEW
120
  Attributes::addVisible(interface, false);
×
NEW
121
  m_interfaceItems.insertBefore(interface, notes);
×
122

NEW
123
  Attributes::addEnabled(canId, !online);
×
NEW
124
  Attributes::addMinMax(canId, CBUS::canIdMin, CBUS::canIdMax);
×
NEW
125
  Attributes::addVisible(canId, false);
×
NEW
126
  m_interfaceItems.insertBefore(canId, notes);
×
127

NEW
128
  m_interfaceItems.insertBefore(cbus, notes);
×
129

NEW
130
  m_interfaceItems.insertBefore(cbusNodeList, notes);
×
131

NEW
132
  m_interfaceItems.insertBefore(cbusSessionList, notes);
×
133

NEW
134
  m_interfaceItems.insertBefore(decoders, notes);
×
135

NEW
136
  m_interfaceItems.insertBefore(inputs, notes);
×
137

NEW
138
  m_interfaceItems.insertBefore(outputs, notes);
×
139

NEW
140
  m_cbusPropertyChanged = cbus->propertyChanged.connect(
×
NEW
141
    [this](BaseProperty& /*property*/)
×
142
    {
NEW
143
      if(m_kernel)
×
144
      {
NEW
145
        m_kernel->setConfig(cbus->config());
×
146
      }
NEW
147
    });
×
148

NEW
149
  updateVisible();
×
NEW
150
}
×
151

NEW
152
CBUSInterface::~CBUSInterface() = default;
×
153

NEW
154
bool CBUSInterface::send(std::vector<uint8_t> message)
×
155
{
NEW
156
  if(m_kernel)
×
157
  {
NEW
158
    return m_kernel->send(std::move(message));
×
159
  }
NEW
160
  return false;
×
161
}
162

NEW
163
bool CBUSInterface::sendDCC(std::vector<uint8_t> dccPacket, uint8_t repeat)
×
164
{
NEW
165
  if(m_kernel)
×
166
  {
NEW
167
    return m_kernel->sendDCC(std::move(dccPacket), repeat);
×
168
  }
NEW
169
  return false;
×
170
}
171

NEW
172
size_t CBUSInterface::registerOnReceive(CBUS::OpCode opCode, std::function<void(uint8_t, const CBUS::Message&)> callback)
×
173
{
NEW
174
  if(m_kernel)
×
175
  {
NEW
176
    return m_kernel->registerOnReceive(opCode, std::move(callback));
×
177
  }
NEW
178
  return 0;
×
179
}
180

NEW
181
void CBUSInterface::unregisterOnReceive(size_t handle)
×
182
{
NEW
183
  if(m_kernel)
×
184
  {
NEW
185
    m_kernel->unregisterOnReceive(handle);
×
186
  }
NEW
187
}
×
188

NEW
189
std::span<const DecoderProtocol> CBUSInterface::decoderProtocols() const
×
190
{
191
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
NEW
192
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
×
193
}
194

NEW
195
void CBUSInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
196
{
NEW
197
  if(m_kernel)
×
198
  {
NEW
199
    const bool longAddress = (decoder.protocol == DecoderProtocol::DCCLong);
×
200

NEW
201
    if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= CBUS::engineFunctionMax)
×
202
    {
NEW
203
      m_kernel->setEngineFunction(
×
204
        decoder.address,
205
        longAddress,
206
        static_cast<uint8_t>(functionNumber),
NEW
207
        decoder.getFunctionValue(functionNumber));
×
208
    }
209
    else
210
    {
NEW
211
      m_kernel->setEngineSpeedDirection(
×
212
        decoder.address,
213
        longAddress,
NEW
214
        Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, decoder.speedSteps),
×
215
        decoder.speedSteps,
216
        decoder.emergencyStop,
NEW
217
        decoder.direction == Direction::Forward);
×
218
    }
219
  }
NEW
220
}
×
221

NEW
222
std::span<const InputChannel> CBUSInterface::inputChannels() const
×
223
{
224
  static const std::array<InputChannel, 2> channels{
225
    InputChannel::ShortEvent,
226
    InputChannel::LongEvent,
227
  };
NEW
228
  return channels;
×
229
}
230

NEW
231
bool CBUSInterface::isInputLocation(InputChannel channel, const InputLocation& location) const
×
232
{
NEW
233
  if(hasNodeAddressLocation(channel))
×
234
  {
235
    return
NEW
236
      inRange<uint32_t>(std::get<InputNodeAddress>(location).node, nodeNumberRange) &&
×
NEW
237
      inRange<uint32_t>(std::get<InputNodeAddress>(location).address, inputAddressMinMax(channel));
×
238
  }
NEW
239
  return InputController::isInputLocation(channel, location);
×
240
}
241

NEW
242
std::pair<uint32_t, uint32_t> CBUSInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
243
{
NEW
244
  return eventNumberRange;
×
245
}
246

NEW
247
void CBUSInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
248
{
NEW
249
  if(m_simulator)
×
250
  {
NEW
251
    boost::asio::post(m_kernel->ioContext(),
×
NEW
252
      [this, channel, location, action]
×
253
      {
NEW
254
        switch(channel)
×
255
        {
NEW
256
          case InputChannel::ShortEvent:
×
NEW
257
            m_simulator->shortEvent(std::get<InputAddress>(location).address, action);
×
NEW
258
            break;
×
259

NEW
260
          case InputChannel::LongEvent:
×
NEW
261
            m_simulator->longEvent(std::get<InputNodeAddress>(location).node, std::get<InputNodeAddress>(location).address, action);
×
NEW
262
            break;
×
263

NEW
264
          default: [[unlikely]]
×
NEW
265
            assert(false);
×
266
            break;
267
        }
NEW
268
      });
×
269
  }
NEW
270
}
×
271

NEW
272
std::span<const OutputChannel> CBUSInterface::outputChannels() const
×
273
{
274
  static const std::array<OutputChannel, 4> channels{
275
    OutputChannel::ShortEvent,
276
    OutputChannel::LongEvent,
277
    OutputChannel::AccessoryDCC,
278
    OutputChannel::DCCext,
279
  };
NEW
280
  return channels;
×
281
}
282

NEW
283
std::pair<uint32_t, uint32_t> CBUSInterface::outputNodeMinMax(OutputChannel /*channel*/) const
×
284
{
NEW
285
  return eventNumberRange;
×
286
}
287

NEW
288
std::pair<uint32_t, uint32_t> CBUSInterface::outputAddressMinMax(OutputChannel channel) const
×
289
{
NEW
290
  switch(channel)
×
291
  {
NEW
292
    case OutputChannel::LongEvent:
×
293
    case OutputChannel::ShortEvent:
NEW
294
      return {std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()};
×
295

NEW
296
    default:
×
NEW
297
      return OutputController::outputAddressMinMax(channel);
×
298
  }
299
}
300

NEW
301
bool CBUSInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
302
{
NEW
303
  if(m_kernel)
×
304
  {
NEW
305
    switch(channel)
×
306
    {
NEW
307
      case OutputChannel::ShortEvent:
×
NEW
308
        assert(std::holds_alternative<TriState>(value));
×
NEW
309
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
310
        {
NEW
311
          assert(std::holds_alternative<OutputAddress>(location));
×
NEW
312
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
NEW
313
          m_kernel->setAccessoryShort(address, v == TriState::True);
×
NEW
314
          return true;
×
315
        }
NEW
316
        break;
×
317

NEW
318
      case OutputChannel::LongEvent:
×
NEW
319
        assert(std::holds_alternative<TriState>(value));
×
NEW
320
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
321
        {
NEW
322
          assert(std::holds_alternative<OutputNodeAddress>(location));
×
NEW
323
          const auto node = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).node);
×
NEW
324
          const auto address = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).address);
×
NEW
325
          m_kernel->setAccessory(node, address, v == TriState::True);
×
NEW
326
          return true;
×
327
        }
NEW
328
        break;
×
329

NEW
330
      case OutputChannel::AccessoryDCC:
×
NEW
331
        assert(std::holds_alternative<OutputPairValue>(value));
×
NEW
332
        if(const auto v = std::get<OutputPairValue>(value); v == OutputPairValue::First || v == OutputPairValue::Second)
×
333
        {
NEW
334
          assert(std::holds_alternative<OutputAddress>(location));
×
NEW
335
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
NEW
336
          m_kernel->setDccAccessory(address, v == OutputPairValue::Second);
×
NEW
337
          return true;
×
338
        }
NEW
339
        break;
×
340

NEW
341
      case OutputChannel::DCCext:
×
NEW
342
        assert(std::holds_alternative<int16_t>(value));
×
NEW
343
        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()))
×
344
        {
NEW
345
          assert(std::holds_alternative<OutputAddress>(location));
×
NEW
346
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
NEW
347
          m_kernel->setDccAdvancedAccessoryValue(address, static_cast<uint8_t>(v));
×
NEW
348
          return true;
×
349
        }
NEW
350
        break;
×
351

NEW
352
      default: [[unlikely]]
×
NEW
353
        assert(false);
×
354
        break;
355
    }
356
  }
NEW
357
  return false;
×
358
}
359

NEW
360
void CBUSInterface::addToWorld()
×
361
{
NEW
362
  Interface::addToWorld();
×
NEW
363
  DecoderController::addToWorld();
×
NEW
364
  InputController::addToWorld(inputListColumns);
×
NEW
365
  OutputController::addToWorld(outputListColumns);
×
NEW
366
  m_world.cbusInterfaces->add(Object::shared_ptr<CBUSInterface>());
×
NEW
367
}
×
368

NEW
369
void CBUSInterface::loaded()
×
370
{
NEW
371
  Interface::loaded();
×
372

NEW
373
  updateVisible();
×
NEW
374
}
×
375

NEW
376
void CBUSInterface::destroying()
×
377
{
NEW
378
  m_world.cbusInterfaces->add(Object::shared_ptr<CBUSInterface>());
×
NEW
379
  m_cbusPropertyChanged.disconnect();
×
NEW
380
  OutputController::destroying();
×
NEW
381
  InputController::destroying();
×
NEW
382
  DecoderController::destroying();
×
NEW
383
  Interface::destroying();
×
NEW
384
}
×
385

NEW
386
void CBUSInterface::worldEvent(WorldState state, WorldEvent event)
×
387
{
NEW
388
  Interface::worldEvent(state, event);
×
389

NEW
390
  switch(event)
×
391
  {
NEW
392
    case WorldEvent::PowerOff:
×
NEW
393
      if(m_kernel)
×
394
      {
NEW
395
        m_kernel->trackOff();
×
396
      }
NEW
397
      break;
×
398

NEW
399
    case WorldEvent::PowerOn:
×
NEW
400
      if(m_kernel)
×
401
      {
NEW
402
        m_kernel->trackOn();
×
403
      }
NEW
404
      break;
×
405

NEW
406
    case WorldEvent::Stop:
×
NEW
407
      if(m_kernel)
×
408
      {
NEW
409
        m_kernel->requestEmergencyStop();
×
410
      }
NEW
411
      break;
×
412

NEW
413
    case WorldEvent::Run:
×
NEW
414
      if(m_kernel)
×
415
      {
416
        // TODO: send all known speed values
417
      }
NEW
418
      break;
×
419

NEW
420
    default:
×
NEW
421
      break;
×
422
  }
NEW
423
}
×
424

NEW
425
bool CBUSInterface::setOnline(bool& value, bool simulation)
×
426
{
NEW
427
  if(!m_kernel && value)
×
428
  {
429
    try
430
    {
NEW
431
      if(simulation)
×
432
      {
NEW
433
        m_simulator = std::make_unique<CBUS::Simulator>();
×
NEW
434
        m_simulator->addModule(std::make_unique<CBUS::Module::CANCMD>(*m_simulator));
×
NEW
435
        m_simulator->addModule(std::make_unique<CBUS::Module::CANCAB>(*m_simulator));
×
NEW
436
        m_kernel = CBUS::Kernel::create<CBUS::SimulationIOHandler>(id.value(), cbus->config(), CBUS::CanId::CANUSB, std::ref(*m_simulator));
×
437
      }
438
      else
439
      {
NEW
440
        switch(type)
×
441
        {
NEW
442
          case CBUSInterfaceType::CANUSB:
×
NEW
443
            m_kernel = CBUS::Kernel::create<CBUS::CANUSBIOHandler>(id.value(), cbus->config(), CBUS::CanId::CANUSB, device.value());
×
NEW
444
            break;
×
445

NEW
446
          case CBUSInterfaceType::CANEther:
×
NEW
447
            m_kernel = CBUS::Kernel::create<CBUS::CANEtherIOHandler>(id.value(), cbus->config(), CBUS::CanId::CANEther, hostname.value(), port.value());
×
NEW
448
            break;
×
449

NEW
450
          case CBUSInterfaceType::SocketCAN:
×
451
#ifdef __linux__
NEW
452
            m_kernel = CBUS::Kernel::create<CBUS::SocketCANIOHandler>(id.value(), cbus->config(), canId.value(), interface.value());
×
NEW
453
            break;
×
454
#else
455
            setState(InterfaceState::Error);
456
            Log::log(*this, LogMessage::C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX);
457
            return false;
458
#endif
459
        }
460
      }
461

NEW
462
      setState(InterfaceState::Initializing);
×
463

NEW
464
      m_kernel->setOnStarted(
×
NEW
465
        [this]()
×
466
        {
NEW
467
          setState(InterfaceState::Online);
×
NEW
468
        });
×
NEW
469
      m_kernel->setOnError(
×
NEW
470
        [this]()
×
471
        {
NEW
472
          setState(InterfaceState::Error);
×
NEW
473
          online = false; // communication no longer possible
×
NEW
474
        });
×
NEW
475
      m_kernel->onPresenceOfNode =
×
NEW
476
        [this](uint8_t canId_, uint16_t nodeNumber, uint8_t manufacturerId, uint8_t moduleId, bool flimMode, bool supportsServiceDiscovery)
×
477
        {
NEW
478
          cbusNodeList->add(CBUSNodeList::Node{
×
479
            .nodeNumber = nodeNumber,
480
            .canId = canId_,
481
            .manufacturerId = manufacturerId,
482
            .moduleId = moduleId,
483
            .flim = flimMode,
484
            .vlcb = supportsServiceDiscovery,
485
            .parameters = {},
486
          });
NEW
487
        };
×
NEW
488
      m_kernel->onNodeParameterResponse =
×
NEW
489
        [this](uint8_t canId_, uint16_t nodeNumber, CBUS::NodeParameter parameter, uint8_t parameterValue)
×
490
        {
NEW
491
          auto& nodes = cbusNodeList->m_nodes;
×
NEW
492
          if(auto it = std::find_if(nodes.begin(), nodes.end(),
×
NEW
493
              [canId_, nodeNumber](const auto& item)
×
494
              {
NEW
495
                return item.canId == canId_ && item.nodeNumber == nodeNumber;
×
NEW
496
              }); it != nodes.end())
×
497
          {
NEW
498
            switch(parameter)
×
499
            {
500
              using enum CBUS::NodeParameter;
501

NEW
502
              case VersionMajor:
×
NEW
503
                it->parameters.versionMajor = parameterValue;
×
NEW
504
                break;
×
505

NEW
506
              case VersionMinor:
×
NEW
507
                it->parameters.versionMinor = parameterValue;
×
NEW
508
                break;
×
509

NEW
510
              case BetaReleaseCode:
×
NEW
511
                it->parameters.betaReleaseCode = parameterValue;
×
NEW
512
                break;
×
513

NEW
514
              default:
×
NEW
515
                return;
×
516
            }
NEW
517
            cbusNodeList->rowChanged(static_cast<uint32_t>(std::distance(nodes.begin(), it)));
×
518
          }
NEW
519
        };
×
NEW
520
      m_kernel->onTrackOff =
×
NEW
521
        [this]()
×
522
        {
NEW
523
          if(contains(m_world.state, WorldState::PowerOn))
×
524
          {
NEW
525
            m_world.powerOff();
×
526
          }
NEW
527
        };
×
NEW
528
      m_kernel->onTrackOn =
×
NEW
529
        [this]()
×
530
        {
NEW
531
          if(!contains(m_world.state, WorldState::PowerOn))
×
532
          {
NEW
533
            m_world.powerOn();
×
534
          }
NEW
535
        };
×
NEW
536
      m_kernel->onEmergencyStop =
×
NEW
537
        [this]()
×
538
        {
NEW
539
          if(contains(m_world.state, WorldState::Run))
×
540
          {
NEW
541
            m_world.stop();
×
542
          }
NEW
543
        };
×
NEW
544
      m_kernel->onEngineSessionAcquire =
×
NEW
545
        [this](uint8_t session, uint16_t address, bool isLongAddress)
×
546
        {
NEW
547
          if(auto it = cbusSessionList->findEngine(address, isLongAddress); it != cbusSessionList->end())
×
548
          {
NEW
549
            it->session = session;
×
NEW
550
            cbusSessionList->rowChanged(static_cast<uint32_t>(std::distance(cbusSessionList->begin(), it)));
×
551
          }
552
          else
553
          {
NEW
554
            cbusSessionList->add({session, address, isLongAddress});
×
555
          }
NEW
556
        };
×
NEW
557
      m_kernel->onEngineSpeedDirectionChanged =
×
NEW
558
        [this](uint8_t session, uint8_t speed, bool forward)
×
559
        {
NEW
560
          if(auto it = cbusSessionList->findSession(session); it != cbusSessionList->end())
×
561
          {
NEW
562
            it->speed = speed;
×
NEW
563
            it->direction = forward ? Direction::Forward : Direction::Reverse;
×
NEW
564
            cbusSessionList->rowChanged(static_cast<uint32_t>(std::distance(cbusSessionList->begin(), it)));
×
565
          }
NEW
566
        };
×
NEW
567
      m_kernel->onEngineFunctionChanged =
×
NEW
568
        [this](uint8_t session, uint8_t number, bool on)
×
569
        {
NEW
570
          if(auto it = cbusSessionList->findSession(session); it != cbusSessionList->end())
×
571
          {
NEW
572
            assert(number < it->functions.size());
×
NEW
573
            it->functions[number] = toTriState(on);
×
NEW
574
            cbusSessionList->rowChanged(static_cast<uint32_t>(std::distance(cbusSessionList->begin(), it)));
×
575
          }
NEW
576
        };
×
NEW
577
      m_kernel->onEngineSessionReleased =
×
NEW
578
        [this](uint8_t session)
×
579
        {
NEW
580
          if(auto it = cbusSessionList->findSession(session); it != cbusSessionList->end())
×
581
          {
NEW
582
            it->session = std::nullopt;
×
NEW
583
            cbusSessionList->rowChanged(static_cast<uint32_t>(std::distance(cbusSessionList->begin(), it)));
×
584
          }
NEW
585
        };
×
NEW
586
      m_kernel->onEngineSessionCancelled =
×
NEW
587
        [this](uint16_t address, bool isLongAddress)
×
588
        {
NEW
589
          if(auto it = cbusSessionList->findEngine(address, isLongAddress); it != cbusSessionList->end())
×
590
          {
NEW
591
            it->session = std::nullopt;
×
NEW
592
            cbusSessionList->rowChanged(static_cast<uint32_t>(std::distance(cbusSessionList->begin(), it)));
×
593
          }
NEW
594
        };
×
NEW
595
      m_kernel->onShortEvent =
×
NEW
596
        [this](uint16_t eventNumber, bool on)
×
597
        {
NEW
598
          updateInputValue(InputChannel::ShortEvent, InputAddress(eventNumber), toTriState(on));
×
NEW
599
          updateOutputValue(OutputChannel::ShortEvent, OutputAddress(eventNumber), toTriState(on));
×
NEW
600
        };
×
NEW
601
      m_kernel->onLongEvent =
×
NEW
602
        [this](uint16_t nodeNumber, uint16_t eventNumber, bool on)
×
603
        {
NEW
604
          updateInputValue(InputChannel::LongEvent, InputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
NEW
605
          updateOutputValue(OutputChannel::LongEvent, OutputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
NEW
606
        };
×
607

608
      {
NEW
609
        std::vector<uint16_t> shortEvents;
×
NEW
610
        std::vector<std::pair<uint16_t, uint16_t>> longEvents;
×
611

NEW
612
        for(const auto& [key, input] : m_inputs)
×
613
        {
NEW
614
          if(input->channel == InputChannel::ShortEvent)
×
615
          {
NEW
616
            shortEvents.emplace_back(input->address);
×
617
          }
NEW
618
          else if(input->channel == InputChannel::LongEvent)
×
619
          {
NEW
620
            longEvents.emplace_back(input->node, input->address);
×
621
          }
622
        }
623

NEW
624
        for(const auto& [key, output] : m_outputs)
×
625
        {
NEW
626
          if(output->channel == OutputChannel::ShortEvent)
×
627
          {
NEW
628
            shortEvents.emplace_back(static_cast<const AddressOutput&>(*output).address);
×
629
          }
NEW
630
          else if(output->channel == OutputChannel::LongEvent)
×
631
          {
NEW
632
            longEvents.emplace_back(static_cast<const AddressOutput&>(*output).node, static_cast<const AddressOutput&>(*output).address);
×
633
          }
634
        }
635

NEW
636
        deduplicate(shortEvents);
×
NEW
637
        deduplicate(longEvents);
×
638

NEW
639
        m_kernel->setRequestEventsDuringInitialize(std::move(shortEvents), std::move(longEvents));
×
NEW
640
      }
×
641

NEW
642
      m_kernel->start();
×
643

NEW
644
      Attributes::setEnabled({type, device, hostname, port, interface, canId}, false);
×
645
    }
NEW
646
    catch(const LogMessageException& e)
×
647
    {
NEW
648
      setState(InterfaceState::Offline);
×
NEW
649
      Log::log(*this, e.message(), e.args());
×
NEW
650
      return false;
×
NEW
651
    }
×
652
  }
NEW
653
  else if(m_kernel && !value)
×
654
  {
NEW
655
    Attributes::setEnabled({type, device, hostname, port, interface, canId}, true);
×
656

NEW
657
    m_kernel->stop();
×
NEW
658
    EventLoop::deleteLater(m_kernel.release());
×
NEW
659
    EventLoop::deleteLater(m_simulator.release());
×
660

NEW
661
    cbusNodeList->clear();
×
662

NEW
663
    if(status->state != InterfaceState::Error)
×
664
    {
NEW
665
      setState(InterfaceState::Offline);
×
666
    }
667
  }
NEW
668
  return true;
×
669
}
670

NEW
671
void CBUSInterface::updateVisible()
×
672
{
NEW
673
  Attributes::setVisible(device, (type == CBUSInterfaceType::CANUSB));
×
NEW
674
  Attributes::setVisible({hostname, port}, (type == CBUSInterfaceType::CANEther));
×
NEW
675
  Attributes::setVisible({interface, canId}, (type == CBUSInterfaceType::SocketCAN));
×
NEW
676
}
×
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