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

traintastic / traintastic / 22823086659

08 Mar 2026 02:30PM UTC coverage: 26.831% (+0.06%) from 26.774%
22823086659

push

github

reinder
[cbus] renamed CBUSAccessory(Short) to Long/Short event and added node setting for Long events.

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

1909 existing lines in 38 files now uncovered.

8230 of 30674 relevant lines covered (26.83%)

186.8 hits per line

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

44.32
/server/src/hardware/interface/marklincaninterface.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2023-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 "marklincaninterface.hpp"
23
#include "../decoder/list/decoderlist.hpp" // ????
24
#include "../decoder/list/decoderlisttablemodel.hpp"
25
#include "../input/input.hpp"
26
#include "../input/list/inputlist.hpp"
27
#include "../output/list/outputlist.hpp"
28
#include "../protocol/marklincan/iohandler/simulationiohandler.hpp"
29
#include "../protocol/marklincan/iohandler/tcpiohandler.hpp"
30
#include "../protocol/marklincan/iohandler/udpiohandler.hpp"
31
#ifdef __linux__
32
  #include "../protocol/marklincan/iohandler/socketcaniohandler.hpp"
33
#endif
34
#include "../protocol/marklincan/iohandler/serialiohandler.hpp"
35
#include "../protocol/marklincan/kernel.hpp"
36
#include "../protocol/marklincan/settings.hpp"
37
#include "../../core/attributes.hpp"
38
#include "../../core/eventloop.hpp"
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 "../../utils/makearray.hpp"
45

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

50
MarklinCANInterface::MarklinCANInterface(World& world, std::string_view _id)
8✔
51
  : Interface(world, _id)
52
  , DecoderController(*this, decoderListColumns)
53
  , InputController(static_cast<IdObject&>(*this))
54
  , OutputController(static_cast<IdObject&>(*this))
55
  , type{this, "type", MarklinCANInterfaceType::NetworkTCP, PropertyFlags::ReadWrite | PropertyFlags::Store,
16✔
56
      [this](MarklinCANInterfaceType /*value*/)
8✔
57
      {
UNCOV
58
        typeChanged();
×
59
      }}
×
60
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
61
  , interface{this, "interface", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
62
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
16✔
63
  , baudrate{this, "baudrate", 115'200, PropertyFlags::ReadWrite | PropertyFlags::Store}
8✔
64
  , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
8✔
65
  , marklinCAN{this, "marklin_can", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
8✔
66
  , marklinCANNodeList{this, "marklin_can_node_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
8✔
67
  , marklinCANLocomotiveList{this, "marklin_can_locomotive_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
16✔
68
{
69
  name = "M\u00E4rklin CAN";
8✔
70
  marklinCAN.setValueInternal(std::make_shared<MarklinCAN::Settings>(*this, marklinCAN.name()));
8✔
71
  marklinCANNodeList.setValueInternal(std::make_shared<MarklinCANNodeList>(*this, marklinCANNodeList.name()));
8✔
72
  marklinCANLocomotiveList.setValueInternal(std::make_shared<MarklinCANLocomotiveList>(*this, marklinCANLocomotiveList.name()));
8✔
73

74
  Attributes::addDisplayName(type, DisplayName::Interface::type);
8✔
75
  Attributes::addEnabled(type, !online);
8✔
76
  Attributes::addValues(type, marklinCANInterfaceTypeValues);
8✔
77
  m_interfaceItems.insertBefore(type, notes);
8✔
78

79
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
8✔
80
  Attributes::addEnabled(hostname, !online);
8✔
81
  Attributes::addVisible(hostname, false);
8✔
82
  m_interfaceItems.insertBefore(hostname, notes);
8✔
83

84
  Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
8✔
85
  Attributes::addEnabled(interface, !online);
8✔
86
  Attributes::addVisible(interface, false);
8✔
87
  m_interfaceItems.insertBefore(interface, notes);
8✔
88

89
  Attributes::addEnabled(device, !online);
8✔
90
  Attributes::addVisible(device, false);
8✔
91
  m_interfaceItems.insertBefore(device, notes);
8✔
92

93
  Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
8✔
94
  Attributes::addEnabled(baudrate, !online);
8✔
95
  Attributes::addVisible(baudrate, false);
8✔
96
  m_interfaceItems.insertBefore(baudrate, notes);
8✔
97

98
  Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
8✔
99
  Attributes::addEnabled(flowControl, !online);
8✔
100
  Attributes::addValues(flowControl, SerialFlowControlValues);
8✔
101
  Attributes::addVisible(flowControl, false);
8✔
102
  m_interfaceItems.insertBefore(flowControl, notes);
8✔
103

104
  Attributes::addDisplayName(marklinCAN, DisplayName::Hardware::marklinCAN);
8✔
105
  m_interfaceItems.insertBefore(marklinCAN, notes);
8✔
106

107
  m_interfaceItems.insertBefore(marklinCANNodeList, notes);
8✔
108

109
  m_interfaceItems.insertBefore(marklinCANLocomotiveList, notes);
8✔
110

111
  m_interfaceItems.insertBefore(decoders, notes);
8✔
112

113
  m_interfaceItems.insertBefore(inputs, notes);
8✔
114

115
  m_interfaceItems.insertBefore(outputs, notes);
8✔
116

117
  typeChanged();
8✔
118
}
8✔
119

120
std::span<const DecoderProtocol> MarklinCANInterface::decoderProtocols() const
10✔
121
{
122
  static constexpr std::array<DecoderProtocol, 4> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong, DecoderProtocol::MFX, DecoderProtocol::Motorola};
123
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
20✔
124
}
125

126
std::span<const uint8_t> MarklinCANInterface::decoderSpeedSteps(DecoderProtocol protocol) const
5✔
127
{
128
  static constexpr std::array<uint8_t, 2> dccLongSpeedSteps{{28, 128}}; // 14 not supported for long addresses
129

130
  switch(protocol)
5✔
131
  {
UNCOV
132
    case DecoderProtocol::DCCLong:
×
133
      return dccLongSpeedSteps;
×
134

135
    default:
5✔
136
      return DecoderController::decoderSpeedSteps(protocol);
5✔
137
  }
138
}
139

140
void MarklinCANInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
4✔
141
{
142
  if(m_kernel)
4✔
UNCOV
143
    m_kernel->decoderChanged(decoder, changes, functionNumber);
×
144
}
4✔
145

146
std::span<const InputChannel> MarklinCANInterface::inputChannels() const
8✔
147
{
148
  static const auto values = makeArray(InputChannel::Input);
149
  return values;
8✔
150
}
151

UNCOV
152
std::pair<uint32_t, uint32_t> MarklinCANInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
153
{
UNCOV
154
  return {MarklinCAN::Kernel::s88AddressMin, MarklinCAN::Kernel::s88AddressMax};
×
155
}
156

157
std::span<const OutputChannel> MarklinCANInterface::outputChannels() const
24✔
158
{
159
  static const auto values = makeArray(OutputChannel::AccessoryMotorola, OutputChannel::AccessoryDCC);
160
  return values;
24✔
161
}
162

UNCOV
163
bool MarklinCANInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
164
{
UNCOV
165
  const auto address = std::get<OutputAddress>(location).address;
×
166
  return
167
    m_kernel &&
×
168
    inRange(address, outputAddressMinMax(channel)) &&
×
169
    m_kernel->setOutput(channel, static_cast<uint16_t>(address), std::get<OutputPairValue>(value));
×
170
}
171

172
bool MarklinCANInterface::setOnline(bool& value, bool simulation)
×
173
{
174
  if(!m_kernel && value)
×
175
  {
176
    try
177
    {
178
      if(simulation)
×
179
      {
180
        m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SimulationIOHandler>(id.value(), marklinCAN->config());
×
181
      }
182
      else
183
      {
184
        switch(type.value())
×
185
        {
186
          case MarklinCANInterfaceType::NetworkTCP:
×
187
            m_kernel = MarklinCAN::Kernel::create<MarklinCAN::TCPIOHandler>(id.value(), marklinCAN->config(), hostname.value());
×
188
            break;
×
189

190
          case MarklinCANInterfaceType::NetworkUDP:
×
191
            m_kernel = MarklinCAN::Kernel::create<MarklinCAN::UDPIOHandler>(id.value(), marklinCAN->config(), hostname.value());
×
192
            break;
×
193

194
          case MarklinCANInterfaceType::SocketCAN:
×
195
#ifdef __linux__
196
            m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SocketCANIOHandler>(id.value(), marklinCAN->config(), interface.value());
×
197
            break;
×
198
#else
199
            setState(InterfaceState::Error);
200
            Log::log(*this, LogMessage::C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX);
201
            return false;
202
#endif
203
          case MarklinCANInterfaceType::Serial:
×
204
            m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SerialIOHandler>(id.value(), marklinCAN->config(), device.value(), baudrate.value(), flowControl.value());
×
205
            break;
×
206
        }
207
      }
208
      assert(m_kernel);
×
209

210
      setState(InterfaceState::Initializing);
×
211

212
      m_kernel->setOnStarted(
×
213
        [this]()
×
214
        {
215
          setState(InterfaceState::Online);
×
216
          Attributes::setEnabled(marklinCANLocomotiveList->reload, true);
×
217
        });
×
218
      m_kernel->setOnError(
×
219
        [this]()
×
220
        {
221
          setState(InterfaceState::Error);
×
222
          online = false; // communication no longer possible
×
223
        });
×
224
      m_kernel->setOnNodeChanged(
×
225
        [this](const MarklinCAN::Node& node)
×
226
        {
227
          marklinCANNodeList->update(node);
×
228
        });
×
229
      m_kernel->setOnLocomotiveListChanged(
×
230
        [this](const std::shared_ptr<MarklinCAN::LocomotiveList>& list)
×
231
        {
232
          marklinCANLocomotiveList->setData(list);
×
233
        });
×
234

235
      m_kernel->setDecoderController(this);
×
236
      m_kernel->setInputController(this);
×
237
      m_kernel->setOutputController(this);
×
238

239
      m_kernel->start();
×
240

241
      m_marklinCANPropertyChanged = marklinCAN->propertyChanged.connect(
×
242
        [this](BaseProperty& /*property*/)
×
243
        {
244
          m_kernel->setConfig(marklinCAN->config());
×
245
        });
×
246

247
      Attributes::setEnabled({type, hostname, interface, device, baudrate, flowControl}, false);
×
248
    }
249
    catch(const LogMessageException& e)
×
250
    {
251
      setState(InterfaceState::Offline);
×
252
      Log::log(*this, e.message(), e.args());
×
253
      return false;
×
254
    }
×
255
  }
256
  else if(m_kernel && !value)
×
257
  {
258
    Attributes::setEnabled({type, hostname, interface, device, baudrate, flowControl}, true);
×
259
    Attributes::setEnabled(marklinCANLocomotiveList->reload, false);
×
260

261
    marklinCANNodeList->clear();
×
262
    marklinCANLocomotiveList->clear();
×
263

264
    m_marklinCANPropertyChanged.disconnect();
×
265

266
    m_kernel->stop();
×
267
    EventLoop::deleteLater(m_kernel.release());
×
268

269
    if(status->state != InterfaceState::Error)
×
270
      setState(InterfaceState::Offline);
×
271
  }
272
  return true;
×
273
}
274

275
void MarklinCANInterface::addToWorld()
8✔
276
{
277
  Interface::addToWorld();
8✔
278
  DecoderController::addToWorld();
8✔
279
  InputController::addToWorld(inputListColumns);
8✔
280
  OutputController::addToWorld(outputListColumns);
8✔
281
}
8✔
282

283
void MarklinCANInterface::loaded()
×
284
{
285
  Interface::loaded();
×
286

287
  typeChanged();
×
288
}
×
289

290
void MarklinCANInterface::destroying()
8✔
291
{
292
  OutputController::destroying();
8✔
293
  InputController::destroying();
8✔
294
  DecoderController::destroying();
8✔
295
  Interface::destroying();
8✔
296
}
8✔
297

298
void MarklinCANInterface::worldEvent(WorldState state, WorldEvent event)
×
299
{
300
  Interface::worldEvent(state, event);
×
301

302
  if(m_kernel)
×
303
  {
304
    switch(event)
×
305
    {
306
      case WorldEvent::PowerOff:
×
307
        m_kernel->systemStop();
×
308
        break;
×
309

310
      case WorldEvent::PowerOn:
×
311
        m_kernel->systemGo();
×
312
        m_kernel->systemHalt();
×
313
        break;
×
314

315
      case WorldEvent::Stop:
×
316
        m_kernel->systemHalt();
×
317
        break;
×
318

319
      case WorldEvent::Run:
×
320
        m_kernel->systemGo();
×
321
        break;
×
322

323
      default:
×
324
        break;
×
325
    }
326
  }
327
}
×
328

329
void MarklinCANInterface::typeChanged()
8✔
330
{
331
  Attributes::setVisible(hostname, isNetwork(type));
8✔
332
  Attributes::setVisible(interface, type == MarklinCANInterfaceType::SocketCAN);
8✔
333
  Attributes::setVisible({device, baudrate, flowControl}, type == MarklinCANInterfaceType::Serial);
8✔
334
}
8✔
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