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

traintastic / traintastic / 23378856294

21 Mar 2026 11:37AM UTC coverage: 26.481% (-0.05%) from 26.526%
23378856294

push

github

reinder
[cbus] added DCC accessory output channel

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

3 existing lines in 2 files now uncovered.

8246 of 31139 relevant lines covered (26.48%)

184.58 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/cbussettings.hpp"
24
#include "../decoder/decoderchangeflags.hpp"
25
#include "../decoder/list/decoderlist.hpp"
26
#include "../decoder/list/decoderlisttablemodel.hpp"
27
#include "../input/list/inputlist.hpp"
28
#include "../output/list/outputlist.hpp"
29
#include "../protocol/cbus/cbusconst.hpp"
30
#include "../protocol/cbus/cbuskernel.hpp"
31
#include "../protocol/cbus/iohandler/cbuscanusbiohandler.hpp"
32
#include "../protocol/cbus/iohandler/cbuscanetheriohandler.hpp"
33
#include "../protocol/cbus/iohandler/cbussimulationiohandler.hpp"
34
#include "../protocol/cbus/simulator/cbussimulator.hpp"
35
#include "../protocol/cbus/simulator/module/cbuscancmd.hpp"
36
#include "../../core/attributes.hpp"
37
#include "../../core/eventloop.hpp"
38
#include "../../core/method.tpp"
39
#include "../../core/objectproperty.tpp"
40
#include "../../log/log.hpp"
41
#include "../../log/logmessageexception.hpp"
42
#include "../../utils/displayname.hpp"
43
#include "../../utils/inrange.hpp"
44
#include "../../world/world.hpp"
45

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

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

53
CREATE_IMPL(CBUSInterface)
×
54

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

73
  Attributes::addDisplayName(type, DisplayName::Interface::type);
×
74
  Attributes::addEnabled(type, !online);
×
75
  Attributes::addValues(type, CBUSInterfaceTypeValues);
×
76
  m_interfaceItems.insertBefore(type, notes);
×
77

78
  Attributes::addEnabled(device, !online);
×
79
  Attributes::addVisible(device, false);
×
80
  m_interfaceItems.insertBefore(device, notes);
×
81

82
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
×
83
  Attributes::addEnabled(hostname, !online);
×
84
  Attributes::addVisible(hostname, false);
×
85
  m_interfaceItems.insertBefore(hostname, notes);
×
86

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

92
  m_interfaceItems.insertBefore(cbus, notes);
×
93

94
  m_interfaceItems.insertBefore(decoders, notes);
×
95

96
  m_interfaceItems.insertBefore(inputs, notes);
×
97

98
  m_interfaceItems.insertBefore(outputs, notes);
×
99

100
  m_cbusPropertyChanged = cbus->propertyChanged.connect(
×
101
    [this](BaseProperty& /*property*/)
×
102
    {
103
      if(m_kernel)
×
104
      {
105
        m_kernel->setConfig(cbus->config());
×
106
      }
107
    });
×
108

109
  updateVisible();
×
110
}
×
111

112
CBUSInterface::~CBUSInterface() = default;
×
113

114
bool CBUSInterface::send(std::vector<uint8_t> message)
×
115
{
116
  if(m_kernel)
×
117
  {
118
    return m_kernel->send(std::move(message));
×
119
  }
120
  return false;
×
121
}
122

123
bool CBUSInterface::sendDCC(std::vector<uint8_t> dccPacket, uint8_t repeat)
×
124
{
125
  if(m_kernel)
×
126
  {
127
    return m_kernel->sendDCC(std::move(dccPacket), repeat);
×
128
  }
129
  return false;
×
130
}
131

132
std::span<const DecoderProtocol> CBUSInterface::decoderProtocols() const
×
133
{
134
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
135
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
×
136
}
137

138
void CBUSInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
139
{
140
  if(m_kernel)
×
141
  {
142
    const bool longAddress = (decoder.protocol == DecoderProtocol::DCCLong);
×
143

144
    if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= CBUS::engineFunctionMax)
×
145
    {
146
      m_kernel->setEngineFunction(
×
147
        decoder.address,
148
        longAddress,
149
        static_cast<uint8_t>(functionNumber),
150
        decoder.getFunctionValue(functionNumber));
×
151
    }
152
    else
153
    {
154
      m_kernel->setEngineSpeedDirection(
×
155
        decoder.address,
156
        longAddress,
157
        Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, decoder.speedSteps),
×
158
        decoder.speedSteps,
159
        decoder.emergencyStop,
160
        decoder.direction == Direction::Forward);
×
161
    }
162
  }
163
}
×
164

165
std::span<const InputChannel> CBUSInterface::inputChannels() const
×
166
{
167
  static const std::array<InputChannel, 2> channels{
168
    InputChannel::ShortEvent,
169
    InputChannel::LongEvent,
170
  };
171
  return channels;
×
172
}
173

174
bool CBUSInterface::isInputLocation(InputChannel channel, const InputLocation& location) const
×
175
{
176
  if(hasNodeAddressLocation(channel))
×
177
  {
178
    return
179
      inRange<uint32_t>(std::get<InputNodeAddress>(location).node, nodeNumberRange) &&
×
180
      inRange<uint32_t>(std::get<InputNodeAddress>(location).address, inputAddressMinMax(channel));
×
181
  }
182
  return InputController::isInputLocation(channel, location);
×
183
}
184

185
std::pair<uint32_t, uint32_t> CBUSInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
186
{
187
  return eventNumberRange;
×
188
}
189

190
void CBUSInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
191
{
192
  if(m_simulator)
×
193
  {
194
    m_kernel->ioContext().post(
×
195
      [this, channel, location, action]
×
196
      {
197
        switch(channel)
×
198
        {
199
          case InputChannel::ShortEvent:
×
200
            m_simulator->shortEvent(std::get<InputAddress>(location).address, action);
×
201
            break;
×
202

203
          case InputChannel::LongEvent:
×
204
            m_simulator->longEvent(std::get<InputNodeAddress>(location).node, std::get<InputNodeAddress>(location).address, action);
×
205
            break;
×
206

207
          default: [[unlikely]]
×
208
            assert(false);
×
209
            break;
210
        }
211
      });
×
212
  }
213
}
×
214

215
std::span<const OutputChannel> CBUSInterface::outputChannels() const
×
216
{
217
  static const std::array<OutputChannel, 4> channels{
218
    OutputChannel::ShortEvent,
219
    OutputChannel::LongEvent,
220
    OutputChannel::AccessoryDCC,
221
    OutputChannel::DCCext,
222
  };
223
  return channels;
×
224
}
225

226
std::pair<uint32_t, uint32_t> CBUSInterface::outputNodeMinMax(OutputChannel /*channel*/) const
×
227
{
228
  return eventNumberRange;
×
229
}
230

231
std::pair<uint32_t, uint32_t> CBUSInterface::outputAddressMinMax(OutputChannel channel) const
×
232
{
233
  switch(channel)
×
234
  {
235
    case OutputChannel::LongEvent:
×
236
    case OutputChannel::ShortEvent:
237
      return {std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()};
×
238

239
    default:
×
240
      return OutputController::outputAddressMinMax(channel);
×
241
  }
242
}
243

244
bool CBUSInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
245
{
246
  if(m_kernel)
×
247
  {
248
    switch(channel)
×
249
    {
250
      case OutputChannel::ShortEvent:
×
251
        assert(std::holds_alternative<TriState>(value));
×
252
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
253
        {
NEW
254
          assert(std::holds_alternative<OutputAddress>(location));
×
255
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
256
          m_kernel->setAccessoryShort(address, v == TriState::True);
×
257
          return true;
×
258
        }
259
        break;
×
260

261
      case OutputChannel::LongEvent:
×
262
        assert(std::holds_alternative<TriState>(value));
×
263
        if(auto v = std::get<TriState>(value); v != TriState::Undefined)
×
264
        {
NEW
265
          assert(std::holds_alternative<OutputNodeAddress>(location));
×
266
          const auto node = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).node);
×
267
          const auto address = static_cast<uint16_t>(std::get<OutputNodeAddress>(location).address);
×
268
          m_kernel->setAccessory(node, address, v == TriState::True);
×
269
          return true;
×
270
        }
271
        break;
×
272

NEW
273
      case OutputChannel::AccessoryDCC:
×
NEW
274
        assert(std::holds_alternative<OutputPairValue>(value));
×
NEW
275
        if(const auto v = std::get<OutputPairValue>(value); v == OutputPairValue::First || v == OutputPairValue::Second)
×
276
        {
NEW
277
          assert(std::holds_alternative<OutputAddress>(location));
×
NEW
278
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
NEW
279
          m_kernel->setDccAccessory(address, v == OutputPairValue::Second);
×
NEW
280
          return true;
×
281
        }
NEW
282
        break;
×
283

284
      case OutputChannel::DCCext:
×
285
        assert(std::holds_alternative<int16_t>(value));
×
286
        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()))
×
287
        {
NEW
288
          assert(std::holds_alternative<OutputAddress>(location));
×
289
          const auto address = static_cast<uint16_t>(std::get<OutputAddress>(location).address);
×
290
          m_kernel->setDccAdvancedAccessoryValue(address, static_cast<uint8_t>(v));
×
291
          return true;
×
292
        }
293
        break;
×
294

295
      default: [[unlikely]]
×
296
        assert(false);
×
297
        break;
298
    }
299
  }
300
  return false;
×
301
}
302

303
void CBUSInterface::addToWorld()
×
304
{
305
  Interface::addToWorld();
×
306
  DecoderController::addToWorld();
×
307
  InputController::addToWorld(inputListColumns);
×
308
  OutputController::addToWorld(outputListColumns);
×
309
}
×
310

311
void CBUSInterface::loaded()
×
312
{
313
  Interface::loaded();
×
314

315
  updateVisible();
×
316
}
×
317

318
void CBUSInterface::destroying()
×
319
{
320
  m_cbusPropertyChanged.disconnect();
×
321
  OutputController::destroying();
×
322
  InputController::destroying();
×
323
  DecoderController::destroying();
×
324
  Interface::destroying();
×
325
}
×
326

327
void CBUSInterface::worldEvent(WorldState state, WorldEvent event)
×
328
{
329
  Interface::worldEvent(state, event);
×
330

331
  switch(event)
×
332
  {
333
    case WorldEvent::PowerOff:
×
334
      if(m_kernel)
×
335
      {
336
        m_kernel->trackOff();
×
337
      }
338
      break;
×
339

340
    case WorldEvent::PowerOn:
×
341
      if(m_kernel)
×
342
      {
343
        m_kernel->trackOn();
×
344
      }
345
      break;
×
346

347
    case WorldEvent::Stop:
×
348
      if(m_kernel)
×
349
      {
350
        m_kernel->requestEmergencyStop();
×
351
      }
352
      break;
×
353

354
    case WorldEvent::Run:
×
355
      if(m_kernel)
×
356
      {
357
        // TODO: send all known speed values
358
      }
359
      break;
×
360

361
    default:
×
362
      break;
×
363
  }
364
}
×
365

366
bool CBUSInterface::setOnline(bool& value, bool simulation)
×
367
{
368
  if(!m_kernel && value)
×
369
  {
370
    try
371
    {
372
      if(simulation)
×
373
      {
374
        m_simulator = std::make_unique<CBUS::Simulator>();
×
375
        m_simulator->addModule(std::make_unique<CBUS::Module::CANCMD>(*m_simulator));
×
376
        m_kernel = CBUS::Kernel::create<CBUS::SimulationIOHandler>(id.value(), cbus->config(), std::ref(*m_simulator));
×
377
      }
378
      else
379
      {
380
        switch(type)
×
381
        {
382
          case CBUSInterfaceType::CANUSB:
×
383
            m_kernel = CBUS::Kernel::create<CBUS::CANUSBIOHandler>(id.value(), cbus->config(), device.value());
×
384
            break;
×
385

386
          case CBUSInterfaceType::CANEther:
×
387
            m_kernel = CBUS::Kernel::create<CBUS::CANEtherIOHandler>(id.value(), cbus->config(), hostname.value(), port.value());
×
388
            break;
×
389
        }
390
      }
391

392
      setState(InterfaceState::Initializing);
×
393

394
      m_kernel->setOnStarted(
×
395
        [this]()
×
396
        {
397
          setState(InterfaceState::Online);
×
398
        });
×
399
      m_kernel->setOnError(
×
400
        [this]()
×
401
        {
402
          setState(InterfaceState::Error);
×
403
          online = false; // communication no longer possible
×
404
        });
×
405
      m_kernel->onTrackOff =
×
406
        [this]()
×
407
        {
408
          if(contains(m_world.state, WorldState::PowerOn))
×
409
          {
410
            m_world.powerOff();
×
411
          }
412
        };
×
413
      m_kernel->onTrackOn =
×
414
        [this]()
×
415
        {
416
          if(!contains(m_world.state, WorldState::PowerOn))
×
417
          {
418
            m_world.powerOn();
×
419
          }
420
        };
×
421
      m_kernel->onEmergencyStop =
×
422
        [this]()
×
423
        {
424
          if(contains(m_world.state, WorldState::Run))
×
425
          {
426
            m_world.stop();
×
427
          }
428
        };
×
429
      m_kernel->onShortEvent =
×
430
        [this](uint16_t eventNumber, bool on)
×
431
        {
432
          updateInputValue(InputChannel::ShortEvent, InputAddress(eventNumber), toTriState(on));
×
433
          updateOutputValue(OutputChannel::ShortEvent, OutputAddress(eventNumber), toTriState(on));
×
434
        };
×
435
      m_kernel->onLongEvent =
×
436
        [this](uint16_t nodeNumber, uint16_t eventNumber, bool on)
×
437
        {
438
          updateInputValue(InputChannel::LongEvent, InputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
439
          updateOutputValue(OutputChannel::LongEvent, OutputNodeAddress(nodeNumber, eventNumber), toTriState(on));
×
440
        };
×
441

442
      m_kernel->start();
×
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
    m_kernel->stop();
×
454
    EventLoop::deleteLater(m_kernel.release());
×
455
    EventLoop::deleteLater(m_simulator.release());
×
456

457
    if(status->state != InterfaceState::Error)
×
458
    {
459
      setState(InterfaceState::Offline);
×
460
    }
461
  }
462
  return true;
×
463
}
464

465
void CBUSInterface::updateVisible()
×
466
{
467
  const bool isSerial = (type == CBUSInterfaceType::CANUSB);
×
468
  Attributes::setVisible(device, isSerial);
×
469

470
  const bool isNetwork = (type == CBUSInterfaceType::CANEther);
×
471
  Attributes::setVisible(hostname, isNetwork);
×
472
  Attributes::setVisible(port, isNetwork);
×
473
}
×
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