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

traintastic / traintastic / 23088148108

14 Mar 2026 12:40PM UTC coverage: 26.661% (-0.2%) from 26.84%
23088148108

push

github

reinder
[cbus] added input support for short/long events

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

587 existing lines in 20 files now uncovered.

8246 of 30929 relevant lines covered (26.66%)

185.83 hits per line

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

34.63
/server/src/hardware/interface/loconetinterface.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2019-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 "loconetinterface.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 "../identification/list/identificationlist.hpp"
29
#include "../identification/identification.hpp"
30
#include "../programming/lncv/lncvprogrammer.hpp"
31
#include "../protocol/dcc/dcc.hpp"
32
#include "../protocol/loconet/kernel.hpp"
33
#include "../protocol/loconet/settings.hpp"
34
#include "../protocol/loconet/iohandler/serialiohandler.hpp"
35
#include "../protocol/loconet/iohandler/simulationiohandler.hpp"
36
#include "../protocol/loconet/iohandler/tcpbinaryiohandler.hpp"
37
#include "../protocol/loconet/iohandler/lbserveriohandler.hpp"
38
#include "../protocol/loconet/iohandler/z21iohandler.hpp"
39
#include "../../core/attributes.hpp"
40
#include "../../core/controllerlist.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 "../../utils/makearray.hpp"
49
#include "../../world/world.hpp"
50

51
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
52
constexpr auto inputListColumns = InputListColumn::Address;
53
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Address;
54
constexpr auto identificationListColumns = IdentificationListColumn::Id | IdentificationListColumn::Name | IdentificationListColumn::Interface | IdentificationListColumn::Address;
55

56
CREATE_IMPL(LocoNetInterface)
11✔
57

58
LocoNetInterface::LocoNetInterface(World& world, std::string_view _id)
11✔
59
  : Interface(world, _id)
60
  , DecoderController(*this, decoderListColumns)
61
  , InputController(static_cast<IdObject&>(*this))
62
  , OutputController(static_cast<IdObject&>(*this))
63
  , IdentificationController(static_cast<IdObject&>(*this))
64
  , type{this, "type", LocoNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
22✔
65
      [this](LocoNetInterfaceType /*value*/)
11✔
66
      {
67
        typeChanged();
×
68
      }}
×
69
  , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
22✔
70
  , baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
11✔
71
  , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
11✔
72
  , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
22✔
73
  , port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
11✔
74
  , loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
22✔
75
{
76
  name = "LocoNet";
11✔
77
  loconet.setValueInternal(std::make_shared<LocoNet::Settings>(*this, loconet.name()));
11✔
78

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

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

88
  Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
11✔
89
  Attributes::addEnabled(baudrate, !online);
11✔
90
  Attributes::addVisible(baudrate, false);
11✔
91
  m_interfaceItems.insertBefore(baudrate, notes);
11✔
92

93
  Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
11✔
94
  Attributes::addEnabled(flowControl, !online);
11✔
95
  Attributes::addValues(flowControl, SerialFlowControlValues);
11✔
96
  Attributes::addVisible(flowControl, false);
11✔
97
  m_interfaceItems.insertBefore(flowControl, notes);
11✔
98

99
  Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
11✔
100
  Attributes::addEnabled(hostname, !online);
11✔
101
  Attributes::addVisible(hostname, false);
11✔
102
  m_interfaceItems.insertBefore(hostname, notes);
11✔
103

104
  Attributes::addDisplayName(port, DisplayName::IP::port);
11✔
105
  Attributes::addEnabled(port, !online);
11✔
106
  Attributes::addVisible(port, false);
11✔
107
  m_interfaceItems.insertBefore(port, notes);
11✔
108

109
  Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
11✔
110
  m_interfaceItems.insertBefore(loconet, notes);
11✔
111

112
  m_interfaceItems.insertBefore(decoders, notes);
11✔
113

114
  m_interfaceItems.insertBefore(inputs, notes);
11✔
115

116
  m_interfaceItems.insertBefore(outputs, notes);
11✔
117

118
  m_interfaceItems.insertBefore(identifications, notes);
11✔
119

120
  typeChanged();
11✔
121
}
11✔
122

123
LocoNetInterface::~LocoNetInterface() = default;
11✔
124

125
bool LocoNetInterface::send(std::span<uint8_t> packet)
×
126
{
127
  if(m_kernel)
×
128
    return m_kernel->send(packet);
×
129
  return false;
×
130
}
131

132
bool LocoNetInterface::immPacket(std::span<uint8_t> dccPacket, uint8_t repeat)
×
133
{
134
  if(m_kernel)
×
135
    return m_kernel->immPacket(dccPacket, repeat);
×
136
  return false;
×
137
}
138

139
void LocoNetInterface::readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback)
×
140
{
141
  if(m_kernel)
×
142
  {
143
    m_kernel->readLNCV(moduleId, address, lncv, std::move(callback));
×
144
  }
145
}
×
146

147
std::span<const DecoderProtocol> LocoNetInterface::decoderProtocols() const
10✔
148
{
149
  static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
150
  return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
20✔
151
}
152

153
std::pair<uint16_t, uint16_t> LocoNetInterface::decoderAddressMinMax(DecoderProtocol protocol) const
5✔
154
{
155
  if(protocol == DecoderProtocol::DCCLong)
5✔
156
  {
157
    return {DCC::addressLongStart, DCC::addressLongMax};
×
158
  }
159
  return DecoderController::decoderAddressMinMax(protocol);
5✔
160
}
161

162
void LocoNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
4✔
163
{
164
  if(m_kernel)
4✔
165
    m_kernel->decoderChanged(decoder, changes, functionNumber);
×
166
}
4✔
167

168
std::span<const InputChannel> LocoNetInterface::inputChannels() const
11✔
169
{
170
  static const auto values = makeArray(InputChannel::Input);
171
  return values;
11✔
172
}
173

174
std::pair<uint32_t, uint32_t> LocoNetInterface::inputAddressMinMax(InputChannel /*channel*/) const
×
175
{
176
  return {LocoNet::Kernel::inputAddressMin, LocoNet::Kernel::inputAddressMax};
×
177
}
178

179
void LocoNetInterface::inputSimulateChange(InputChannel channel, const InputLocation& location, SimulateInputAction action)
×
180
{
181
  assert(std::holds_alternative<InputAddress>(location));
×
182
  const auto address = std::get<InputAddress>(location).address;
×
183
  if(m_kernel && inRange(address, inputAddressMinMax(channel)))
×
UNCOV
184
    m_kernel->simulateInputChange(address, action);
×
UNCOV
185
}
×
186

187
std::span<const OutputChannel> LocoNetInterface::outputChannels() const
40✔
188
{
189
  static const auto values = makeArray(OutputChannel::Accessory, OutputChannel::DCCext);
190
  return values;
40✔
191
}
192

193
std::pair<uint32_t, uint32_t> LocoNetInterface::outputAddressMinMax(OutputChannel channel) const
8✔
194
{
195
  if(channel == OutputChannel::Accessory)
8✔
196
  {
197
    return {LocoNet::Kernel::accessoryOutputAddressMin, LocoNet::Kernel::accessoryOutputAddressMax};
8✔
198
  }
UNCOV
199
  return OutputController::outputAddressMinMax(channel);
×
200
}
201

202
bool LocoNetInterface::setOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
203
{
204
  const auto address = std::get<OutputAddress>(location).address;
×
205
  return
206
      m_kernel &&
×
UNCOV
207
      inRange(address, outputAddressMinMax(channel)) &&
×
UNCOV
208
      m_kernel->setOutput(channel, static_cast<uint16_t>(address), value);
×
209
}
210

211
std::pair<uint32_t, uint32_t> LocoNetInterface::identificationAddressMinMax(uint32_t /*channel*/) const
6✔
212
{
213
  return {LocoNet::Kernel::identificationAddressMin, LocoNet::Kernel::identificationAddressMax};
6✔
214
}
215

UNCOV
216
void LocoNetInterface::identificationEvent(uint32_t channel, uint32_t address, IdentificationEventType eventType, uint16_t identifier, Direction direction, uint8_t category)
×
217
{
218
  // OPC_MULTI_SENSE direction:
219
  if(direction == Direction::Unknown && (eventType == IdentificationEventType::Present || eventType == IdentificationEventType::Absent))
×
220
  {
221
    constexpr uint32_t addressDirectionMask = 0x800;
×
222

223
    if(auto it = m_identifications.find({channel, address}); it != m_identifications.end() )
×
224
    {
225
      switch(it->second->opcMultiSenseDirection.value())
×
226
      {
UNCOV
227
        case OPCMultiSenseDirection::None:
×
228
          break;
×
229

230
        case OPCMultiSenseDirection::InSensorAddress:
×
UNCOV
231
          direction = Direction::Reverse;
×
232
          break;
×
233

234
        case OPCMultiSenseDirection::InTransponderAddress:
×
235
        {
236
          constexpr uint16_t identifierDirectionMask = 0x1000;
×
237
          direction = (identifier & identifierDirectionMask) ? Direction::Forward : Direction::Reverse;
×
UNCOV
238
          identifier &= ~identifierDirectionMask;
×
UNCOV
239
          break;
×
240
        }
241
      }
242
    }
243
    else if(address & addressDirectionMask)
×
244
    {
245
      address &= ~addressDirectionMask;
×
246

247
      if(it = m_identifications.find({channel, address});
×
UNCOV
248
          it != m_identifications.end() &&
×
249
          it->second->opcMultiSenseDirection == OPCMultiSenseDirection::InSensorAddress)
×
250
      {
UNCOV
251
        direction = Direction::Forward;
×
252
      }
253
    }
254
  }
255

UNCOV
256
  IdentificationController::identificationEvent(channel, address, eventType, identifier, direction, category);
×
257
}
×
258

259
bool LocoNetInterface::startLNCVProgramming(uint16_t moduleId, uint16_t moduleAddress)
×
260
{
UNCOV
261
  if(!m_kernel)
×
262
    return false;
×
263

UNCOV
264
  m_kernel->lncvStart(moduleId, moduleAddress);
×
UNCOV
265
  return true;
×
266
}
267

268
bool LocoNetInterface::readLNCV(uint16_t lncv)
×
269
{
UNCOV
270
  if(!m_kernel)
×
271
    return false;
×
272

UNCOV
273
  m_kernel->lncvRead(lncv);
×
UNCOV
274
  return true;
×
275
}
276

277
bool LocoNetInterface::writeLNCV(uint16_t lncv, uint16_t value)
×
278
{
UNCOV
279
  if(!m_kernel)
×
280
    return false;
×
281

UNCOV
282
  m_kernel->lncvWrite(lncv, value);
×
UNCOV
283
  return true;
×
284
}
285

286
bool LocoNetInterface::stopLNCVProgramming()
×
287
{
UNCOV
288
  if(!m_kernel)
×
289
    return false;
×
290

UNCOV
291
  m_kernel->lncvStop();
×
UNCOV
292
  return true;
×
293
}
294

295
bool LocoNetInterface::setOnline(bool& value, bool simulation)
×
296
{
UNCOV
297
  if(!m_kernel && value)
×
298
  {
299
    try
300
    {
301
      if(simulation)
×
302
      {
UNCOV
303
        m_kernel = LocoNet::Kernel::create<LocoNet::SimulationIOHandler>(id.value(), loconet->config());
×
304
      }
305
      else
306
      {
307
        switch(type)
×
308
        {
309
          case LocoNetInterfaceType::Serial:
×
UNCOV
310
            m_kernel = LocoNet::Kernel::create<LocoNet::SerialIOHandler>(id.value(), loconet->config(), device.value(), baudrate.value(), flowControl.value());
×
311
            break;
×
312

313
          case LocoNetInterfaceType::TCPBinary:
×
UNCOV
314
            m_kernel = LocoNet::Kernel::create<LocoNet::TCPBinaryIOHandler>(id.value(), loconet->config(), hostname.value(), port.value());
×
315
            break;
×
316

317
          case LocoNetInterfaceType::LBServer:
×
UNCOV
318
            m_kernel = LocoNet::Kernel::create<LocoNet::LBServerIOHandler>(id.value(), loconet->config(), hostname.value(), port.value());
×
319
            break;
×
320

321
          case LocoNetInterfaceType::Z21:
×
UNCOV
322
            m_kernel = LocoNet::Kernel::create<LocoNet::Z21IOHandler>(id.value(), loconet->config(), hostname.value());
×
323
            break;
×
324

UNCOV
325
          default:
×
UNCOV
326
            assert(false);
×
327
            return false;
328
        }
329
      }
330

331
      setState(InterfaceState::Initializing);
×
332

UNCOV
333
      m_kernel->setOnStarted(
×
334
        [this]()
×
335
        {
336
          setState(InterfaceState::Online);
×
337
          const auto worldState = m_world.state.value();
×
338
          m_kernel->setState(contains(worldState, WorldState::PowerOn), contains(worldState, WorldState::Run));
×
339
        });
×
UNCOV
340
      m_kernel->setOnError(
×
341
        [this]()
×
342
        {
343
          setState(InterfaceState::Error);
×
344
          online = false; // communication no longer possible
×
345
        });
×
UNCOV
346
      m_kernel->setOnStateChanged(
×
347
        [this](bool powerOn, bool run)
×
348
        {
349
          if(run && !contains(m_world.state.value(), WorldState::Run))
×
350
          {
351
            m_world.run();
×
352
          }
353
          else if(powerOn && !contains(m_world.state.value(), WorldState::PowerOn))
×
354
          {
355
            m_world.powerOn();
×
356
          }
357
          else if(!powerOn && contains(m_world.state.value(), WorldState::PowerOn))
×
358
          {
359
            m_world.powerOff();
×
360
          }
361
          else if(!run && contains(m_world.state.value(), WorldState::Run))
×
362
          {
363
            m_world.stop();
×
364
          }
365
        });
×
366
      m_kernel->setClock(m_world.clock.value());
×
367
      m_kernel->setDecoderController(this);
×
368
      m_kernel->setInputController(this);
×
UNCOV
369
      m_kernel->setOutputController(this);
×
370
      m_kernel->setIdentificationController(this);
×
371

UNCOV
372
      m_kernel->setOnLNCVReadResponse(
×
373
        [this](bool success, uint16_t lncv, uint16_t lncvValue)
×
374
        {
375
          if(auto* programmer = lncvProgrammer())
×
UNCOV
376
            programmer->readResponse(success, lncv, lncvValue);
×
377
        });
×
378

379
      m_kernel->start();
×
380

UNCOV
381
      m_loconetPropertyChanged = loconet->propertyChanged.connect(
×
382
        [this](BaseProperty& /*property*/)
×
383
        {
UNCOV
384
          m_kernel->setConfig(loconet->config());
×
385
        });
×
386

387
      Attributes::setEnabled(type, false);
×
388
      Attributes::setEnabled(device, false);
×
389
      Attributes::setEnabled(baudrate, false);
×
390
      Attributes::setEnabled(flowControl, false);
×
UNCOV
391
      Attributes::setEnabled(hostname, false);
×
392
      Attributes::setEnabled(port, false);
×
393
    }
394
    catch(const LogMessageException& e)
×
395
    {
396
      setState(InterfaceState::Offline);
×
397
      Log::log(*this, e.message(), e.args());
×
UNCOV
398
      return false;
×
399
    }
×
400
  }
401
  else if(m_kernel && !value)
×
402
  {
403
    Attributes::setEnabled(type, true);
×
404
    Attributes::setEnabled(device, true);
×
405
    Attributes::setEnabled(baudrate, true);
×
406
    Attributes::setEnabled(flowControl, true);
×
UNCOV
407
    Attributes::setEnabled(hostname, true);
×
408
    Attributes::setEnabled(port, true);
×
409

410
    m_loconetPropertyChanged.disconnect();
×
411

UNCOV
412
    m_kernel->stop();
×
413
    EventLoop::deleteLater(m_kernel.release());
×
414

UNCOV
415
    if(status->state != InterfaceState::Error)
×
416
      setState(InterfaceState::Offline);
×
417
  }
UNCOV
418
  return true;
×
419
}
420

421
void LocoNetInterface::addToWorld()
11✔
422
{
423
  Interface::addToWorld();
11✔
424
  DecoderController::addToWorld();
11✔
425
  InputController::addToWorld(inputListColumns);
11✔
426
  OutputController::addToWorld(outputListColumns);
11✔
427
  IdentificationController::addToWorld(identificationListColumns);
11✔
428
  LNCVProgrammingController::addToWorld();
11✔
429
  m_world.loconetInterfaces->add(Object::shared_ptr<LocoNetInterface>());
11✔
430
}
11✔
431

432
void LocoNetInterface::loaded()
×
433
{
434
  Interface::loaded();
×
435

UNCOV
436
  typeChanged();
×
UNCOV
437
}
×
438

439
void LocoNetInterface::destroying()
11✔
440
{
441
  m_world.loconetInterfaces->remove(Object::shared_ptr<LocoNetInterface>());
11✔
442
  LNCVProgrammingController::destroying();
11✔
443
  IdentificationController::destroying();
11✔
444
  OutputController::destroying();
11✔
445
  InputController::destroying();
11✔
446
  DecoderController::destroying();
11✔
447
  Interface::destroying();
11✔
448
}
11✔
449

450
void LocoNetInterface::worldEvent(WorldState state, WorldEvent event)
×
451
{
452
  Interface::worldEvent(state, event);
×
453

454
  if(m_kernel)
×
455
  {
456
    switch(event)
×
457
    {
UNCOV
458
      case WorldEvent::PowerOff:
×
459
      case WorldEvent::PowerOn:
460
      case WorldEvent::Stop:
461
      case WorldEvent::Run:
UNCOV
462
        m_kernel->setState(contains(state, WorldState::PowerOn), contains(state, WorldState::Run));
×
463
        break;
×
464

UNCOV
465
      default:
×
UNCOV
466
        break;
×
467
    }
468
  }
UNCOV
469
}
×
470

471
void LocoNetInterface::typeChanged()
11✔
472
{
473
  const bool serialVisible = isSerial(type);
11✔
474
  Attributes::setVisible(device, serialVisible);
11✔
475
  Attributes::setVisible(baudrate, serialVisible);
11✔
476
  Attributes::setVisible(flowControl, serialVisible);
11✔
477

478
  const bool networkVisible = isNetwork(type);
11✔
479
  Attributes::setVisible(hostname, networkVisible);
11✔
480
  Attributes::setVisible(port, networkVisible && type != LocoNetInterfaceType::Z21);
11✔
481
}
11✔
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