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

traintastic / traintastic / 23669027368

27 Mar 2026 09:50PM UTC coverage: 26.198% (+0.02%) from 26.176%
23669027368

push

github

reinder
Merge remote-tracking branch 'origin/master' into cbus

11 of 144 new or added lines in 34 files covered. (7.64%)

1 existing line in 1 file now uncovered.

8256 of 31514 relevant lines covered (26.2%)

182.55 hits per line

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

0.0
/server/src/hardware/protocol/cbus/cbuskernel.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 "cbuskernel.hpp"
23
#include "cbusmessages.hpp"
24
#include "cbustostring.hpp"
25
#include "simulator/cbussimulator.hpp"
26
#include "../dcc/dcc.hpp"
27
#include "../../../core/eventloop.hpp"
28
#include "../../../log/log.hpp"
29
#include "../../../log/logmessageexception.hpp"
30
#include "../../../utils/inrange.hpp"
31
#include "../../../utils/setthreadname.hpp"
32

33
namespace {
34

35
using namespace std::chrono_literals;
36

37
static constexpr auto queryNodeNumberTimeout = 100ms;
38
static constexpr auto readNodeParameterTimeout = 50ms;
39
static constexpr auto requestCommandStationStatusTimeout = 100ms;
40

41
constexpr uint16_t makeAddressKey(uint16_t address, bool longAddress)
×
42
{
43
  return longAddress ? (0xC000 | (address & 0x3FFF)) : (address & 0x7F);
×
44
}
45

46
constexpr CBUS::SetEngineSessionMode::SpeedMode toSpeedMode(uint8_t speedSteps)
×
47
{
48
  using enum CBUS::SetEngineSessionMode::SpeedMode;
49

50
  switch(speedSteps)
×
51
  {
52
    case 14:
×
53
      return SpeedMode14;
×
54

55
    case 28:
×
56
      return SpeedMode28;
×
57

58
    default:
×
59
      return SpeedMode128;
×
60
  }
61
}
62

63
}
64

65
namespace CBUS {
66

67
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
×
68
  : KernelBase(std::move(logId_))
×
69
  , m_simulation{simulation}
×
70
  , m_initializationTimer{ioContext()}
×
71
  , m_config{config}
×
72
  , m_engineKeepAliveTimer{ioContext()}
×
73
  , m_dccAccessoryTimer{ioContext()}
×
74
{
75
  assert(isEventLoopThread());
×
76
}
×
77

78
void Kernel::setConfig(const Config& config)
×
79
{
80
  assert(isEventLoopThread());
×
81

NEW
82
  boost::asio::post(m_ioContext,
×
83
    [this, newConfig=config]()
×
84
    {
85
      m_config = newConfig;
×
86
    });
×
87
}
×
88

89
void Kernel::start()
×
90
{
91
  assert(isEventLoopThread());
×
92
  assert(m_ioHandler);
×
93

94
  m_thread = std::thread(
×
95
    [this]()
×
96
    {
97
      setThreadName("cbus");
×
98
      auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
×
99
      m_ioContext.run();
×
100
    });
×
101

NEW
102
  boost::asio::post(m_ioContext,
×
103
    [this]()
×
104
    {
105
      try
106
      {
107
        m_ioHandler->start();
×
108
      }
109
      catch(const LogMessageException& e)
×
110
      {
111
        EventLoop::call(
×
112
          [this, e]()
×
113
          {
114
            Log::log(logId, e.message(), e.args());
×
115
            error();
×
116
          });
×
117
        return;
×
118
      }
×
119
    });
120
}
×
121

122
void Kernel::stop()
×
123
{
124
  assert(isEventLoopThread());
×
125

NEW
126
  boost::asio::post(m_ioContext,
×
127
    [this]()
×
128
    {
129
      m_ioHandler->stop();
×
130
    });
×
131

132
  m_ioContext.stop();
×
133

134
  m_thread.join();
×
135
}
×
136

137
void Kernel::started()
×
138
{
139
  assert(isKernelThread());
×
140

141
  nextState();
×
142
}
×
143

144
void Kernel::receive(uint8_t canId, const Message& message)
×
145
{
146
  assert(isKernelThread());
×
147

148
  if(m_config.debugLogRXTX)
×
149
  {
150
    EventLoop::call(
×
151
      [this, msg=toString(message)]()
×
152
      {
153
        Log::log(logId, LogMessage::D2002_RX_X, msg);
×
154
      });
×
155
  }
156

157
  switch(message.opCode)
×
158
  {
159
    case OpCode::TOF:
×
160
      m_trackOn = false;
×
161
      if(onTrackOff) [[likely]]
×
162
      {
163
        EventLoop::call(onTrackOff);
×
164
      }
165
      break;
×
166

167
    case OpCode::TON:
×
168
      m_trackOn = true;
×
169
      if(onTrackOn) [[likely]]
×
170
      {
171
        EventLoop::call(onTrackOn);
×
172
      }
173
      break;
×
174

175
    case OpCode::ESTOP:
×
176
      if(onEmergencyStop) [[likely]]
×
177
      {
178
        EventLoop::call(onEmergencyStop);
×
179
      }
180
      break;
×
181

182
    case OpCode::ASON:
×
183
    {
184
      const auto& ason = static_cast<const AccessoryShortOn&>(message);
×
185
      EventLoop::call(
×
186
        [this, eventNumber=ason.deviceNumber()]()
×
187
        {
188
          if(onShortEvent) [[likely]]
×
189
          {
190
            onShortEvent(eventNumber, true);
×
191
          }
192
        });
×
193
      break;
×
194
    }
195
    case OpCode::ASOF:
×
196
    {
197
      const auto& asof = static_cast<const AccessoryShortOff&>(message);
×
198
      EventLoop::call(
×
199
        [this, eventNumber=asof.deviceNumber()]()
×
200
        {
201
          if(onShortEvent) [[likely]]
×
202
          {
203
            onShortEvent(eventNumber, false);
×
204
          }
205
        });
×
206
      break;
×
207
    }
208
    case OpCode::ERR:
×
209
    {
210
      switch(static_cast<const CommandStationErrorMessage&>(message).errorCode)
×
211
      {
212
        using enum DCCErr;
213

214
        case LocoStackFull:
×
215
        {
216
          const auto& err = static_cast<const CommandStationLocoStackFullError&>(message);
×
217
          const auto key = makeAddressKey(err.address(), err.isLongAddress());
×
218
          if(m_engineGLOCs.contains(key))
×
219
          {
220
            m_engineGLOCs.erase(key);
×
221
            // FIXME: log error
222
          }
223
          break;
×
224
        }
225
        case SessionCancelled:
×
226
          if(auto it = std::find_if(m_engines.begin(), m_engines.end(),
×
227
            [session=static_cast<const CommandStationSessionCancelled&>(message).session()](const auto& item)
×
228
            {
229
              return item.second.session && *item.second.session == session;
×
230
            }); it != m_engines.end())
×
231
          {
232
            it->second.session = std::nullopt;
×
233

234
            if(m_engineKeepAliveTimerActive && m_engineKeepAliveSession == *it->second.session)
×
235
            {
236
              restartEngineKeepAliveTimer();
×
237
            }
238

239
            EventLoop::call(
×
240
              [this, key=it->first]()
×
241
              {
242
                if(onEngineSessionCancelled) [[likely]]
×
243
                {
244
                  onEngineSessionCancelled(key & 0x3FFF, (key & 0xC000) == 0xC000);
×
245
                }
246
              });
×
247
          }
248
          break;
×
249

250
        case LocoAddressTaken:
×
251
        case SessionNotPresent:
252
        case ConsistEmpty:
253
        case LocoNotFound:
254
        case CANBusError:
255
        case InvalidRequest:
256
          break;
×
257
      }
258
      break;
×
259
    }
260
    case OpCode::ACON:
×
261
    {
262
      const auto& acon = static_cast<const AccessoryOn&>(message);
×
263
      EventLoop::call(
×
264
        [this, nodeNumber=acon.nodeNumber(), eventNumber=acon.eventNumber()]()
×
265
        {
266
          if(onLongEvent) [[likely]]
×
267
          {
268
            onLongEvent(nodeNumber, eventNumber, true);
×
269
          }
270
        });
×
271
      break;
×
272
    }
273
    case OpCode::ACOF:
×
274
    {
275
      const auto& acof = static_cast<const AccessoryOff&>(message);
×
276
      EventLoop::call(
×
277
        [this, nodeNumber=acof.nodeNumber(), eventNumber=acof.eventNumber()]()
×
278
        {
279
          if(onLongEvent) [[likely]]
×
280
          {
281
            onLongEvent(nodeNumber, eventNumber, false);
×
282
          }
283
        });
×
284
      break;
×
285
    }
286
    case OpCode::PARAN:
×
287
      if(m_state == State::ReadNodeParameters && !m_readNodeParameters.empty())
×
288
      {
289
        const auto& rqnpn = m_readNodeParameters.front();
×
290
        const auto& paran = static_cast<const NodeParameterResponse&>(message);
×
291

292
        if(rqnpn.nodeNumber() == paran.nodeNumber() && rqnpn.parameter == paran.parameter)
×
293
        {
294
          m_readNodeParameters.pop();
×
295
          m_initializationTimer.cancel();
×
296

297
          EventLoop::call(
×
298
            [this, canId, paran]()
×
299
            {
300
              if(onNodeParameterResponse) [[likely]]
×
301
              {
302
                onNodeParameterResponse(canId, paran.nodeNumber(), paran.parameter, paran.value);
×
303
              }
304
            });
×
305

306
          readNodeParameter();
×
307
        }
308
      }
309
      break;
×
310

311
    case OpCode::PNN:
×
312
      if(m_state == State::QueryNodes)
×
313
      {
314
        restartInitializationTimer(queryNodeNumberTimeout);
×
315

316
        const auto& pnn = static_cast<const PresenceOfNode&>(message);
×
317

318
        m_readNodeParameters.emplace(ReadNodeParameter(pnn.nodeNumber(), NodeParameter::VersionMajor));
×
319
        m_readNodeParameters.emplace(ReadNodeParameter(pnn.nodeNumber(), NodeParameter::VersionMinor));
×
320
        m_readNodeParameters.emplace(ReadNodeParameter(pnn.nodeNumber(), NodeParameter::BetaReleaseCode));
×
321

322
        EventLoop::call(
×
323
          [this, canId, pnn]()
×
324
          {
325
            if(onPresenceOfNode) [[likely]]
×
326
            {
327
              onPresenceOfNode(canId, pnn.nodeNumber(), pnn.manufacturerId, pnn.moduleId);
×
328
            }
329
          });
×
330
      }
331
      break;
×
332

333
    case OpCode::PLOC:
×
334
    {
335
      const auto& ploc = static_cast<const EngineReport&>(message);
×
336
      const auto key = makeAddressKey(ploc.address(), ploc.isLongAddress());
×
337
      if(m_engineGLOCs.contains(key))
×
338
      {
339
        m_engineGLOCs.erase(key);
×
340

341
        if(auto it = m_engines.find(key); it != m_engines.end())
×
342
        {
343
          auto& engine = it->second;
×
344
          engine.session = ploc.session;
×
345

346
          sendSetEngineSessionMode(ploc.session, engine.speedSteps);
×
347
          sendSetEngineSpeedDirection(ploc.session, engine.speed, engine.directionForward);
×
348

349
          for(const auto& [number, value] : engine.functions)
×
350
          {
351
            sendSetEngineFunction(ploc.session, number, value);
×
352
          }
353

354
          engine.lastCommand = std::chrono::steady_clock::now();
×
355

356
          if(!m_engineKeepAliveTimerActive)
×
357
          {
358
            restartEngineKeepAliveTimer();
×
359
          }
360
        }
361
        else // we're no longer in need of control (rare but possible)
362
        {
363
          send(ReleaseEngine(ploc.session));
×
364
        }
365
      }
366
      break;
×
367
    }
368
    case OpCode::STAT:
×
369
      if(m_state == State::GetCommandStationStatus)
×
370
      {
371
        m_initializationTimer.cancel();
×
372
        nextState();
×
373
      }
374
      break;
×
375

376
    default:
×
377
      break;
×
378
  }
379
}
×
380

381
void Kernel::trackOff()
×
382
{
383
  assert(isEventLoopThread());
×
384

NEW
385
  boost::asio::post(m_ioContext,
×
386
    [this]()
×
387
    {
388
      if(m_trackOn)
×
389
      {
390
        send(RequestTrackOff());
×
391
      }
392
    });
×
393
}
×
394

395
void Kernel::trackOn()
×
396
{
397
  assert(isEventLoopThread());
×
398

NEW
399
  boost::asio::post(m_ioContext,
×
400
    [this]()
×
401
    {
402
      if(!m_trackOn)
×
403
      {
404
        send(RequestTrackOn());
×
405
      }
406
    });
×
407
}
×
408

409
void Kernel::requestEmergencyStop()
×
410
{
411
  assert(isEventLoopThread());
×
412

NEW
413
  boost::asio::post(m_ioContext,
×
414
    [this]()
×
415
    {
416
      send(RequestEmergencyStop());
×
417
    });
×
418
}
×
419

420
void Kernel::setEngineSpeedDirection(uint16_t address, bool longAddress, uint8_t speedStep, uint8_t speedSteps, bool eStop, bool directionForward)
×
421
{
422
  assert(isEventLoopThread());
×
423

424
  const uint8_t speed = eStop ? 1 : (speedStep > 0 ? speedStep + 1 : 0);
×
425

NEW
426
  boost::asio::post(m_ioContext,
×
427
    [this, address, longAddress, speed, speedSteps, directionForward]()
×
428
    {
429
      auto& engine = m_engines[makeAddressKey(address, longAddress)];
×
430
      const bool speedStepsChanged = engine.speedSteps != speedSteps;
×
431
      engine.speedSteps = speedSteps;
×
432
      engine.speed = speed;
×
433
      engine.directionForward = directionForward;
×
434

435
      if(engine.session) // we're in control
×
436
      {
437
        if(speedStepsChanged)
×
438
        {
439
          sendSetEngineSessionMode(*engine.session, engine.speedSteps);
×
440
        }
441
        sendSetEngineSpeedDirection(*engine.session, engine.speed, engine.directionForward);
×
442

443
        engine.lastCommand = std::chrono::steady_clock::now();
×
444

445
        if(!m_engineKeepAliveTimerActive || (m_engineKeepAliveTimerActive && m_engineKeepAliveSession == *engine.session))
×
446
        {
447
          restartEngineKeepAliveTimer();
×
448
        }
449
      }
450
      else // take control
451
      {
452
        sendGetEngineSession(address, longAddress);
×
453
      }
454
    });
×
455
}
×
456

457
void Kernel::setEngineFunction(uint16_t address, bool longAddress, uint8_t number, bool value)
×
458
{
459
  assert(isEventLoopThread());
×
460

NEW
461
  boost::asio::post(m_ioContext,
×
462
    [this, address, longAddress, number, value]()
×
463
    {
464
      auto& engine = m_engines[makeAddressKey(address, longAddress)];
×
465
      engine.functions[number] = value;
×
466
      if(engine.session) // we're in control
×
467
      {
468
        sendSetEngineFunction(*engine.session, number, value);
×
469

470
        engine.lastCommand = std::chrono::steady_clock::now();
×
471

472
        if(!m_engineKeepAliveTimerActive || (m_engineKeepAliveTimerActive && m_engineKeepAliveSession == *engine.session))
×
473
        {
474
          restartEngineKeepAliveTimer();
×
475
        }
476
      }
477
      else // take control
478
      {
479
        sendGetEngineSession(address, longAddress);
×
480
      }
481
    });
×
482
}
×
483

484
void Kernel::setAccessoryShort(uint16_t deviceNumber, bool on)
×
485
{
486
  assert(isEventLoopThread());
×
487

NEW
488
  boost::asio::post(m_ioContext,
×
489
    [this, deviceNumber, on]()
×
490
    {
491
      if(on)
×
492
      {
493
        send(AccessoryShortOn(Config::nodeId, deviceNumber));
×
494
      }
495
      else
496
      {
497
        send(AccessoryShortOff(Config::nodeId, deviceNumber));
×
498
      }
499
    });
×
500
}
×
501

502
void Kernel::setAccessory(uint16_t nodeNumber, uint16_t eventNumber, bool on)
×
503
{
504
  assert(isEventLoopThread());
×
505

NEW
506
  boost::asio::post(m_ioContext,
×
507
    [this, nodeNumber, eventNumber, on]()
×
508
    {
509
      if(on)
×
510
      {
511
        send(AccessoryOn(nodeNumber, eventNumber));
×
512
      }
513
      else
514
      {
515
        send(AccessoryOff(nodeNumber, eventNumber));
×
516
      }
517
    });
×
518
}
×
519

520
void Kernel::setDccAccessory(uint16_t address, bool secondOutput)
×
521
{
522
  assert(isEventLoopThread());
×
523

NEW
524
  boost::asio::post(m_ioContext,
×
525
    [this, address, secondOutput]()
×
526
    {
527
      send(RequestDCCPacket<sizeof(DCC::SetSimpleAccessory) + 1>(DCC::SetSimpleAccessory(address, secondOutput, true), Config::dccAccessoryRepeat));
×
528
      const bool wasEmpty = m_dccAccessoryQueue.empty();
×
529
      m_dccAccessoryQueue.emplace(std::make_pair(
×
530
        std::chrono::steady_clock::now() + m_config.dccAccessorySwitchTime,
×
531
        DCC::SetSimpleAccessory(address, secondOutput, false)
×
532
      ));
533
      if(wasEmpty)
×
534
      {
535
        startDccAccessoryTimer();
×
536
      }
537
    });
×
538
}
×
539

540
void Kernel::setDccAdvancedAccessoryValue(uint16_t address, uint8_t aspect)
×
541
{
542
  assert(isEventLoopThread());
×
543

NEW
544
  boost::asio::post(m_ioContext,
×
545
    [this, address, aspect]()
×
546
    {
547
      send(RequestDCCPacket<sizeof(DCC::SetAdvancedAccessoryValue) + 1>(DCC::SetAdvancedAccessoryValue(address, aspect), Config::dccExtRepeat));
×
548
    });
×
549
}
×
550

551
bool Kernel::send(std::vector<uint8_t> message)
×
552
{
553
  assert(isEventLoopThread());
×
554

555
  if(!inRange<size_t>(message.size(), 1, 8))
×
556
  {
557
    return false;
×
558
  }
559

NEW
560
  boost::asio::post(m_ioContext,
×
561
    [this, msg=std::move(message)]()
×
562
    {
563
      send(*reinterpret_cast<const Message*>(msg.data()));
×
564
    });
×
565

566
  return true;
×
567
}
568

569
bool Kernel::sendDCC(std::vector<uint8_t> dccPacket, uint8_t repeat)
×
570
{
571
  assert(isEventLoopThread());
×
572

573
  if(!inRange<size_t>(dccPacket.size(), 2, 5) || repeat == 0)
×
574
  {
575
    return false;
×
576
  }
577

578
  dccPacket.emplace_back(DCC::calcChecksum(dccPacket));
×
579

NEW
580
  boost::asio::post(m_ioContext,
×
581
    [this, packet=std::move(dccPacket), repeat]()
×
582
    {
583
      switch(packet.size())
×
584
      {
585
        case 3:
×
586
          send(RequestDCCPacket<3>(packet, repeat));
×
587
          break;
×
588

589
        case 4:
×
590
          send(RequestDCCPacket<4>(packet, repeat));
×
591
          break;
×
592

593
        case 5:
×
594
          send(RequestDCCPacket<5>(packet, repeat));
×
595
          break;
×
596

597
        case 6:
×
598
          send(RequestDCCPacket<6>(packet, repeat));
×
599
          break;
×
600

601
        default: [[unlikely]]
×
602
          assert(false);
×
603
          break;
604
      }
605
    });
×
606

607
  return true;
×
608
}
609

610
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
611
{
612
  assert(isEventLoopThread());
×
613
  assert(handler);
×
614
  assert(!m_ioHandler);
×
615
  m_ioHandler = std::move(handler);
×
616
}
×
617

618
void Kernel::send(const Message& message)
×
619
{
620
  assert(isKernelThread());
×
621

622
  if(m_config.debugLogRXTX)
×
623
  {
624
    EventLoop::call(
×
625
      [this, msg=toString(message)]()
×
626
      {
627
        Log::log(logId, LogMessage::D2001_TX_X, msg);
×
628
      });
×
629
  }
630

631
  if(auto ec = m_ioHandler->send(message); ec)
×
632
  {
633
    (void)ec; // FIXME: handle error
634
  }
635
}
×
636

637
void Kernel::sendGetEngineSession(uint16_t address, bool longAddress)
×
638
{
639
  assert(isKernelThread());
×
640
  const auto key = makeAddressKey(address, longAddress);
×
641
  if(!m_engineGLOCs.contains(key))
×
642
  {
643
    m_engineGLOCs.emplace(key);
×
644
    send(GetEngineSession(address, longAddress, GetEngineSession::Mode::Steal));
×
645
  }
646
}
×
647

648
void Kernel::sendSetEngineSessionMode(uint8_t session, uint8_t speedSteps)
×
649
{
650
  assert(isKernelThread());
×
651
  // FIXME: what to do with: serviceMode and soundControlMode?
652
  send(SetEngineSessionMode(session, toSpeedMode(speedSteps), false, false));
×
653
}
×
654

655
void Kernel::sendSetEngineSpeedDirection(uint8_t session, uint8_t speed, bool directionForward)
×
656
{
657
  assert(isKernelThread());
×
658
  send(SetEngineSpeedDirection(session, speed, directionForward));
×
659
}
×
660

661
void Kernel::sendSetEngineFunction(uint8_t session, uint8_t number, bool value)
×
662
{
663
  assert(isKernelThread());
×
664
  if(value)
×
665
  {
666
    send(SetEngineFunctionOn(session, number));
×
667
  }
668
  else
669
  {
670
    send(SetEngineFunctionOff(session, number));
×
671
  }
672
}
×
673

674
void Kernel::changeState(State value)
×
675
{
676
  assert(isKernelThread());
×
677
  assert(m_state != value);
×
678

679
  m_state = value;
×
680

681
  switch(m_state)
×
682
  {
683
    case State::Initial: [[unlikely]]
×
684
      assert(false);
×
685
      break;
686

687
    case State::QueryNodes:
×
688
      send(QueryNodeNumber());
×
689
      restartInitializationTimer(queryNodeNumberTimeout);
×
690
      break;
×
691

692
    case State::ReadNodeParameters:
×
693
      readNodeParameter();
×
694
      break;
×
695

696
    case State::GetCommandStationStatus:
×
697
      send(RequestCommandStationStatus());
×
698
      restartInitializationTimer(requestCommandStationStatusTimeout);
×
699
      break;
×
700

701
    case State::Started:
×
702
      KernelBase::started();
×
703
      break;
×
704
  }
705
}
×
706

707
void Kernel::readNodeParameter()
×
708
{
709
  assert(m_state == State::ReadNodeParameters);
×
710
  if(m_readNodeParameters.empty())
×
711
  {
712
    nextState();
×
713
    return;
×
714
  }
715
  send(m_readNodeParameters.front());
×
716
  restartInitializationTimer(readNodeParameterTimeout);
×
717
}
718

719
void Kernel::restartInitializationTimer(std::chrono::milliseconds timeout)
×
720
{
721
  assert(isKernelThread());
×
722

723
  m_initializationTimer.cancel();
×
724

725
  m_initializationTimer.expires_after(timeout);
×
726
  m_initializationTimer.async_wait(
×
727
    [this](std::error_code ec)
×
728
    {
729
      if(ec)
×
730
      {
731
        return;
×
732
      }
733

734
      switch(m_state)
×
735
      {
736
        case State::QueryNodes:
×
737
          nextState();
×
738
          break;
×
739

740
        case State::ReadNodeParameters:
×
741
          m_readNodeParameters.pop();
×
742
          readNodeParameter();
×
743
          break;
×
744

745
        case State::GetCommandStationStatus:
×
746
          nextState();
×
747
          break;
×
748

749
        case State::Initial: [[unlikely]]
×
750
        case State::Started: [[unlikely]]
×
751
          assert(false);
×
752
          break;
753
      }
754
    });
755
}
×
756

757
void Kernel::restartEngineKeepAliveTimer()
×
758
{
759
  assert(isKernelThread());
×
760

761
  m_engineKeepAliveTimer.cancel();
×
762

763
  // find first expiring engine:
764
  std::chrono::steady_clock::time_point lastUpdate = std::chrono::steady_clock::time_point::max();
×
765
  for(const auto& [_, engine] : m_engines)
×
766
  {
767
    if(engine.session && engine.lastCommand < lastUpdate)
×
768
    {
769
      lastUpdate = engine.lastCommand;
×
770
      m_engineKeepAliveSession = *engine.session;
×
771
    }
772
  }
773

774
  m_engineKeepAliveTimerActive = (lastUpdate < std::chrono::steady_clock::time_point::max());
×
775

776
  if(m_engineKeepAliveTimerActive)
×
777
  {
778
    m_engineKeepAliveTimer.expires_at(lastUpdate + m_config.engineKeepAlive);
×
779
    m_engineKeepAliveTimer.async_wait(
×
780
      [this](std::error_code ec)
×
781
      {
782
        if(ec)
×
783
        {
784
          return;
×
785
        }
786

787
        send(SessionKeepAlive(m_engineKeepAliveSession));
×
788

789
        if(auto it = std::find_if(m_engines.begin(), m_engines.end(),
×
790
            [session=m_engineKeepAliveSession](const auto& item)
×
791
            {
792
              return item.second.session && *item.second.session == session;
×
793
            }); it != m_engines.end()) [[likely]]
×
794
        {
795
          it->second.lastCommand = std::chrono::steady_clock::now();
×
796
        }
797

798
        restartEngineKeepAliveTimer();
×
799
      });
800
  }
801
}
×
802

803
void Kernel::startDccAccessoryTimer()
×
804
{
805
  assert(isKernelThread());
×
806

807
  if(m_dccAccessoryQueue.empty()) [[unlikely]]
×
808
  {
809
    return;
×
810
  }
811

812
  m_dccAccessoryTimer.expires_at(m_dccAccessoryQueue.front().first);
×
813
  m_dccAccessoryTimer.async_wait(
×
814
    [this](std::error_code ec)
×
815
    {
816
      if(ec)
×
817
      {
818
        return;
×
819
      }
820

821
      send(RequestDCCPacket<sizeof(DCC::SetSimpleAccessory) + 1>(m_dccAccessoryQueue.front().second, Config::dccAccessoryRepeat));
×
822

823
      m_dccAccessoryQueue.pop();
×
824

825
      if(!m_dccAccessoryQueue.empty())
×
826
      {
827
        startDccAccessoryTimer();
×
828
      }
829
    });
830
}
831

832
}
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