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

traintastic / traintastic / 20392201742

20 Dec 2025 09:05AM UTC coverage: 27.55% (-0.006%) from 27.556%
20392201742

push

github

reinder
[loconet] fix: missing file changes

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

388 existing lines in 2 files now uncovered.

7842 of 28465 relevant lines covered (27.55%)

192.56 hits per line

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

0.0
/server/src/hardware/protocol/loconet/kernel.cpp
1
/**
2
 * server/src/hardware/protocol/loconet/loconet.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2019-2025 Reinder Feenstra
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
 */
22

23
#include "kernel.hpp"
24
#include "iohandler/iohandler.hpp"
25
#include "messages.hpp"
26
#include "../../decoder/decoder.hpp"
27
#include "../../decoder/decoderchangeflags.hpp"
28
#include "../../decoder/decodercontroller.hpp"
29
#include "../../decoder/list/decoderlist.hpp"
30
#include "../../input/inputcontroller.hpp"
31
#include "../../output/outputcontroller.hpp"
32
#include "../../identification/identificationcontroller.hpp"
33
#include "../../../utils/datetimestr.hpp"
34
#include "../../../utils/setthreadname.hpp"
35
#include "../../../utils/inrange.hpp"
36
#include "../../../pcap/pcapfile.hpp"
37
#include "../../../pcap/pcappipe.hpp"
38
#include "../../../core/eventloop.hpp"
39
#include "../../../core/objectproperty.tpp"
40
#include "../../../log/log.hpp"
41
#include "../../../log/logmessageexception.hpp"
42
#include "../../../clock/clock.hpp"
43
#include "../../../traintastic/traintastic.hpp"
44
#include "../dcc/dcc.hpp"
45
#include "../dcc/messages.hpp"
46

47
namespace LocoNet {
48

49
static constexpr uint8_t multiplierFreeze = 0;
50

51
static void updateDecoderSpeed(const std::shared_ptr<Decoder>& decoder, uint8_t speed)
×
52
{
53
  decoder->emergencyStop.setValueInternal(speed == SPEED_ESTOP);
×
54

UNCOV
55
  if(speed == SPEED_STOP || speed == SPEED_ESTOP)
×
UNCOV
56
    decoder->throttle.setValueInternal(Decoder::throttleStop);
×
57
  else
58
  {
59
    speed--; // decrement one for ESTOP: 2..127 -> 1..126
×
60
    const auto currentStep = Decoder::throttleToSpeedStep<uint8_t>(decoder->throttle.value(), SPEED_MAX - 1);
×
UNCOV
61
    if(currentStep != speed) // only update trottle if it is a different step
×
62
      decoder->throttle.setValueInternal(Decoder::speedStepToThrottle<uint8_t>(speed, SPEED_MAX - 1));
×
63
  }
64
}
×
65

66
constexpr Kernel::Priority& operator ++(Kernel::Priority& value)
×
67
{
UNCOV
68
  return (value = static_cast<Kernel::Priority>(static_cast<std::underlying_type_t<Kernel::Priority>>(value) + 1));
×
69
}
70

71
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
×
72
  : KernelBase(std::move(logId_))
×
73
  , m_simulation{simulation}
×
74
  , m_waitingForEcho{false}
×
75
  , m_waitingForEchoTimer{m_ioContext}
×
76
  , m_waitingForResponse{false}
×
77
  , m_waitingForResponseTimer{m_ioContext}
×
78
  , m_fastClockSyncTimer(m_ioContext)
×
79
  , m_decoderController{nullptr}
×
80
  , m_inputController{nullptr}
×
81
  , m_outputController{nullptr}
×
82
  , m_identificationController{nullptr}
×
UNCOV
83
  , m_debugDir{Traintastic::instance->debugDir()}
×
84
  , m_config{config}
×
85
{
UNCOV
86
  assert(isEventLoopThread());
×
87
}
×
88

89
Kernel::~Kernel() = default;
×
90

91
void Kernel::setConfig(const Config& config)
×
92
{
93
  assert(isEventLoopThread());
×
94

95
  switch(config.fastClock)
×
96
  {
97
    case LocoNetFastClock::Off:
×
UNCOV
98
      disableClockEvents();
×
99
      break;
×
100

101
    case LocoNetFastClock::Master:
×
UNCOV
102
      enableClockEvents();
×
UNCOV
103
      break;
×
104
  }
105

UNCOV
106
  m_ioContext.post(
×
107
    [this, newConfig=config]()
×
108
    {
109
      if(newConfig.pcap != m_config.pcap)
×
110
      {
UNCOV
111
        if(newConfig.pcap)
×
112
          startPCAP(newConfig.pcapOutput);
×
113
        else
114
          m_pcap.reset();
×
115
      }
116
      else if(newConfig.pcap && newConfig.pcapOutput != m_config.pcapOutput)
×
117
      {
UNCOV
118
        m_pcap.reset();
×
UNCOV
119
        startPCAP(newConfig.pcapOutput);
×
120
      }
121

122
      if(newConfig.listenOnly && !m_config.listenOnly)
×
123
      {
UNCOV
124
        for(auto& queue : m_sendQueue)
×
125
          queue.clear();
×
126

UNCOV
127
        EventLoop::call(
×
128
          [this]()
×
129
          {
130
            Log::log(logId, LogMessage::N2006_LISTEN_ONLY_MODE_ACTIVATED);
×
131
          });
×
UNCOV
132
      }
×
133
      else if(!newConfig.listenOnly && m_config.listenOnly)
×
134
      {
UNCOV
135
        EventLoop::call(
×
136
          [this]()
×
137
          {
UNCOV
138
            Log::log(logId, LogMessage::N2007_LISTEN_ONLY_MODE_DEACTIVATED);
×
UNCOV
139
          });
×
140
      }
141

142
      if(m_config.fastClock == LocoNetFastClock::Master && newConfig.fastClock == LocoNetFastClock::Off)
×
143
      {
144
        setFastClockMaster(false);
×
145
      }
146
      else if(m_config.fastClock == LocoNetFastClock::Off && newConfig.fastClock == LocoNetFastClock::Master)
×
147
      {
UNCOV
148
        setFastClockMaster(true);
×
149
      }
150

151
      if(!m_config.fastClockSyncEnabled && newConfig.fastClockSyncEnabled)
×
152
      {
UNCOV
153
        send(RequestSlotData(SLOT_FAST_CLOCK));
×
154
        startFastClockSyncTimer();
×
155
      }
156
      else if(m_config.fastClockSyncEnabled && !newConfig.fastClockSyncEnabled)
×
157
      {
158
        stopFastClockSyncTimer();
×
159
      }
160
      m_config = newConfig;
×
UNCOV
161
    });
×
162
}
×
163

164
void Kernel::setOnGlobalPowerChanged(std::function<void(bool)> callback)
×
165
{
166
  assert(isEventLoopThread());
×
167
  assert(!m_started);
×
UNCOV
168
  m_onGlobalPowerChanged = std::move(callback);
×
169
}
×
170

171
void Kernel::setOnIdle(std::function<void()> callback)
×
172
{
173
  assert(isEventLoopThread());
×
174
  assert(!m_started);
×
UNCOV
175
  m_onIdle = std::move(callback);
×
176
}
×
177

178
void Kernel::setClock(std::shared_ptr<Clock> clock)
×
179
{
180
  assert(isEventLoopThread());
×
181
  assert(!m_started);
×
182
  assert(clock);
×
183
  m_clock = std::move(clock);
×
UNCOV
184
  m_fastClock.store(FastClock{m_clock->running ? m_clock->multiplier : multiplierFreeze, m_clock->hour, m_clock->minute});
×
185
}
×
186

187
void Kernel::setDecoderController(DecoderController* decoderController)
×
188
{
189
  assert(isEventLoopThread());
×
190
  assert(!m_started);
×
UNCOV
191
  m_decoderController = decoderController;
×
192
}
×
193

194
void Kernel::setInputController(InputController* inputController)
×
195
{
196
  assert(isEventLoopThread());
×
197
  assert(!m_started);
×
UNCOV
198
  m_inputController = inputController;
×
199
}
×
200

201
void Kernel::setOutputController(OutputController* outputController)
×
202
{
203
  assert(isEventLoopThread());
×
204
  assert(!m_started);
×
UNCOV
205
  m_outputController = outputController;
×
206
}
×
207

208
void Kernel::setIdentificationController(IdentificationController* identificationController)
×
209
{
210
  assert(isEventLoopThread());
×
211
  assert(!m_started);
×
UNCOV
212
  m_identificationController= identificationController;
×
213
}
×
214

215
void Kernel::start()
×
216
{
217
  assert(isEventLoopThread());
×
UNCOV
218
  assert(m_ioHandler);
×
UNCOV
219
  assert(!m_started);
×
220

221
  // reset all state values
222
  m_globalPower = TriState::Undefined;
×
223
  m_emergencyStop = TriState::Undefined;
×
224
  m_addressToSlot.clear();
×
225
  m_slots.clear();
×
226
  m_pendingSlotMessages.clear();
×
UNCOV
227
  m_inputValues.fill(TriState::Undefined);
×
228
  m_outputValues.fill(OutputPairValue::Undefined);
×
229

UNCOV
230
  if(m_config.listenOnly)
×
231
    Log::log(logId, LogMessage::N2006_LISTEN_ONLY_MODE_ACTIVATED);
×
232

UNCOV
233
  m_thread = std::thread(
×
234
    [this]()
×
235
    {
236
      setThreadName("loconet");
×
237
      auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
×
UNCOV
238
      m_ioContext.run();
×
239
    });
×
240

UNCOV
241
  if(m_config.fastClock == LocoNetFastClock::Master)
×
242
    enableClockEvents();
×
243

UNCOV
244
  m_ioContext.post(
×
245
    [this]()
×
246
    {
UNCOV
247
      if(m_config.pcap)
×
UNCOV
248
        startPCAP(m_config.pcapOutput);
×
249

250
      try
251
      {
252
        m_ioHandler->start();
×
253
      }
254
      catch(const LogMessageException& e)
×
255
      {
UNCOV
256
        EventLoop::call(
×
257
          [this, e]()
×
258
          {
259
            Log::log(logId, e.message(), e.args());
×
260
            error();
×
261
          });
×
UNCOV
262
        return;
×
UNCOV
263
      }
×
264
    });
265

266
#ifndef NDEBUG
267
  m_started = true;
×
268
#endif
269
}
×
270

271
void Kernel::stop()
×
272
{
273
  assert(isEventLoopThread());
×
274

275
  disableClockEvents();
×
276

UNCOV
277
  m_ioContext.post(
×
278
    [this]()
×
279
    {
280
      m_waitingForEchoTimer.cancel();
×
281
      m_waitingForResponseTimer.cancel();
×
282
      m_fastClockSyncTimer.cancel();
×
283
      m_ioHandler->stop();
×
UNCOV
284
      m_pcap.reset();
×
285
    });
×
286

287
  m_ioContext.stop();
×
288

UNCOV
289
  m_thread.join();
×
290

291
#ifndef NDEBUG
292
  m_started = false;
×
293
#endif
294
}
×
295

296
void Kernel::started()
×
297
{
298
  assert(isKernelThread());
×
299

UNCOV
300
  if(m_config.fastClock == LocoNetFastClock::Master)
×
301
    setFastClockMaster(true);
×
302

UNCOV
303
  if(m_config.fastClockSyncEnabled)
×
304
    startFastClockSyncTimer();
×
305

UNCOV
306
  for(uint8_t slot = SLOT_LOCO_MIN; slot <= m_config.locomotiveSlots; slot++)
×
307
    send(RequestSlotData(slot), LowPriority);
×
308

UNCOV
309
  KernelBase::started();
×
310
}
×
311

312
void Kernel::receive(const Message& message)
×
313
{
UNCOV
314
  assert(isKernelThread());
×
315
  assert(isValid(message)); // only valid messages should be received
×
316

UNCOV
317
  if(m_pcap)
×
318
    m_pcap->writeRecord(&message, message.size());
×
319

UNCOV
320
  if(m_config.debugLogRXTX)
×
321
    EventLoop::call([this, msg=toString(message)](){ Log::log(logId, LogMessage::D2002_RX_X, msg); });
×
322

UNCOV
323
  bool isResponse = false;
×
324
  if(m_waitingForEcho && message == lastSentMessage())
×
325
  {
326
    m_waitingForEcho = false;
×
UNCOV
327
    m_waitingForEchoTimer.cancel();
×
328
    if(!m_waitingForResponse)
×
329
    {
UNCOV
330
      m_sendQueue[m_sentMessagePriority].pop();
×
UNCOV
331
      sendNextMessage();
×
332
    }
333
  }
334
  else if(m_waitingForResponse)
×
335
  {
UNCOV
336
    isResponse = isValidResponse(lastSentMessage(), message);
×
337
  }
338

339
  switch(message.opCode)
×
340
  {
UNCOV
341
    case OPC_GPON:
×
342
      if(m_globalPower != TriState::True)
×
343
      {
344
        m_globalPower = TriState::True;
×
345
        if(m_onGlobalPowerChanged)
×
UNCOV
346
          EventLoop::call(
×
347
            [this]()
×
348
            {
UNCOV
349
              m_onGlobalPowerChanged(true);
×
350
            });
×
351
      }
352
      break;
×
353

UNCOV
354
    case OPC_GPOFF:
×
355
      if(m_globalPower != TriState::False)
×
356
      {
357
        m_globalPower = TriState::False;
×
358
        if(m_onGlobalPowerChanged)
×
UNCOV
359
          EventLoop::call(
×
360
            [this]()
×
361
            {
UNCOV
362
              m_onGlobalPowerChanged(false);
×
363
            });
×
364
      }
365
      break;
×
366

UNCOV
367
    case OPC_IDLE:
×
368
      if(m_emergencyStop != TriState::True)
×
369
      {
370
        m_emergencyStop = TriState::True;
×
371
        if(m_onIdle)
×
UNCOV
372
          EventLoop::call(
×
373
            [this]()
×
374
            {
UNCOV
375
              m_onIdle();
×
376
            });
×
377
      }
378
      break;
×
379

UNCOV
380
    case OPC_LOCO_SPD:
×
381
      if(m_decoderController)
×
382
      {
UNCOV
383
        const auto& locoSpd = static_cast<const LocoSpd&>(message);
×
384
        if(LocoSlot* slot = getLocoSlot(locoSpd.slot))
×
385
        {
386
          if(!slot->isAddressValid())
×
387
          {
388
            send(RequestSlotData(locoSpd.slot));
×
389
          }
390
          else if(slot->speed != locoSpd.speed)
×
391
          {
392
            slot->speed = locoSpd.speed;
×
393

UNCOV
394
            EventLoop::call(
×
395
              [this, address=slot->address, speed=slot->speed]()
×
396
              {
397
                if(auto decoder = getDecoder(address))
×
UNCOV
398
                  updateDecoderSpeed(decoder, speed);
×
UNCOV
399
              });
×
400
          }
401
        }
402
      }
403
      break;
×
404

UNCOV
405
    case OPC_LOCO_DIRF: // direction and F0-F4
×
406
      if(m_decoderController)
×
407
      {
UNCOV
408
        const auto& locoDirF = static_cast<const LocoDirF&>(message);
×
409
        if(LocoSlot* slot = getLocoSlot(locoDirF.slot))
×
410
        {
411
          if(!slot->isAddressValid())
×
412
          {
UNCOV
413
            send(RequestSlotData(locoDirF.slot));
×
414
          }
415
          else
416
          {
417
            if(slot->direction != locoDirF.direction())
×
418
            {
419
              slot->direction = locoDirF.direction();
×
420

UNCOV
421
              EventLoop::call(
×
422
                [this, address=slot->address, direction=locoDirF.direction()]()
×
423
                {
424
                  if(auto decoder = getDecoder(address))
×
UNCOV
425
                    decoder->direction.setValueInternal(direction);
×
UNCOV
426
                });
×
427
            }
428

UNCOV
429
            updateFunctions<0, 4>(*slot, locoDirF);
×
430
          }
431
        }
432
      }
433
      break;
×
434

UNCOV
435
    case OPC_LOCO_SND: // F5-F8
×
436
      if(m_decoderController)
×
437
      {
UNCOV
438
        const auto& locoSnd = static_cast<const LocoSnd&>(message);
×
439
        if(LocoSlot* slot = getLocoSlot(locoSnd.slot))
×
440
        {
441
          if(!slot->isAddressValid())
×
442
          {
UNCOV
443
            send(RequestSlotData(locoSnd.slot));
×
444
          }
445
          else
446
          {
UNCOV
447
            updateFunctions<5, 8>(*slot, locoSnd);
×
448
          }
449
        }
450
      }
451
      break;
×
452

UNCOV
453
    case OPC_LOCO_F9F12:
×
454
      if(m_decoderController)
×
455
      {
UNCOV
456
        const auto& locoF9F12 = static_cast<const LocoF9F12&>(message);
×
457
        if(LocoSlot* slot = getLocoSlot(locoF9F12.slot))
×
458
        {
459
          if(!slot->isAddressValid())
×
460
          {
461
            send(RequestSlotData(locoF9F12.slot));
×
462
          }
UNCOV
463
          updateFunctions<9, 12>(*slot, locoF9F12);
×
464
        }
465
      }
466
      break;
×
467

UNCOV
468
    case OPC_INPUT_REP:
×
469
      if(m_inputController)
×
470
      {
UNCOV
471
        const auto& inputRep = static_cast<const InputRep&>(message);
×
472
        if(inputRep.isControlSet())
×
473
        {
UNCOV
474
          const auto value = toTriState(inputRep.value());
×
475
          if(m_inputValues[inputRep.fullAddress()] != value)
×
476
          {
477
            if(m_config.debugLogInput)
×
UNCOV
478
              EventLoop::call(
×
479
                [this, address=1 + inputRep.fullAddress(), value=inputRep.value()]()
×
480
                {
UNCOV
481
                  Log::log(logId, LogMessage::D2007_INPUT_X_IS_X, address, value ? std::string_view{"1"} : std::string_view{"0"});
×
482
                });
×
483

484
            m_inputValues[inputRep.fullAddress()] = value;
×
485

UNCOV
486
            EventLoop::call(
×
487
              [this, address=1 + inputRep.fullAddress(), value]()
×
488
              {
UNCOV
489
                m_inputController->updateInputValue(InputChannel::Input, address, value);
×
UNCOV
490
              });
×
491
          }
492
        }
493
      }
494
      break;
×
495

UNCOV
496
    case OPC_SW_REQ:
×
497
      if(m_outputController)
×
498
      {
UNCOV
499
        const auto& switchRequest = static_cast<const SwitchRequest&>(message);
×
500
        if(switchRequest.on())
×
501
        {
UNCOV
502
          const auto value = switchRequest.dir() ? OutputPairValue::Second : OutputPairValue::First;
×
503
          if(m_outputValues[switchRequest.address() - accessoryOutputAddressMin] != value)
×
504
          {
505
            m_outputValues[switchRequest.address() - accessoryOutputAddressMin] = value;
×
506

UNCOV
507
            EventLoop::call(
×
508
              [this, address=switchRequest.address(), value]()
×
509
              {
UNCOV
510
                m_outputController->updateOutputValue(OutputChannel::Accessory, address, value);
×
UNCOV
511
              });
×
512
          }
513
        }
514
      }
515
      break;
×
516

UNCOV
517
    case OPC_SW_REP:
×
518
      break; // unimplemented
×
519

520
    case OPC_SL_RD_DATA:
×
521
    {
UNCOV
522
      const uint8_t slot = *(reinterpret_cast<const uint8_t*>(&message) + 2);
×
523
      if(m_decoderController && isLocoSlot(slot))
×
524
      {
UNCOV
525
        const auto& slotReadData = static_cast<const SlotReadData&>(message);
×
526
        if(slotReadData.isFree())
×
527
        {
UNCOV
528
          clearLocoSlot(slotReadData.slot);
×
UNCOV
529
          break;
×
530
        }
531

UNCOV
532
        LocoSlot* locoSlot = getLocoSlot(slotReadData.slot, false);
×
533
        assert(locoSlot);
×
534

UNCOV
535
        if(!locoSlot->isAddressValid())
×
536
          m_addressToSlot[slotReadData.address()] = slot;
×
537

UNCOV
538
        bool changed = locoSlot->isAddressValid() && locoSlot->address != slotReadData.address();
×
539
        if(changed)
×
540
        {
541
          if(auto it = m_addressToSlot.find(locoSlot->address); it != m_addressToSlot.end() && it->second == slotReadData.slot)
×
UNCOV
542
            m_addressToSlot.erase(locoSlot->address);
×
543
          locoSlot->invalidate();
×
544
        }
545
        locoSlot->address = slotReadData.address();
×
546

547
        if(changed)
×
548
        {
UNCOV
549
          EventLoop::call(
×
550
            [this, address=locoSlot->address, speed=locoSlot->speed, direction=locoSlot->direction]()
×
551
            {
552
              if(auto decoder = getDecoder(address))
×
553
              {
554
                updateDecoderSpeed(decoder, speed);
×
555
                decoder->direction.setValueInternal(direction);
×
UNCOV
556
              }
×
UNCOV
557
            });
×
558
        }
559

UNCOV
560
        updateFunctions<0, 8>(*locoSlot, slotReadData);
×
561

562
        // check if there are pending slot messages
563
        if(auto it = m_pendingSlotMessages.find(locoSlot->address); it != m_pendingSlotMessages.end())
×
564
        {
565
          std::byte* p = it->second.data();
×
UNCOV
566
          size_t size = it->second.size();
×
567
          while(size > 0)
×
568
          {
569
            Message& slotMassage =  *reinterpret_cast<Message*>(p);
×
570
            setSlot(slotMassage, slot);
×
571
            updateChecksum(slotMassage);
×
572
            send(slotMassage);
×
UNCOV
573
            p += slotMassage.size();
×
574
            size -= slotMassage.size();
×
575
          }
UNCOV
576
          m_pendingSlotMessages.erase(it);
×
577
        }
578
      }
579
      else if(slot == SLOT_FAST_CLOCK)
580
      {
581
        // todo
582
      }
583
      else if(slot == SLOT_PROGRAMMING_TRACK)
584
      {
585
        // todo
586
      }
587
      break;
×
588
    }
UNCOV
589
    case OPC_BUSY:
×
590
      break; // unimplemented
×
591

592
    case OPC_LONG_ACK:
×
593
    {
UNCOV
594
      const auto& longAck = static_cast<const LongAck&>(message);
×
595
      if(longAck.respondingOpCode() == OPC_LOCO_ADR && longAck.ack1 == 0)
×
596
      {
UNCOV
597
        EventLoop::call(
×
598
          [this]()
×
599
          {
UNCOV
600
            Log::log(logId, LogMessage::C2004_CANT_GET_FREE_SLOT);
×
601
          });
×
602
      }
603
      else if(longAck.respondingOpCode() == OPC_RQ_SL_DATA && longAck.ack1 == 0 && lastSentMessage().opCode == OPC_RQ_SL_DATA)
×
604
      {
605
        const uint8_t slot = static_cast<const RequestSlotData&>(lastSentMessage()).slot;
×
606

607
        if(isLocoSlot(slot))
×
608
        {
UNCOV
609
          EventLoop::call(
×
610
            [this, slot]()
×
611
            {
UNCOV
612
              Log::log(logId, LogMessage::W2006_COMMAND_STATION_DOES_NOT_SUPPORT_LOCO_SLOT_X, slot);
×
613
            });
×
614
        }
615
        else if(slot == SLOT_FAST_CLOCK)
×
616
        {
617
          m_fastClockSupported = false;
×
UNCOV
618
          if(m_config.fastClockSyncEnabled)
×
619
            stopFastClockSyncTimer();
×
620

UNCOV
621
          EventLoop::call(
×
622
            [this, stoppedFastClockSyncTimer=m_config.fastClockSyncEnabled]()
×
623
            {
624
              Log::log(logId, LogMessage::W2007_COMMAND_STATION_DOES_NOT_SUPPORT_THE_FAST_CLOCK_SLOT);
×
625
              if(stoppedFastClockSyncTimer)
×
UNCOV
626
                Log::log(logId, LogMessage::N2003_STOPPED_SENDING_FAST_CLOCK_SYNC);
×
UNCOV
627
            });
×
628
        }
629
      }
630
      else if(m_lncvActive && m_onLNCVReadResponse &&
×
UNCOV
631
          longAck.respondingOpCode() == OPC_IMM_PACKET && longAck.ack1 == 0x7F &&
×
632
          Uhlenbrock::LNCVWrite::check(lastSentMessage()))
×
633
      {
UNCOV
634
        const auto& lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(lastSentMessage());
×
635
        if(lncvWrite.lncv() == 0)
×
636
        {
UNCOV
637
          m_lncvModuleAddress = lncvWrite.value();
×
638
        }
639

UNCOV
640
        EventLoop::call(
×
641
          [this, lncvWrite]()
×
642
          {
UNCOV
643
            m_onLNCVReadResponse(true, lncvWrite.lncv(), lncvWrite.value());
×
644
          });
×
645
      }
646
      break;
×
647
    }
UNCOV
648
    case OPC_SLOT_STAT1:
×
649
      break; // unimplemented
×
650

UNCOV
651
    case OPC_CONSIST_FUNC:
×
652
      break; // unimplemented
×
653

UNCOV
654
    case OPC_UNLINK_SLOTS:
×
655
      break; // unimplemented
×
656

UNCOV
657
    case OPC_LINK_SLOTS:
×
658
      break; // unimplemented
×
659

UNCOV
660
    case OPC_MOVE_SLOTS:
×
661
      break; // unimplemented
×
662

UNCOV
663
    case OPC_RQ_SL_DATA:
×
664
      break; // unimplemented
×
665

UNCOV
666
    case OPC_SW_STATE:
×
667
      break; // unimplemented
×
668

UNCOV
669
    case OPC_SW_ACK:
×
670
      break; // unimplemented
×
671

UNCOV
672
    case OPC_LOCO_ADR:
×
673
      break; // unimplemented
×
674

675
    case OPC_MULTI_SENSE:
×
676
    {
UNCOV
677
      const auto& multiSense = static_cast<const MultiSense&>(message);
×
678
      if(multiSense.isTransponder())
×
679
      {
UNCOV
680
        EventLoop::call(
×
681
          [this, multiSenseTransponder=static_cast<const MultiSenseTransponder&>(multiSense)]()
×
682
          {
683
            m_identificationController->identificationEvent(
×
684
              IdentificationController::defaultIdentificationChannel,
685
              1 + multiSenseTransponder.sensorAddress(),
×
UNCOV
686
              multiSenseTransponder.isPresent() ? IdentificationEventType::Present : IdentificationEventType::Absent,
×
UNCOV
687
              multiSenseTransponder.transponderAddress(),
×
688
              Direction::Unknown,
689
              0);
690
          });
×
691
      }
692
      break;
×
693
    }
UNCOV
694
    case OPC_D4:
×
695
      if(m_decoderController)
×
696
      {
UNCOV
697
        const auto* bytes = reinterpret_cast<const uint8_t*>(&message);
×
698
        if(bytes[1] == 0x20)
×
699
        {
700
          switch(bytes[3])
×
701
          {
702
            case 0x08:
×
703
            {
UNCOV
704
              const auto& locoF13F19 = static_cast<const LocoF13F19&>(message);
×
705
              if(LocoSlot* slot = getLocoSlot(locoF13F19.slot))
×
706
              {
707
                if(!slot->isAddressValid())
×
708
                {
709
                  send(RequestSlotData(locoF13F19.slot));
×
710
                }
711
                updateFunctions<13, 19>(*slot, locoF13F19);
×
712
              }
713
              break;
×
714
            }
715
            case 0x05:
×
716
            {
UNCOV
717
              const auto& locoF12F20F28 = static_cast<const LocoF12F20F28&>(message);
×
718
              if(LocoSlot* slot = getLocoSlot(locoF12F20F28.slot))
×
719
              {
720
                if(!slot->isAddressValid())
×
721
                {
UNCOV
722
                  send(RequestSlotData(locoF12F20F28.slot));
×
723
                }
724

725
                const bool changed =
726
                  slot->functions[12] != locoF12F20F28.f12() ||
×
UNCOV
727
                  slot->functions[20] != locoF12F20F28.f20() ||
×
728
                  slot->functions[28] != locoF12F20F28.f28();
×
729

730
                if(changed)
×
731
                {
732
                  slot->functions[12] = toTriState(locoF12F20F28.f12());
×
UNCOV
733
                  slot->functions[20] = toTriState(locoF12F20F28.f20());
×
734
                  slot->functions[28] = toTriState(locoF12F20F28.f28());
×
735

UNCOV
736
                  EventLoop::call(
×
737
                    [this, address=slot->address, f12=locoF12F20F28.f12(), f20=locoF12F20F28.f20(), f28=locoF12F20F28.f28()]()
×
738
                    {
739
                      if(auto decoder = getDecoder(address))
×
740
                      {
741
                        decoder->setFunctionValue(12, f12);
×
742
                        decoder->setFunctionValue(20, f20);
×
743
                        decoder->setFunctionValue(28, f28);
×
UNCOV
744
                      }
×
UNCOV
745
                    });
×
746
                }
747
              }
748
              break;
×
749
            }
750
            case 0x09:
×
751
            {
UNCOV
752
              const auto& locoF21F27 = static_cast<const LocoF21F27&>(message);
×
753
              if(LocoSlot* slot = getLocoSlot(locoF21F27.slot))
×
754
              {
755
                if(!slot->isAddressValid())
×
756
                {
757
                  send(RequestSlotData(locoF21F27.slot));
×
758
                }
759
                updateFunctions<21, 27>(*slot, locoF21F27);
×
760
              }
UNCOV
761
              break;
×
762
            }
763
          }
764
        }
765
      }
766
      break;
×
767

768
    case OPC_MULTI_SENSE_LONG:
×
769
    {
UNCOV
770
      const auto& multiSense = static_cast<const MultiSenseLong&>(message);
×
771
      if(multiSense.code() == MultiSenseLong::Code::ReleaseTransponder || multiSense.code() == MultiSenseLong::Code::DetectTransponder)
×
772
      {
UNCOV
773
        EventLoop::call(
×
774
          [this, multiSense]()
×
775
          {
776
            m_identificationController->identificationEvent(
×
777
              IdentificationController::defaultIdentificationChannel,
778
              1 + multiSense.sensorAddress(),
×
UNCOV
779
              multiSense.code()  == MultiSenseLong::Code::DetectTransponder ? IdentificationEventType::Present : IdentificationEventType::Absent,
×
UNCOV
780
              multiSense.transponderAddress(),
×
781
              multiSense.transponderDirection(),
782
              0);
783
          });
×
784
      }
785
      break;
×
786
    }
UNCOV
787
    case OPC_E4:
×
788
      if(static_cast<const Uhlenbrock::Lissy&>(message).type() == Uhlenbrock::Lissy::Type::AddressCategoryDirection)
×
789
      {
UNCOV
790
        EventLoop::call(
×
791
          [this, lissy=static_cast<const Uhlenbrock::LissyAddressCategoryDirection&>(message)]()
×
792
          {
793
            m_identificationController->identificationEvent(
×
794
              IdentificationController::defaultIdentificationChannel,
795
              lissy.sensorAddress(),
×
796
              IdentificationEventType::Seen,
797
              lissy.decoderAddress(),
×
798
              lissy.direction(),
UNCOV
799
              lissy.category());
×
800
          });
×
801
      }
802
      break;
×
803

UNCOV
804
    case OPC_PEER_XFER:
×
805
      if(isResponse)
×
806
      {
807
        if(Uhlenbrock::ReadSpecialOptionReply::check(message))
×
808
        {
809
          [[maybe_unused]] const auto& readSpecialOptionReply = static_cast<const Uhlenbrock::ReadSpecialOptionReply&>(message);
×
810
        }
811
        else if(m_onLNCVReadResponse && Uhlenbrock::LNCVReadResponse::check(message))
×
812
        {
UNCOV
813
          const auto& lncvReadResponse = static_cast<const Uhlenbrock::LNCVReadResponse&>(message);
×
814
          if(lncvReadResponse.lncv() == 0)
×
815
          {
UNCOV
816
            m_lncvActive = true;
×
UNCOV
817
            m_lncvModuleAddress = lncvReadResponse.value();
×
818
          }
819

UNCOV
820
          EventLoop::call(
×
821
            [this, lncvReadResponse]()
×
822
            {
UNCOV
823
              m_onLNCVReadResponse(true, lncvReadResponse.lncv(), lncvReadResponse.value());
×
UNCOV
824
            });
×
825
        }
826
      }
827
      break;
×
828

UNCOV
829
    case OPC_IMM_PACKET:
×
830
      if(m_decoderController)
×
831
      {
832
        if(isSignatureMatch<LocoF9F12IMMShortAddress>(message))
×
833
        {
834
          const auto& locoF9F12 = static_cast<const LocoF9F12IMMShortAddress&>(message);
×
UNCOV
835
          if(LocoSlot* slot = getLocoSlotByAddress(locoF9F12.address()))
×
836
            updateFunctions<9, 12>(*slot, locoF9F12);
×
837
          else
838
            send(LocoAdr(locoF9F12.address()));
×
839
        }
840
        else if(isSignatureMatch<LocoF9F12IMMLongAddress>(message))
×
841
        {
842
          const auto& locoF9F12 = static_cast<const LocoF9F12IMMLongAddress&>(message);
×
UNCOV
843
          if(LocoSlot* slot = getLocoSlotByAddress(locoF9F12.address()))
×
844
            updateFunctions<9, 12>(*slot, locoF9F12);
×
845
          else
846
            send(LocoAdr(locoF9F12.address()));
×
847
        }
848
        else if(isSignatureMatch<LocoF13F20IMMShortAddress>(message))
×
849
        {
850
          const auto& locoF13F20 = static_cast<const LocoF13F20IMMShortAddress&>(message);
×
UNCOV
851
          if(LocoSlot* slot = getLocoSlotByAddress(locoF13F20.address()))
×
852
            updateFunctions<13, 20>(*slot, locoF13F20);
×
853
          else
854
            send(LocoAdr(locoF13F20.address()));
×
855
        }
856
        else if(isSignatureMatch<LocoF13F20IMMLongAddress>(message))
×
857
        {
858
          const auto& locoF13F20 = static_cast<const LocoF13F20IMMLongAddress&>(message);
×
UNCOV
859
          if(LocoSlot* slot = getLocoSlotByAddress(locoF13F20.address()))
×
860
            updateFunctions<13, 20>(*slot, locoF13F20);
×
861
          else
862
            send(LocoAdr(locoF13F20.address()));
×
863
        }
864
        else if(isSignatureMatch<LocoF21F28IMMShortAddress>(message))
×
865
        {
866
          const auto& locoF21F28 = static_cast<const LocoF21F28IMMShortAddress&>(message);
×
UNCOV
867
          if(LocoSlot* slot = getLocoSlotByAddress(locoF21F28.address()))
×
868
            updateFunctions<21, 28>(*slot, locoF21F28);
×
869
          else
870
            send(LocoAdr(locoF21F28.address()));
×
871
        }
872
        else if(isSignatureMatch<LocoF21F28IMMLongAddress>(message))
×
873
        {
874
          const auto& locoF21F28 = static_cast<const LocoF21F28IMMLongAddress&>(message);
×
UNCOV
875
          if(LocoSlot* slot = getLocoSlotByAddress(locoF21F28.address()))
×
876
            updateFunctions<21, 28>(*slot, locoF21F28);
×
877
          else
UNCOV
878
            send(LocoAdr(locoF21F28.address()));
×
879
        }
880
      }
881
      break; // unimplemented
×
882

UNCOV
883
    case OPC_WR_SL_DATA:
×
UNCOV
884
      break; // unimplemented
×
885
  }
886

887
  if(m_waitingForResponse && isResponse)
×
888
  {
889
    m_waitingForResponse = false;
×
890
    m_waitingForResponseTimer.cancel();
×
UNCOV
891
    m_sendQueue[m_sentMessagePriority].pop();
×
892
    sendNextMessage();
×
893
  }
894
}
×
895

896
void Kernel::setState(bool powerOn, bool run)
×
897
{
UNCOV
898
  assert(isEventLoopThread());
×
899
  m_ioContext.post(
×
900
    [this, powerOn, run]()
×
901
    {
902
      if(!powerOn) // disable power
×
903
      {
904
        if(m_emergencyStop != TriState::True)
×
905
        {
UNCOV
906
          send(Idle(), HighPriority); // estop trains
×
907
        }
908
        if(m_globalPower != TriState::False)
×
909
        {
UNCOV
910
          send(GlobalPowerOff(), HighPriority);
×
911
        }
912
      }
913
      else
914
      {
915
        if(m_globalPower != TriState::True) // enable power
×
916
        {
917
          send(GlobalPowerOn(), HighPriority);
×
918
        }
919
        if(run && m_emergencyStop != TriState::False)
×
920
        {
921
          EventLoop::call(std::bind_front(&Kernel::resume, this));
×
922
        }
923
        else if(!run && m_emergencyStop != TriState::True)
×
924
        {
925
          send(Idle(), HighPriority); // estop trains
×
926
        }
927
      }
928
    });
×
UNCOV
929
}
×
930

931
void Kernel::resume()
×
932
{
UNCOV
933
  assert(isEventLoopThread());
×
934

UNCOV
935
  auto& list = *m_decoderController->decoders.value();
×
936
  for(const auto& decoder : list)
×
937
  {
UNCOV
938
    decoderChanged(*decoder, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps, 0);
×
939

940
    if(decoder->hasFunction(5) ||
×
941
        decoder->hasFunction(6) ||
×
UNCOV
942
        decoder->hasFunction(7) ||
×
943
        decoder->hasFunction(8))
×
944
    {
945
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 5); // trigger F5-F8 update
×
946
    }
947
    if(decoder->hasFunction(9) ||
×
948
        decoder->hasFunction(10) ||
×
UNCOV
949
        decoder->hasFunction(11) ||
×
950
        decoder->hasFunction(12))
×
951
    {
UNCOV
952
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 9); // trigger F9-F12 update
×
953
    }
954
    if(decoder->hasFunction(13) ||
×
UNCOV
955
        decoder->hasFunction(14) ||
×
956
        decoder->hasFunction(15) ||
×
957
        decoder->hasFunction(16) ||
×
UNCOV
958
        decoder->hasFunction(17) ||
×
959
        decoder->hasFunction(18) ||
×
960
        decoder->hasFunction(19) ||
×
UNCOV
961
        decoder->hasFunction(20))
×
962
    {
963
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 13); // trigger F13-F20 update
×
964
    }
965
    if(decoder->hasFunction(21) ||
×
UNCOV
966
        decoder->hasFunction(22) ||
×
967
        decoder->hasFunction(23) ||
×
UNCOV
968
        decoder->hasFunction(24) ||
×
969
        decoder->hasFunction(25) ||
×
970
        decoder->hasFunction(26) ||
×
UNCOV
971
        decoder->hasFunction(27) ||
×
972
        decoder->hasFunction(28))
×
973
    {
UNCOV
974
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 21); // trigger F21-F28 update
×
975
    }
976
  }
UNCOV
977
}
×
978

UNCOV
979
bool Kernel::send(std::span<uint8_t> packet)
×
980
{
UNCOV
981
  assert(isEventLoopThread());
×
982

983
  if(reinterpret_cast<Message*>(packet.data())->size() != packet.size() + 1) // verify packet length, must be all bytes excluding checksum
×
UNCOV
984
    return false;
×
985

986
  std::vector<uint8_t> data(packet.data(), packet.data() + packet.size());
×
987
  data.push_back(calcChecksum(*reinterpret_cast<Message*>(data.data())));
×
988

UNCOV
989
  if(!isValid(*reinterpret_cast<Message*>(data.data())))
×
UNCOV
990
    return false;
×
991

UNCOV
992
  m_ioContext.post(
×
993
    [this, message=std::move(data)]()
×
994
    {
UNCOV
995
      send(*reinterpret_cast<const Message*>(message.data()));
×
UNCOV
996
    });
×
997

998
  return true;
×
999
}
×
1000

1001
bool Kernel::immPacket(std::span<const uint8_t> dccPacket, uint8_t repeat)
×
1002
{
UNCOV
1003
  assert(isEventLoopThread());
×
1004

UNCOV
1005
  if(dccPacket.size() > ImmPacket::dccPacketSizeMax || repeat > ImmPacket::repeatMax)
×
UNCOV
1006
    return false;
×
1007

1008
  postSend(ImmPacket(dccPacket, repeat));
×
1009
  return true;
×
1010
}
1011

UNCOV
1012
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
1013
{
UNCOV
1014
  assert(isEventLoopThread());
×
1015

UNCOV
1016
  if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle))
×
1017
  {
UNCOV
1018
    const auto speedStep = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, SPEED_MAX - 1);
×
1019
    if(m_emergencyStop == TriState::False || decoder.emergencyStop || speedStep == SPEED_STOP)
×
1020
    {
1021
      // only send speed updates if bus estop isn't active, except for speed STOP and ESTOP
UNCOV
1022
      LocoSpd message{static_cast<uint8_t>(decoder.emergencyStop ? SPEED_ESTOP : (speedStep > 0 ? 1 + speedStep : SPEED_STOP))};
×
1023
      postSend(decoder.address, message);
×
1024
    }
1025
  }
1026

UNCOV
1027
  if(has(changes, DecoderChangeFlags::FunctionValue | DecoderChangeFlags::Direction))
×
1028
  {
1029
    if(functionNumber <= 4 || has(changes, DecoderChangeFlags::Direction))
×
1030
    {
1031
      LocoDirF message{
1032
        decoder.direction,
UNCOV
1033
        decoder.getFunctionValue(0),
×
UNCOV
1034
        decoder.getFunctionValue(1),
×
1035
        decoder.getFunctionValue(2),
×
1036
        decoder.getFunctionValue(3),
×
UNCOV
1037
        decoder.getFunctionValue(4)};
×
1038
      postSend(decoder.address, message);
×
1039
    }
1040
    else if(functionNumber <= 8)
×
1041
    {
1042
      LocoSnd message{
UNCOV
1043
        decoder.getFunctionValue(5),
×
1044
        decoder.getFunctionValue(6),
×
UNCOV
1045
        decoder.getFunctionValue(7),
×
1046
        decoder.getFunctionValue(8)};
×
UNCOV
1047
      postSend(decoder.address, message);
×
1048
    }
1049
    else if(functionNumber <= 28)
×
1050
    {
1051
      switch(m_config.f9f28)
×
1052
      {
1053
        case LocoNetF9F28::IMMPacket:
×
1054
        {
1055
          const bool longAddress = decoder.protocol == DecoderProtocol::DCCLong;
×
1056

1057
          if(functionNumber <= 12)
×
1058
          {
UNCOV
1059
            if(longAddress)
×
1060
            {
UNCOV
1061
              postSend(
×
1062
                LocoF9F12IMMLongAddress(
×
1063
                  decoder.address,
UNCOV
1064
                  decoder.getFunctionValue(9),
×
1065
                  decoder.getFunctionValue(10),
×
1066
                  decoder.getFunctionValue(11),
×
1067
                  decoder.getFunctionValue(12)));
×
1068
            }
1069
            else
1070
            {
1071
              postSend(
×
1072
                LocoF9F12IMMShortAddress(
×
1073
                  decoder.address,
UNCOV
1074
                  decoder.getFunctionValue(9),
×
1075
                  decoder.getFunctionValue(10),
×
UNCOV
1076
                  decoder.getFunctionValue(11),
×
1077
                  decoder.getFunctionValue(12)));
×
1078
            }
1079
          }
1080
          else if(functionNumber <= 20)
×
1081
          {
1082
            if(longAddress)
×
1083
            {
1084
              postSend(
×
1085
                LocoF13F20IMMLongAddress(
×
1086
                  decoder.address,
1087
                  decoder.getFunctionValue(13),
×
1088
                  decoder.getFunctionValue(14),
×
1089
                  decoder.getFunctionValue(15),
×
UNCOV
1090
                  decoder.getFunctionValue(16),
×
UNCOV
1091
                  decoder.getFunctionValue(17),
×
UNCOV
1092
                  decoder.getFunctionValue(18),
×
1093
                  decoder.getFunctionValue(19),
×
1094
                  decoder.getFunctionValue(20)));
×
1095
            }
1096
            else
1097
            {
1098
              postSend(
×
1099
                LocoF13F20IMMShortAddress(
×
1100
                  decoder.address,
1101
                  decoder.getFunctionValue(13),
×
1102
                  decoder.getFunctionValue(14),
×
1103
                  decoder.getFunctionValue(15),
×
UNCOV
1104
                  decoder.getFunctionValue(16),
×
UNCOV
1105
                  decoder.getFunctionValue(17),
×
1106
                  decoder.getFunctionValue(18),
×
UNCOV
1107
                  decoder.getFunctionValue(19),
×
1108
                  decoder.getFunctionValue(20)));
×
1109
            }
1110
          }
UNCOV
1111
          else if(functionNumber <= 28)
×
1112
          {
1113
            if(longAddress)
×
1114
            {
1115
              postSend(
×
1116
                LocoF21F28IMMLongAddress(
×
1117
                  decoder.address,
1118
                  decoder.getFunctionValue(21),
×
UNCOV
1119
                  decoder.getFunctionValue(22),
×
UNCOV
1120
                  decoder.getFunctionValue(23),
×
1121
                  decoder.getFunctionValue(24),
×
1122
                  decoder.getFunctionValue(25),
×
1123
                  decoder.getFunctionValue(26),
×
1124
                  decoder.getFunctionValue(27),
×
1125
                  decoder.getFunctionValue(28)));
×
1126
            }
1127
            else
1128
            {
UNCOV
1129
              postSend(
×
1130
                LocoF21F28IMMShortAddress(
×
1131
                  decoder.address,
UNCOV
1132
                  decoder.getFunctionValue(21),
×
1133
                  decoder.getFunctionValue(22),
×
1134
                  decoder.getFunctionValue(23),
×
1135
                  decoder.getFunctionValue(24),
×
1136
                  decoder.getFunctionValue(25),
×
1137
                  decoder.getFunctionValue(26),
×
1138
                  decoder.getFunctionValue(27),
×
UNCOV
1139
                  decoder.getFunctionValue(28)));
×
1140
            }
1141
          }
1142
          break;
×
1143
        }
1144
        case LocoNetF9F28::UhlenbrockExtended:
×
1145
          if(functionNumber <= 12)
×
1146
          {
1147
            LocoF9F12 message{
1148
              decoder.getFunctionValue(9),
×
UNCOV
1149
              decoder.getFunctionValue(10),
×
1150
              decoder.getFunctionValue(11),
×
UNCOV
1151
              decoder.getFunctionValue(12)};
×
1152
            postSend(decoder.address, message);
×
1153
          }
UNCOV
1154
          else if(functionNumber <= 19)
×
1155
          {
1156
            LocoF13F19 message{
UNCOV
1157
              decoder.getFunctionValue(13),
×
1158
              decoder.getFunctionValue(14),
×
UNCOV
1159
              decoder.getFunctionValue(15),
×
1160
              decoder.getFunctionValue(16),
×
UNCOV
1161
              decoder.getFunctionValue(17),
×
1162
              decoder.getFunctionValue(18),
×
UNCOV
1163
              decoder.getFunctionValue(19)};
×
1164
            postSend(decoder.address, message);
×
1165
          }
1166
          else if(functionNumber == 20 || functionNumber == 28)
×
1167
          {
1168
            LocoF12F20F28 message{
UNCOV
1169
              decoder.getFunctionValue(12),
×
1170
              decoder.getFunctionValue(20),
×
1171
              decoder.getFunctionValue(28)};
×
UNCOV
1172
            postSend(decoder.address, message);
×
1173
          }
×
1174
          else if(functionNumber <= 27)
×
1175
          {
1176
            LocoF21F27 message{
1177
              decoder.getFunctionValue(21),
×
UNCOV
1178
              decoder.getFunctionValue(22),
×
1179
              decoder.getFunctionValue(23),
×
1180
              decoder.getFunctionValue(24),
×
1181
              decoder.getFunctionValue(25),
×
UNCOV
1182
              decoder.getFunctionValue(26),
×
1183
              decoder.getFunctionValue(27)};
×
1184
            postSend(decoder.address, message);
×
1185
          }
UNCOV
1186
          break;
×
1187

UNCOV
1188
        default:
×
UNCOV
1189
          assert(false);
×
1190
          break;
1191
      }
1192
    }
1193
  }
1194
}
×
1195

1196
bool Kernel::setOutput(OutputChannel channel, uint16_t address, OutputValue value)
×
1197
{
1198
  assert(isEventLoopThread());
×
1199

1200
  switch(channel)
×
1201
  {
1202
    case OutputChannel::Accessory:
×
1203
      if(!inRange(address, accessoryOutputAddressMin, accessoryOutputAddressMax))
×
UNCOV
1204
        return false;
×
1205

1206
      m_ioContext.post(
×
1207
        [this, address, dir=std::get<OutputPairValue>(value) == OutputPairValue::Second]()
×
1208
        {
UNCOV
1209
          send(SwitchRequest(address, dir, true));
×
1210
        });
×
1211
      return true;
×
1212

UNCOV
1213
    case OutputChannel::DCCext:
×
1214
      return
1215
        inRange(address, DCC::Accessory::addressMin, DCC::Accessory::addressMax) &&
×
UNCOV
1216
        inRange<int16_t>(std::get<int16_t>(value), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()) &&
×
1217
        immPacket(DCC::SetAdvancedAccessoryValue(address, static_cast<uint8_t>(std::get<int16_t>(value))), 2);
×
1218

1219
    default: /*[[unlikely]]*/
×
1220
      assert(false);
×
1221
      break;
1222
  }
1223
  return false;
1224
}
1225

1226
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
×
1227
{
1228
  assert(isEventLoopThread());
×
1229
  assert(inRange(address, inputAddressMin, inputAddressMax));
×
UNCOV
1230
  if(m_simulation)
×
1231
    m_ioContext.post(
×
UNCOV
1232
      [this, fullAddress=address - 1, action]()
×
1233
      {
UNCOV
1234
        switch(action)
×
1235
        {
1236
            case SimulateInputAction::SetFalse:
×
1237
              if(m_inputValues[fullAddress] != TriState::False)
×
UNCOV
1238
                receive(InputRep(fullAddress, false));
×
1239
              break;
×
1240

1241
            case SimulateInputAction::SetTrue:
×
1242
              if(m_inputValues[fullAddress] != TriState::True)
×
UNCOV
1243
                receive(InputRep(fullAddress, true));
×
1244
              break;
×
1245

1246
            case SimulateInputAction::Toggle:
×
1247
              receive(InputRep(fullAddress, m_inputValues[fullAddress] != TriState::True));
×
1248
              break;
×
1249
        }
1250
      });
×
1251
}
×
1252

1253
void Kernel::lncvStart(uint16_t moduleId, uint16_t moduleAddress)
×
1254
{
1255
  assert(isEventLoopThread());
×
UNCOV
1256
  m_ioContext.post(
×
1257
    [this, moduleId, moduleAddress]()
×
1258
    {
1259
      if(m_lncvActive)
×
UNCOV
1260
        return;
×
1261

1262
      m_lncvActive = true;
×
UNCOV
1263
      m_lncvModuleId = moduleId;
×
1264
      m_lncvModuleAddress = moduleAddress;
×
1265
      send(Uhlenbrock::LNCVStart(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1266
    });
1267
}
×
1268

1269
void Kernel::lncvRead(uint16_t lncv)
×
1270
{
1271
  assert(isEventLoopThread());
×
1272
  m_ioContext.post(
×
1273
    [this, lncv]()
×
1274
    {
UNCOV
1275
      if(m_lncvActive)
×
1276
        send(Uhlenbrock::LNCVRead(m_lncvModuleId, m_lncvModuleAddress, lncv), HighPriority);
×
UNCOV
1277
    });
×
1278
}
×
1279

1280
void Kernel::lncvWrite(uint16_t lncv, uint16_t value)
×
1281
{
UNCOV
1282
  assert(isEventLoopThread());
×
1283
  m_ioContext.post(
×
1284
    [this, lncv, value]()
×
1285
    {
1286
      if(m_lncvActive)
×
1287
        send(Uhlenbrock::LNCVWrite(m_lncvModuleId, lncv, value), HighPriority);
×
1288
    });
×
UNCOV
1289
}
×
1290

1291
void Kernel::lncvStop()
×
1292
{
UNCOV
1293
  assert(isEventLoopThread());
×
1294
  m_ioContext.post(
×
UNCOV
1295
    [this]()
×
1296
    {
UNCOV
1297
      if(!m_lncvActive)
×
1298
        return;
×
1299

UNCOV
1300
      send(Uhlenbrock::LNCVStop(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1301
      m_lncvActive = false;
×
1302
    });
UNCOV
1303
}
×
1304

1305
void Kernel::setOnLNCVReadResponse(OnLNCVReadResponse callback)
×
1306
{
UNCOV
1307
  assert(isEventLoopThread());
×
1308
  assert(!m_started);
×
UNCOV
1309
  m_onLNCVReadResponse = std::move(callback);
×
1310
}
×
1311

1312
Kernel::LocoSlot* Kernel::getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew)
×
1313
{
UNCOV
1314
  assert(isKernelThread());
×
1315

1316
  if(!isLocoSlot(slot))
×
1317
    return nullptr;
×
1318

1319
  auto it = m_slots.find(slot);
×
UNCOV
1320
  if(it == m_slots.end())
×
1321
  {
1322
    if(sendSlotDataRequestIfNew)
×
UNCOV
1323
      send(RequestSlotData(slot));
×
UNCOV
1324
    it = m_slots.emplace(slot, LocoSlot()).first;
×
1325
  }
1326

1327
  return &it->second;
×
1328
}
1329

1330
Kernel::LocoSlot* Kernel::getLocoSlotByAddress(uint16_t address)
×
1331
{
UNCOV
1332
  assert(isKernelThread());
×
1333

UNCOV
1334
  auto it = std::find_if(m_slots.begin(), m_slots.end(),
×
1335
    [address](auto& item)
×
1336
    {
1337
      assert(address != LocoSlot::invalidAddress);
×
1338
      return item.second.address == address;
×
1339
    });
1340

UNCOV
1341
  return it != m_slots.end() ? &it->second : nullptr;
×
1342
}
1343

UNCOV
1344
void Kernel::clearLocoSlot(uint8_t slot)
×
1345
{
1346
  assert(isKernelThread());
×
1347

UNCOV
1348
  if(auto it = m_slots.find(slot); it != m_slots.end())
×
UNCOV
1349
    m_slots.erase(it);
×
1350

UNCOV
1351
  if(auto it = std::find_if(m_addressToSlot.begin(), m_addressToSlot.end(), [slot](const auto& item){ return item.second == slot; }); it != m_addressToSlot.end())
×
1352
    m_addressToSlot.erase(it);
×
UNCOV
1353
}
×
1354

UNCOV
1355
std::shared_ptr<Decoder> Kernel::getDecoder(uint16_t address)
×
1356
{
UNCOV
1357
  assert(isEventLoopThread());
×
1358
  return m_decoderController->getDecoder(DCC::getProtocol(address), address);
×
1359
}
1360

1361
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
1362
{
1363
  assert(isEventLoopThread());
×
1364
  assert(handler);
×
UNCOV
1365
  assert(!m_ioHandler);
×
UNCOV
1366
  m_ioHandler = std::move(handler);
×
UNCOV
1367
}
×
1368

UNCOV
1369
void Kernel::send(const Message& message, Priority priority)
×
1370
{
1371
  assert(isKernelThread());
×
1372

1373
  if(m_config.listenOnly)
×
1374
    return; // drop it
×
1375

UNCOV
1376
  if(!m_sendQueue[priority].append(message))
×
1377
  {
1378
    // TODO: log message
UNCOV
1379
    return;
×
1380
  }
1381

UNCOV
1382
  if(!m_waitingForEcho && !m_waitingForResponse)
×
1383
    sendNextMessage();
×
1384
}
1385

UNCOV
1386
void Kernel::send(uint16_t address, Message& message, uint8_t& slot)
×
1387
{
UNCOV
1388
  assert(isKernelThread());
×
1389

UNCOV
1390
  if(auto addressToSlot = m_addressToSlot.find(address); addressToSlot != m_addressToSlot.end())
×
1391
  {
1392
    slot = addressToSlot->second;
×
1393

1394
    if(message.opCode == OPC_LOCO_SPD &&
×
UNCOV
1395
        static_cast<LocoSpd&>(message).speed >= SPEED_MIN &&
×
1396
        static_cast<LocoSpd&>(message).speed == m_slots[slot].speed)
×
UNCOV
1397
      return; // same speed, don't send it (always send ESTOP and STOP)
×
1398

1399
    updateChecksum(message);
×
1400
    send(message);
×
1401
  }
1402
  else // try get a slot
1403
  {
UNCOV
1404
    auto* ptr = reinterpret_cast<std::byte*>(&message);
×
1405

1406
    auto pendingSlotMessage = m_pendingSlotMessages.find(address);
×
UNCOV
1407
    if(pendingSlotMessage == m_pendingSlotMessages.end())
×
1408
    {
UNCOV
1409
      m_pendingSlotMessages[address].assign(ptr, ptr + message.size());
×
UNCOV
1410
      send(LocoAdr{address}, HighPriority);
×
1411
    }
1412
    else
UNCOV
1413
      pendingSlotMessage->second.insert(pendingSlotMessage->second.end(), ptr, ptr + message.size());
×
1414
  }
1415
}
1416

UNCOV
1417
void Kernel::sendNextMessage()
×
1418
{
UNCOV
1419
  assert(isKernelThread());
×
1420

1421
  for(Priority priority = HighPriority; priority <= LowPriority; ++priority)
×
1422
  {
1423
    if(!m_sendQueue[priority].empty())
×
1424
    {
UNCOV
1425
      const Message& message = m_sendQueue[priority].front();
×
1426

1427
      if(m_config.debugLogRXTX)
×
UNCOV
1428
        EventLoop::call([this, msg=toString(message)](){ Log::log(logId, LogMessage::D2001_TX_X, msg); });
×
1429

1430
      if(m_ioHandler->send(message))
×
1431
      {
1432
        m_sentMessagePriority = priority;
×
1433

1434
        m_waitingForEcho = true;
×
1435
        m_waitingForEchoTimer.expires_after(boost::asio::chrono::milliseconds(m_config.echoTimeout));
×
UNCOV
1436
        m_waitingForEchoTimer.async_wait(std::bind(&Kernel::waitingForEchoTimerExpired, this, std::placeholders::_1));
×
1437

UNCOV
1438
        m_waitingForResponse = hasResponse(message);
×
1439
        if(m_waitingForResponse)
×
1440
        {
UNCOV
1441
          m_waitingForResponseTimer.expires_after(boost::asio::chrono::milliseconds(m_config.responseTimeout));
×
1442
          m_waitingForResponseTimer.async_wait(std::bind(&Kernel::waitingForResponseTimerExpired, this, std::placeholders::_1));
×
1443
        }
1444
      }
1445
      else
1446
      {} // log message and go to error state
UNCOV
1447
      return;
×
1448
    }
1449
  }
1450
}
1451

1452
void Kernel::waitingForEchoTimerExpired(const boost::system::error_code& ec)
×
1453
{
UNCOV
1454
  assert(isKernelThread());
×
1455

1456
  if(ec)
×
1457
    return;
×
1458

UNCOV
1459
  EventLoop::call(
×
UNCOV
1460
    [this]()
×
1461
    {
UNCOV
1462
      Log::log(logId, LogMessage::W2018_TIMEOUT_NO_ECHO_WITHIN_X_MS, m_config.echoTimeout);
×
1463
    });
×
1464
}
1465

1466
void Kernel::waitingForResponseTimerExpired(const boost::system::error_code& ec)
×
1467
{
1468
  assert(isKernelThread());
×
1469

1470
  if(ec)
×
1471
    return;
×
1472

1473
  if(m_lncvActive && Uhlenbrock::LNCVStart::check(lastSentMessage()))
×
1474
  {
UNCOV
1475
    EventLoop::call(
×
UNCOV
1476
      [this, lncvStart=static_cast<const Uhlenbrock::LNCVStart&>(lastSentMessage())]()
×
1477
      {
1478
        Log::log(logId, LogMessage::N2002_NO_RESPONSE_FROM_LNCV_MODULE_X_WITH_ADDRESS_X, lncvStart.moduleId(), lncvStart.address());
×
1479

1480
        if(m_onLNCVReadResponse && m_lncvModuleId == lncvStart.moduleId())
×
UNCOV
1481
          m_onLNCVReadResponse(false, lncvStart.address(), 0);
×
1482
      });
×
1483

UNCOV
1484
    sendNextMessage();
×
1485
  }
1486
  else
1487
  {
1488
    EventLoop::call(
×
UNCOV
1489
      [this]()
×
1490
      {
1491
        Log::log(logId, LogMessage::E2019_TIMEOUT_NO_RESPONSE_WITHIN_X_MS, m_config.responseTimeout);
×
UNCOV
1492
        error();
×
1493
      });
×
1494
  }
1495
}
1496

1497
void Kernel::setFastClockMaster(bool enable)
×
1498
{
1499
  assert(isKernelThread());
×
1500
  if(enable)
×
1501
  {
1502
    const auto clock = m_fastClock.load();
×
UNCOV
1503
    FastClockSlotWriteData fastClock;
×
UNCOV
1504
    fastClock.clk_rate = clock.multiplier;
×
1505
    fastClock.setHour(clock.hour);
×
UNCOV
1506
    fastClock.setMinute(clock.minute);
×
1507
    fastClock.setValid(true);
×
1508
    fastClock.setId(idTraintastic);
×
1509
    updateChecksum(fastClock);
×
1510
    send(fastClock, HighPriority);
×
1511
  }
1512
  else
1513
    send(FastClockSlotWriteData(), HighPriority);
×
UNCOV
1514
}
×
1515

1516
void Kernel::disableClockEvents()
×
1517
{
UNCOV
1518
  m_clockChangeConnection.disconnect();
×
1519
}
×
1520

1521
void Kernel::enableClockEvents()
×
1522
{
1523
  if(m_clockChangeConnection.connected() || !m_clock)
×
1524
    return;
×
1525

1526
  m_clockChangeConnection = m_clock->onChange.connect(
×
UNCOV
1527
    [this](Clock::ClockEvent event, uint8_t multiplier, Time time)
×
1528
    {
UNCOV
1529
      m_fastClock.store(FastClock{event == Clock::ClockEvent::Freeze ? multiplierFreeze : multiplier, time.hour(), time.minute()});
×
UNCOV
1530
      if(event == Clock::ClockEvent::Freeze || event == Clock::ClockEvent::Resume)
×
1531
      {
1532
        m_ioContext.post(
×
UNCOV
1533
          [this]()
×
1534
          {
UNCOV
1535
            setFastClockMaster(true);
×
1536
          });
×
1537
      }
UNCOV
1538
    });
×
1539
}
1540

1541
void Kernel::startFastClockSyncTimer()
×
1542
{
UNCOV
1543
  assert(isKernelThread());
×
UNCOV
1544
  assert(m_config.fastClockSyncInterval > 0);
×
UNCOV
1545
  m_fastClockSyncTimer.expires_after(boost::asio::chrono::seconds(m_config.fastClockSyncInterval));
×
1546
  m_fastClockSyncTimer.async_wait(std::bind(&Kernel::fastClockSyncTimerExpired, this, std::placeholders::_1));
×
1547
}
×
1548

1549
void Kernel::stopFastClockSyncTimer()
×
1550
{
1551
  assert(isKernelThread());
×
UNCOV
1552
  m_fastClockSyncTimer.cancel();
×
UNCOV
1553
}
×
1554

UNCOV
1555
void Kernel::fastClockSyncTimerExpired(const boost::system::error_code& ec)
×
1556
{
1557
  assert(isKernelThread());
×
1558

1559
  if(ec || !m_config.fastClockSyncEnabled || !m_fastClockSupported)
×
1560
    return;
×
1561

1562
  send(RequestSlotData(SLOT_FAST_CLOCK));
×
1563

UNCOV
1564
  startFastClockSyncTimer();
×
1565
}
1566

1567
template<uint8_t First, uint8_t Last, class T>
1568
bool Kernel::updateFunctions(LocoSlot& slot, const T& message)
×
1569
{
1570
  assert(isKernelThread());
×
1571

1572
  bool changed = false;
×
UNCOV
1573
  for(uint8_t i = First; i <= Last; ++i)
×
1574
  {
1575
    if(slot.functions[i] != message.f(i))
×
1576
    {
1577
      slot.functions[i] = toTriState(message.f(i));
×
1578
      changed = true;
×
1579
    }
1580
  }
1581

UNCOV
1582
  EventLoop::call(
×
UNCOV
1583
    [this, address=slot.address, message]()
×
1584
    {
1585
      if(auto decoder = getDecoder(address))
×
UNCOV
1586
        for(uint8_t i = First; i <= Last; ++i)
×
1587
          decoder->setFunctionValue(i, message.f(i));
×
1588
    });
1589

1590
  return changed;
×
1591
}
1592

1593
void Kernel::startPCAP(PCAPOutput pcapOutput)
×
1594
{
UNCOV
1595
  assert(isKernelThread());
×
UNCOV
1596
  assert(!m_pcap);
×
1597

UNCOV
1598
  const uint32_t DLT_USER0 = 147; //! \todo change to LocoNet DLT once it is registered
×
1599

1600
  try
1601
  {
1602
    switch(pcapOutput)
×
1603
    {
1604
      case PCAPOutput::File:
×
1605
      {
UNCOV
1606
        const auto filename = m_debugDir / logId += dateTimeStr() += ".pcap";
×
UNCOV
1607
        EventLoop::call(
×
1608
          [this, filename]()
×
1609
          {
1610
            Log::log(logId, LogMessage::N2004_STARTING_PCAP_FILE_LOG_X, filename);
×
1611
          });
×
1612
        m_pcap = std::make_unique<PCAPFile>(filename, DLT_USER0);
×
UNCOV
1613
        break;
×
1614
      }
×
1615
      case PCAPOutput::Pipe:
×
1616
      {
1617
        std::filesystem::path pipe;
×
1618
#ifdef WIN32
1619
        return; //! \todo Implement
1620
#else // unix
UNCOV
1621
        pipe = std::filesystem::temp_directory_path() / "traintastic-server" / logId;
×
1622
#endif
1623
        EventLoop::call(
×
1624
          [this, pipe]()
×
1625
          {
1626
            Log::log(logId, LogMessage::N2005_STARTING_PCAP_LOG_PIPE_X, pipe);
×
UNCOV
1627
          });
×
1628
        m_pcap = std::make_unique<PCAPPipe>(std::move(pipe), DLT_USER0);
×
1629
        break;
×
UNCOV
1630
      }
×
1631
    }
1632
  }
1633
  catch(const std::exception& e)
×
1634
  {
1635
    EventLoop::call(
×
1636
      [this, what=std::string(e.what())]()
×
1637
      {
UNCOV
1638
        Log::log(logId, LogMessage::E2021_STARTING_PCAP_LOG_FAILED_X, what);
×
UNCOV
1639
      });
×
UNCOV
1640
  }
×
UNCOV
1641
}
×
1642

1643

UNCOV
1644
bool Kernel::SendQueue::append(const Message& message)
×
1645
{
UNCOV
1646
  const uint8_t messageSize = message.size();
×
UNCOV
1647
  if(m_bytes + messageSize > threshold())
×
UNCOV
1648
    return false;
×
1649

UNCOV
1650
  memcpy(m_front + m_bytes, &message, messageSize);
×
UNCOV
1651
  m_bytes += messageSize;
×
1652

UNCOV
1653
  return true;
×
1654
}
1655

UNCOV
1656
void Kernel::SendQueue::pop()
×
1657
{
UNCOV
1658
  const uint8_t messageSize = front().size();
×
UNCOV
1659
  m_front += messageSize;
×
UNCOV
1660
  m_bytes -= messageSize;
×
1661

UNCOV
1662
  if(static_cast<std::size_t>(m_front - m_buffer.data()) >= threshold())
×
1663
  {
UNCOV
1664
    memmove(m_buffer.data(), m_front, m_bytes);
×
UNCOV
1665
    m_front = m_buffer.data();
×
1666
  }
UNCOV
1667
}
×
1668

UNCOV
1669
void Kernel::SendQueue::clear()
×
1670
{
UNCOV
1671
  m_bytes = 0;
×
UNCOV
1672
}
×
1673

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