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

traintastic / traintastic / 23401582808

22 Mar 2026 10:58AM UTC coverage: 26.237% (-0.02%) from 26.259%
23401582808

push

github

reinder
[cbus] added more message  toString's

0 of 8 new or added lines in 1 file covered. (0.0%)

158 existing lines in 3 files now uncovered.

8246 of 31429 relevant lines covered (26.24%)

182.88 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 requestCommandStationStatusTimeout = 100ms;
39

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

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

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

54
    case 28:
×
55
      return SpeedMode28;
×
56

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

62
}
63

64
namespace CBUS {
65

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

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

81
  m_ioContext.post(
×
82
    [this, newConfig=config]()
×
83
    {
84
      m_config = newConfig;
×
85
    });
×
86
}
×
87

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

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

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

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

125
  m_ioContext.post(
×
126
    [this]()
×
127
    {
128
      m_ioHandler->stop();
×
129
    });
×
130

131
  m_ioContext.stop();
×
132

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

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

140
  nextState();
×
141
}
×
142

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

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

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

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

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

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

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

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

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

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

290
        const auto& pnn = static_cast<const PresenceOfNode&>(message);
×
291

292
        send(ReadNodeParameter(pnn.nodeNumber(), NodeParameter::VersionMajor));
×
UNCOV
293
        send(ReadNodeParameter(pnn.nodeNumber(), NodeParameter::VersionMinor));
×
294

UNCOV
295
        EventLoop::call(
×
296
          [this, canId, pnn]()
×
297
          {
298
            if(onPresenceOfNode) [[likely]]
×
299
            {
300
              onPresenceOfNode(canId, pnn.nodeNumber(), pnn.manufacturerId, pnn.moduleId);
×
301
            }
302
          });
×
303
      }
304
      break;
×
305

306
    case OpCode::PLOC:
×
307
    {
308
      const auto& ploc = static_cast<const EngineReport&>(message);
×
UNCOV
309
      const auto key = makeAddressKey(ploc.address(), ploc.isLongAddress());
×
310
      if(m_engineGLOCs.contains(key))
×
311
      {
UNCOV
312
        m_engineGLOCs.erase(key);
×
313

314
        if(auto it = m_engines.find(key); it != m_engines.end())
×
315
        {
316
          auto& engine = it->second;
×
UNCOV
317
          engine.session = ploc.session;
×
318

UNCOV
319
          sendSetEngineSessionMode(ploc.session, engine.speedSteps);
×
UNCOV
320
          sendSetEngineSpeedDirection(ploc.session, engine.speed, engine.directionForward);
×
321

UNCOV
322
          for(const auto& [number, value] : engine.functions)
×
323
          {
UNCOV
324
            sendSetEngineFunction(ploc.session, number, value);
×
325
          }
326

UNCOV
327
          engine.lastCommand = std::chrono::steady_clock::now();
×
328

UNCOV
329
          if(!m_engineKeepAliveTimerActive)
×
330
          {
UNCOV
331
            restartEngineKeepAliveTimer();
×
332
          }
333
        }
334
        else // we're no longer in need of control (rare but possible)
335
        {
336
          send(ReleaseEngine(ploc.session));
×
337
        }
338
      }
339
      break;
×
340
    }
341
    case OpCode::STAT:
×
UNCOV
342
      if(m_state == State::GetCommandStationStatus)
×
343
      {
344
        m_initializationTimer.cancel();
×
UNCOV
345
        nextState();
×
346
      }
UNCOV
347
      break;
×
348

UNCOV
349
    default:
×
350
      break;
×
351
  }
352
}
×
353

UNCOV
354
void Kernel::trackOff()
×
355
{
UNCOV
356
  assert(isEventLoopThread());
×
357

UNCOV
358
  m_ioContext.post(
×
359
    [this]()
×
360
    {
UNCOV
361
      if(m_trackOn)
×
362
      {
UNCOV
363
        send(RequestTrackOff());
×
364
      }
UNCOV
365
    });
×
366
}
×
367

UNCOV
368
void Kernel::trackOn()
×
369
{
UNCOV
370
  assert(isEventLoopThread());
×
371

UNCOV
372
  m_ioContext.post(
×
373
    [this]()
×
374
    {
UNCOV
375
      if(!m_trackOn)
×
376
      {
UNCOV
377
        send(RequestTrackOn());
×
378
      }
UNCOV
379
    });
×
380
}
×
381

UNCOV
382
void Kernel::requestEmergencyStop()
×
383
{
384
  assert(isEventLoopThread());
×
385

UNCOV
386
  m_ioContext.post(
×
387
    [this]()
×
388
    {
389
      send(RequestEmergencyStop());
×
UNCOV
390
    });
×
391
}
×
392

393
void Kernel::setEngineSpeedDirection(uint16_t address, bool longAddress, uint8_t speedStep, uint8_t speedSteps, bool eStop, bool directionForward)
×
394
{
UNCOV
395
  assert(isEventLoopThread());
×
396

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

399
  m_ioContext.post(
×
400
    [this, address, longAddress, speed, speedSteps, directionForward]()
×
401
    {
402
      auto& engine = m_engines[makeAddressKey(address, longAddress)];
×
UNCOV
403
      const bool speedStepsChanged = engine.speedSteps != speedSteps;
×
404
      engine.speedSteps = speedSteps;
×
UNCOV
405
      engine.speed = speed;
×
406
      engine.directionForward = directionForward;
×
407

408
      if(engine.session) // we're in control
×
409
      {
410
        if(speedStepsChanged)
×
411
        {
412
          sendSetEngineSessionMode(*engine.session, engine.speedSteps);
×
413
        }
414
        sendSetEngineSpeedDirection(*engine.session, engine.speed, engine.directionForward);
×
415

UNCOV
416
        engine.lastCommand = std::chrono::steady_clock::now();
×
417

UNCOV
418
        if(!m_engineKeepAliveTimerActive || (m_engineKeepAliveTimerActive && m_engineKeepAliveSession == *engine.session))
×
419
        {
UNCOV
420
          restartEngineKeepAliveTimer();
×
421
        }
422
      }
423
      else // take control
424
      {
UNCOV
425
        sendGetEngineSession(address, longAddress);
×
426
      }
UNCOV
427
    });
×
428
}
×
429

UNCOV
430
void Kernel::setEngineFunction(uint16_t address, bool longAddress, uint8_t number, bool value)
×
431
{
432
  assert(isEventLoopThread());
×
433

UNCOV
434
  m_ioContext.post(
×
435
    [this, address, longAddress, number, value]()
×
436
    {
437
      auto& engine = m_engines[makeAddressKey(address, longAddress)];
×
UNCOV
438
      engine.functions[number] = value;
×
439
      if(engine.session) // we're in control
×
440
      {
441
        sendSetEngineFunction(*engine.session, number, value);
×
442

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

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

UNCOV
457
void Kernel::setAccessoryShort(uint16_t deviceNumber, bool on)
×
458
{
UNCOV
459
  assert(isEventLoopThread());
×
460

UNCOV
461
  m_ioContext.post(
×
UNCOV
462
    [this, deviceNumber, on]()
×
463
    {
464
      if(on)
×
465
      {
466
        send(AccessoryShortOn(Config::nodeId, deviceNumber));
×
467
      }
468
      else
469
      {
UNCOV
470
        send(AccessoryShortOff(Config::nodeId, deviceNumber));
×
471
      }
UNCOV
472
    });
×
473
}
×
474

UNCOV
475
void Kernel::setAccessory(uint16_t nodeNumber, uint16_t eventNumber, bool on)
×
476
{
UNCOV
477
  assert(isEventLoopThread());
×
478

UNCOV
479
  m_ioContext.post(
×
UNCOV
480
    [this, nodeNumber, eventNumber, on]()
×
481
    {
482
      if(on)
×
483
      {
484
        send(AccessoryOn(nodeNumber, eventNumber));
×
485
      }
486
      else
487
      {
UNCOV
488
        send(AccessoryOff(nodeNumber, eventNumber));
×
489
      }
UNCOV
490
    });
×
491
}
×
492

UNCOV
493
void Kernel::setDccAccessory(uint16_t address, bool secondOutput)
×
494
{
495
  assert(isEventLoopThread());
×
496

497
  m_ioContext.post(
×
498
    [this, address, secondOutput]()
×
499
    {
500
      send(RequestDCCPacket<sizeof(DCC::SetSimpleAccessory) + 1>(DCC::SetSimpleAccessory(address, secondOutput, true), Config::dccAccessoryRepeat));
×
UNCOV
501
      const bool wasEmpty = m_dccAccessoryQueue.empty();
×
502
      m_dccAccessoryQueue.emplace(std::make_pair(
×
UNCOV
503
        std::chrono::steady_clock::now() + m_config.dccAccessorySwitchTime,
×
504
        DCC::SetSimpleAccessory(address, secondOutput, false)
×
505
      ));
UNCOV
506
      if(wasEmpty)
×
507
      {
UNCOV
508
        startDccAccessoryTimer();
×
509
      }
UNCOV
510
    });
×
511
}
×
512

UNCOV
513
void Kernel::setDccAdvancedAccessoryValue(uint16_t address, uint8_t aspect)
×
514
{
515
  assert(isEventLoopThread());
×
516

UNCOV
517
  m_ioContext.post(
×
518
    [this, address, aspect]()
×
519
    {
520
      send(RequestDCCPacket<sizeof(DCC::SetAdvancedAccessoryValue) + 1>(DCC::SetAdvancedAccessoryValue(address, aspect), Config::dccExtRepeat));
×
UNCOV
521
    });
×
522
}
×
523

524
bool Kernel::send(std::vector<uint8_t> message)
×
525
{
UNCOV
526
  assert(isEventLoopThread());
×
527

528
  if(!inRange<size_t>(message.size(), 1, 8))
×
529
  {
530
    return false;
×
531
  }
532

533
  m_ioContext.post(
×
UNCOV
534
    [this, msg=std::move(message)]()
×
535
    {
536
      send(*reinterpret_cast<const Message*>(msg.data()));
×
UNCOV
537
    });
×
538

UNCOV
539
  return true;
×
540
}
541

542
bool Kernel::sendDCC(std::vector<uint8_t> dccPacket, uint8_t repeat)
×
543
{
UNCOV
544
  assert(isEventLoopThread());
×
545

UNCOV
546
  if(!inRange<size_t>(dccPacket.size(), 2, 5) || repeat == 0)
×
547
  {
548
    return false;
×
549
  }
550

UNCOV
551
  dccPacket.emplace_back(DCC::calcChecksum(dccPacket));
×
552

553
  m_ioContext.post(
×
554
    [this, packet=std::move(dccPacket), repeat]()
×
555
    {
556
      switch(packet.size())
×
557
      {
558
        case 3:
×
UNCOV
559
          send(RequestDCCPacket<3>(packet, repeat));
×
560
          break;
×
561

562
        case 4:
×
UNCOV
563
          send(RequestDCCPacket<4>(packet, repeat));
×
564
          break;
×
565

566
        case 5:
×
UNCOV
567
          send(RequestDCCPacket<5>(packet, repeat));
×
568
          break;
×
569

UNCOV
570
        case 6:
×
UNCOV
571
          send(RequestDCCPacket<6>(packet, repeat));
×
572
          break;
×
573

574
        default: [[unlikely]]
×
UNCOV
575
          assert(false);
×
576
          break;
577
      }
UNCOV
578
    });
×
579

580
  return true;
×
581
}
582

583
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
584
{
585
  assert(isEventLoopThread());
×
UNCOV
586
  assert(handler);
×
587
  assert(!m_ioHandler);
×
UNCOV
588
  m_ioHandler = std::move(handler);
×
589
}
×
590

591
void Kernel::send(const Message& message)
×
592
{
UNCOV
593
  assert(isKernelThread());
×
594

595
  if(m_config.debugLogRXTX)
×
596
  {
UNCOV
597
    EventLoop::call(
×
598
      [this, msg=toString(message)]()
×
599
      {
UNCOV
600
        Log::log(logId, LogMessage::D2001_TX_X, msg);
×
UNCOV
601
      });
×
602
  }
603

604
  if(auto ec = m_ioHandler->send(message); ec)
×
605
  {
606
    (void)ec; // FIXME: handle error
607
  }
608
}
×
609

610
void Kernel::sendGetEngineSession(uint16_t address, bool longAddress)
×
611
{
UNCOV
612
  assert(isKernelThread());
×
613
  const auto key = makeAddressKey(address, longAddress);
×
UNCOV
614
  if(!m_engineGLOCs.contains(key))
×
615
  {
UNCOV
616
    m_engineGLOCs.emplace(key);
×
617
    send(GetEngineSession(address, longAddress, GetEngineSession::Mode::Steal));
×
618
  }
619
}
×
620

UNCOV
621
void Kernel::sendSetEngineSessionMode(uint8_t session, uint8_t speedSteps)
×
622
{
UNCOV
623
  assert(isKernelThread());
×
624
  // FIXME: what to do with: serviceMode and soundControlMode?
625
  send(SetEngineSessionMode(session, toSpeedMode(speedSteps), false, false));
×
626
}
×
627

628
void Kernel::sendSetEngineSpeedDirection(uint8_t session, uint8_t speed, bool directionForward)
×
629
{
630
  assert(isKernelThread());
×
631
  send(SetEngineSpeedDirection(session, speed, directionForward));
×
UNCOV
632
}
×
633

UNCOV
634
void Kernel::sendSetEngineFunction(uint8_t session, uint8_t number, bool value)
×
635
{
UNCOV
636
  assert(isKernelThread());
×
637
  if(value)
×
638
  {
639
    send(SetEngineFunctionOn(session, number));
×
640
  }
641
  else
642
  {
643
    send(SetEngineFunctionOff(session, number));
×
644
  }
UNCOV
645
}
×
646

UNCOV
647
void Kernel::changeState(State value)
×
648
{
UNCOV
649
  assert(isKernelThread());
×
650
  assert(m_state != value);
×
651

UNCOV
652
  m_state = value;
×
653

654
  switch(m_state)
×
655
  {
656
    case State::Initial: [[unlikely]]
×
UNCOV
657
      assert(false);
×
658
      break;
659

660
    case State::QueryNodes:
×
661
      send(QueryNodeNumber());
×
UNCOV
662
      restartInitializationTimer(queryNodeNumberTimeout);
×
663
      break;
×
664

665
    case State::GetCommandStationStatus:
×
UNCOV
666
      send(RequestCommandStationStatus());
×
667
      restartInitializationTimer(requestCommandStationStatusTimeout);
×
UNCOV
668
      break;
×
669

UNCOV
670
    case State::Started:
×
671
      KernelBase::started();
×
UNCOV
672
      break;
×
673
  }
UNCOV
674
}
×
675

676
void Kernel::restartInitializationTimer(std::chrono::milliseconds timeout)
×
677
{
UNCOV
678
  assert(isKernelThread());
×
679

UNCOV
680
  m_initializationTimer.cancel();
×
681

UNCOV
682
  m_initializationTimer.expires_after(timeout);
×
UNCOV
683
  m_initializationTimer.async_wait(
×
684
    [this](std::error_code ec)
×
685
    {
686
      if(ec)
×
687
      {
688
        return;
×
689
      }
690

691
      switch(m_state)
×
692
      {
UNCOV
693
        case State::QueryNodes:
×
694
          nextState();
×
UNCOV
695
          break;
×
696

UNCOV
697
        case State::GetCommandStationStatus:
×
UNCOV
698
          nextState();
×
UNCOV
699
          break;
×
700

UNCOV
701
        case State::Initial: [[unlikely]]
×
702
        case State::Started: [[unlikely]]
×
UNCOV
703
          assert(false);
×
704
          break;
705
      }
706
    });
UNCOV
707
}
×
708

709
void Kernel::restartEngineKeepAliveTimer()
×
710
{
UNCOV
711
  assert(isKernelThread());
×
712

UNCOV
713
  m_engineKeepAliveTimer.cancel();
×
714

715
  // find first expiring engine:
UNCOV
716
  std::chrono::steady_clock::time_point lastUpdate = std::chrono::steady_clock::time_point::max();
×
UNCOV
717
  for(const auto& [_, engine] : m_engines)
×
718
  {
719
    if(engine.session && engine.lastCommand < lastUpdate)
×
720
    {
721
      lastUpdate = engine.lastCommand;
×
UNCOV
722
      m_engineKeepAliveSession = *engine.session;
×
723
    }
724
  }
725

UNCOV
726
  m_engineKeepAliveTimerActive = (lastUpdate < std::chrono::steady_clock::time_point::max());
×
727

UNCOV
728
  if(m_engineKeepAliveTimerActive)
×
729
  {
UNCOV
730
    m_engineKeepAliveTimer.expires_at(lastUpdate + m_config.engineKeepAlive);
×
UNCOV
731
    m_engineKeepAliveTimer.async_wait(
×
732
      [this](std::error_code ec)
×
733
      {
734
        if(ec)
×
735
        {
UNCOV
736
          return;
×
737
        }
738

UNCOV
739
        send(SessionKeepAlive(m_engineKeepAliveSession));
×
740

UNCOV
741
        if(auto it = std::find_if(m_engines.begin(), m_engines.end(),
×
UNCOV
742
            [session=m_engineKeepAliveSession](const auto& item)
×
743
            {
UNCOV
744
              return item.second.session && *item.second.session == session;
×
UNCOV
745
            }); it != m_engines.end()) [[likely]]
×
746
        {
UNCOV
747
          it->second.lastCommand = std::chrono::steady_clock::now();
×
748
        }
749

750
        restartEngineKeepAliveTimer();
×
751
      });
752
  }
UNCOV
753
}
×
754

UNCOV
755
void Kernel::startDccAccessoryTimer()
×
756
{
757
  assert(isKernelThread());
×
758

759
  if(m_dccAccessoryQueue.empty()) [[unlikely]]
×
760
  {
761
    return;
×
762
  }
763

UNCOV
764
  m_dccAccessoryTimer.expires_at(m_dccAccessoryQueue.front().first);
×
UNCOV
765
  m_dccAccessoryTimer.async_wait(
×
766
    [this](std::error_code ec)
×
767
    {
768
      if(ec)
×
769
      {
770
        return;
×
771
      }
772

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

UNCOV
775
      m_dccAccessoryQueue.pop();
×
776

UNCOV
777
      if(!m_dccAccessoryQueue.empty())
×
778
      {
UNCOV
779
        startDccAccessoryTimer();
×
780
      }
781
    });
782
}
783

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