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

traintastic / traintastic / 23671871465

27 Mar 2026 11:30PM UTC coverage: 26.15% (-0.05%) from 26.198%
23671871465

push

github

reinder
[cbus] added SocketCAN io handler

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

16 existing lines in 2 files now uncovered.

8256 of 31572 relevant lines covered (26.15%)

182.21 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
#include "../protocol/cbus/iohandler/cbussocketcaniohandler.hpp"
36
#include "../protocol/cbus/simulator/cbussimulator.hpp"
37
#include "../protocol/cbus/simulator/module/cbuscancmd.hpp"
38
#include "../../core/attributes.hpp"
39
#include "../../core/eventloop.hpp"
40
#include "../../core/method.tpp"
41
#include "../../core/objectproperty.tpp"
42
#include "../../log/log.hpp"
43
#include "../../log/logmessageexception.hpp"
44
#include "../../utils/displayname.hpp"
45
#include "../../utils/inrange.hpp"
46
#include "../../world/world.hpp"
47

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

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

55
CREATE_IMPL(CBUSInterface)
×
56

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

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

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

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

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

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

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

UNCOV
108
  m_interfaceItems.insertBefore(cbus, notes);
×
109

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

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

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

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

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

127
  updateVisible();
×
128
}
×
129

130
CBUSInterface::~CBUSInterface() = default;
×
131

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

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

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

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

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

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

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

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

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

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

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

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

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

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

257
    default:
×
258
      return OutputController::outputAddressMinMax(channel);
×
259
  }
260
}
261

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

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

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

302
      case OutputChannel::DCCext:
×
303
        assert(std::holds_alternative<int16_t>(value));
×
304
        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()))
×
305
        {
306
          assert(std::holds_alternative<OutputAddress>(location));
×
307
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
308
          m_kernel->setDccAdvancedAccessoryValue(address, static_cast<uint8_t>(v));
×
309
          return true;
×
310
        }
311
        break;
×
312

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

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

329
void CBUSInterface::loaded()
×
330
{
331
  Interface::loaded();
×
332

333
  updateVisible();
×
334
}
×
335

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

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

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

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

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

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

379
    default:
×
380
      break;
×
381
  }
382
}
×
383

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

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

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

420
      setState(InterfaceState::Initializing);
×
421

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

458
              case VersionMajor:
×
459
                it->parameters.versionMajor = parameterValue;
×
460
                break;
×
461

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

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

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

513
      m_kernel->start();
×
514

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

528
    m_kernel->stop();
×
529
    EventLoop::deleteLater(m_kernel.release());
×
530
    EventLoop::deleteLater(m_simulator.release());
×
531

532
    cbusNodeList->clear();
×
533

534
    if(status->state != InterfaceState::Error)
×
535
    {
536
      setState(InterfaceState::Offline);
×
537
    }
538
  }
539
  return true;
×
540
}
541

542
void CBUSInterface::updateVisible()
×
543
{
NEW
544
  Attributes::setVisible(device, (type == CBUSInterfaceType::CANUSB));
×
NEW
545
  Attributes::setVisible({hostname, port}, (type == CBUSInterfaceType::CANEther));
×
NEW
546
  Attributes::setVisible({interface, canId}, (type == CBUSInterfaceType::SocketCAN));
×
UNCOV
547
}
×
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