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

traintastic / traintastic / 20638780725

01 Jan 2026 12:44PM UTC coverage: 27.155% (-0.4%) from 27.527%
20638780725

push

github

reinder
bumped copyright year to 2026

7873 of 28993 relevant lines covered (27.15%)

191.86 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-2026 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 "lncv/lncvutils.hpp"
26
#include "messages.hpp"
27
#include "../../decoder/decoder.hpp"
28
#include "../../decoder/decoderchangeflags.hpp"
29
#include "../../decoder/decodercontroller.hpp"
30
#include "../../decoder/list/decoderlist.hpp"
31
#include "../../input/inputcontroller.hpp"
32
#include "../../output/outputcontroller.hpp"
33
#include "../../identification/identificationcontroller.hpp"
34
#include "../../../utils/datetimestr.hpp"
35
#include "../../../utils/setthreadname.hpp"
36
#include "../../../utils/inrange.hpp"
37
#include "../../../pcap/pcapfile.hpp"
38
#include "../../../pcap/pcappipe.hpp"
39
#include "../../../core/eventloop.hpp"
40
#include "../../../core/objectproperty.tpp"
41
#include "../../../log/log.hpp"
42
#include "../../../log/logmessageexception.hpp"
43
#include "../../../clock/clock.hpp"
44
#include "../../../traintastic/traintastic.hpp"
45
#include "../dcc/dcc.hpp"
46
#include "../dcc/messages.hpp"
47

48
namespace LocoNet {
49

50
static constexpr uint8_t multiplierFreeze = 0;
51

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

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

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

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

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

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

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

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

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

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

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

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

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

165
void Kernel::setOnStateChanged(std::function<void(bool, bool)> callback)
×
166
{
167
  assert(isEventLoopThread());
×
168
  assert(!m_started);
×
169
  m_onStateChanged = std::move(callback);
×
170
}
×
171

172
void Kernel::setClock(std::shared_ptr<Clock> clock)
×
173
{
174
  assert(isEventLoopThread());
×
175
  assert(!m_started);
×
176
  assert(clock);
×
177
  m_clock = std::move(clock);
×
178
  m_fastClock.store(FastClock{m_clock->running ? m_clock->multiplier : multiplierFreeze, m_clock->hour, m_clock->minute});
×
179
}
×
180

181
void Kernel::setDecoderController(DecoderController* decoderController)
×
182
{
183
  assert(isEventLoopThread());
×
184
  assert(!m_started);
×
185
  m_decoderController = decoderController;
×
186
}
×
187

188
void Kernel::setInputController(InputController* inputController)
×
189
{
190
  assert(isEventLoopThread());
×
191
  assert(!m_started);
×
192
  m_inputController = inputController;
×
193
}
×
194

195
void Kernel::setOutputController(OutputController* outputController)
×
196
{
197
  assert(isEventLoopThread());
×
198
  assert(!m_started);
×
199
  m_outputController = outputController;
×
200
}
×
201

202
void Kernel::setIdentificationController(IdentificationController* identificationController)
×
203
{
204
  assert(isEventLoopThread());
×
205
  assert(!m_started);
×
206
  m_identificationController= identificationController;
×
207
}
×
208

209
void Kernel::start()
×
210
{
211
  assert(isEventLoopThread());
×
212
  assert(m_ioHandler);
×
213
  assert(!m_started);
×
214

215
  // reset all state values
216
  m_globalPower = TriState::Undefined;
×
217
  m_emergencyStop = TriState::Undefined;
×
218
  m_addressToSlot.clear();
×
219
  m_slots.clear();
×
220
  m_pendingSlotMessages.clear();
×
221
  m_inputValues.fill(TriState::Undefined);
×
222
  m_outputValues.fill(OutputPairValue::Undefined);
×
223

224
  if(m_config.listenOnly)
×
225
    Log::log(logId, LogMessage::N2006_LISTEN_ONLY_MODE_ACTIVATED);
×
226

227
  m_thread = std::thread(
×
228
    [this]()
×
229
    {
230
      setThreadName("loconet");
×
231
      auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
×
232
      m_ioContext.run();
×
233
    });
×
234

235
  if(m_config.fastClock == LocoNetFastClock::Master)
×
236
    enableClockEvents();
×
237

238
  m_ioContext.post(
×
239
    [this]()
×
240
    {
241
      if(m_config.pcap)
×
242
        startPCAP(m_config.pcapOutput);
×
243

244
      try
245
      {
246
        m_ioHandler->start();
×
247
      }
248
      catch(const LogMessageException& e)
×
249
      {
250
        EventLoop::call(
×
251
          [this, e]()
×
252
          {
253
            Log::log(logId, e.message(), e.args());
×
254
            error();
×
255
          });
×
256
        return;
×
257
      }
×
258
    });
259

260
#ifndef NDEBUG
261
  m_started = true;
×
262
#endif
263
}
×
264

265
void Kernel::stop()
×
266
{
267
  assert(isEventLoopThread());
×
268

269
  disableClockEvents();
×
270

271
  m_ioContext.post(
×
272
    [this]()
×
273
    {
274
      m_waitingForEchoTimer.cancel();
×
275
      m_waitingForResponseTimer.cancel();
×
276
      m_fastClockSyncTimer.cancel();
×
277
      m_ioHandler->stop();
×
278
      m_pcap.reset();
×
279
    });
×
280

281
  m_ioContext.stop();
×
282

283
  m_thread.join();
×
284

285
#ifndef NDEBUG
286
  m_started = false;
×
287
#endif
288
}
×
289

290
void Kernel::started()
×
291
{
292
  assert(isKernelThread());
×
293

294
  if(m_config.fastClock == LocoNetFastClock::Master)
×
295
    setFastClockMaster(true);
×
296

297
  if(m_config.fastClockSyncEnabled)
×
298
    startFastClockSyncTimer();
×
299

300
  for(uint8_t slot = SLOT_LOCO_MIN; slot <= m_config.locomotiveSlots; slot++)
×
301
    send(RequestSlotData(slot), LowPriority);
×
302

303
  KernelBase::started();
×
304
}
×
305

306
void Kernel::receive(const Message& message)
×
307
{
308
  assert(isKernelThread());
×
309
  assert(isValid(message)); // only valid messages should be received
×
310

311
  if(m_pcap)
×
312
    m_pcap->writeRecord(&message, message.size());
×
313

314
  if(m_config.debugLogRXTX)
×
315
    EventLoop::call([this, msg=toString(message)](){ Log::log(logId, LogMessage::D2002_RX_X, msg); });
×
316

317
  const bool isEcho = (message == lastSentMessage());
×
318
  bool isResponse = false;
×
319
  if(m_waitingForEcho && isEcho)
×
320
  {
321
    m_waitingForEcho = false;
×
322
    m_waitingForEchoTimer.cancel();
×
323
    if(!m_waitingForResponse)
×
324
    {
325
      m_sendQueue[m_sentMessagePriority].pop();
×
326
      sendNextMessage();
×
327
    }
328
  }
329
  else if(m_waitingForResponse)
×
330
  {
331
    isResponse = isValidResponse(lastSentMessage(), message);
×
332
  }
333

334
  switch(message.opCode)
×
335
  {
336
    case OPC_GPON:
×
337
      if(!isEcho) // sent by another LocoNet device, not by Traintastic
×
338
      {
339
        m_globalPower = TriState::True;
×
340
        m_emergencyStop = TriState::False;
×
341
        if(m_onStateChanged) [[likely]]
×
342
        {
343
          EventLoop::call(
×
344
            [this]()
×
345
            {
346
              m_onStateChanged(true, true);
×
347
            });
×
348
        }
349
        EventLoop::call(std::bind_front(&Kernel::resume, this));
×
350
      }
351
      break;
×
352

353
    case OPC_GPOFF:
×
354
      if(!isEcho) // sent by another LocoNet device, not by Traintastic
×
355
      {
356
        m_globalPower = TriState::False;
×
357
        if(m_onStateChanged) [[likely]]
×
358
        {
359
          EventLoop::call(
×
360
            [this]()
×
361
            {
362
              m_onStateChanged(false, false);
×
363
            });
×
364
        }
365
      }
366
      break;
×
367

368
    case OPC_IDLE:
×
369
      if(!isEcho) // sent by another LocoNet device, not by Traintastic
×
370
      {
371
        m_emergencyStop = TriState::True;
×
372
        if(m_onStateChanged) [[likely]]
×
373
        {
374
          EventLoop::call(
×
375
            [this]()
×
376
            {
377
              m_onStateChanged(m_globalPower == TriState::True, false);
×
378
            });
×
379
        }
380
      }
381
      break;
×
382

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

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

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

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

432
            updateFunctions<0, 4>(*slot, locoDirF);
×
433
          }
434
        }
435
      }
436
      break;
×
437

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

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

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

487
            m_inputValues[inputRep.fullAddress()] = value;
×
488

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

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

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

520
    case OPC_SW_REP:
×
521
      break; // unimplemented
×
522

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

535
        LocoSlot* locoSlot = getLocoSlot(slotReadData.slot, false);
×
536
        assert(locoSlot);
×
537

538
        if(!locoSlot->isAddressValid())
×
539
          m_addressToSlot[slotReadData.address()] = slot;
×
540

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

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

563
        updateFunctions<0, 8>(*locoSlot, slotReadData);
×
564

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

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

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

624
          EventLoop::call(
×
625
            [this, stoppedFastClockSyncTimer=m_config.fastClockSyncEnabled]()
×
626
            {
627
              Log::log(logId, LogMessage::W2007_COMMAND_STATION_DOES_NOT_SUPPORT_THE_FAST_CLOCK_SLOT);
×
628
              if(stoppedFastClockSyncTimer)
×
629
                Log::log(logId, LogMessage::N2003_STOPPED_SENDING_FAST_CLOCK_SYNC);
×
630
            });
×
631
        }
632
      }
633
      else if(longAck.respondingOpCode() == OPC_IMM_PACKET)
×
634
      {
635
        if(m_waitingForLNCVReadResponse)
×
636
        {
637
          if(!m_lncvReads.empty() &&
×
638
              m_lncvReads.front().moduleId == m_pendingLNCVRead.moduleId &&
×
639
              m_lncvReads.front().address == m_pendingLNCVRead.address &&
×
640
              m_lncvReads.front().lncv == m_pendingLNCVRead.lncv)
×
641
          {
642
            m_pendingLNCVRead.reset();
×
643
            auto ec = LNCV::parseLongAck(longAck.ack1);
×
644
            if(!ec)
×
645
            {
646
              // 0x7F indicates a successful LONG_ACK, but for a read operation
647
              // we expect a proper read response message instead. Receiving
648
              // only a LONG_ACK is unexpected, so we treat it as
649
              // unexpected response until proven otherwise.
650
              ec = LNCV::Error::unexpectedResponse();
×
651
            }
652
            EventLoop::call(
×
653
              [callback=std::move(m_lncvReads.front().callback), ec]()
×
654
              {
655
                callback(0, ec);
×
656
              });
×
657
            m_lncvReads.pop();
×
658
          }
659
        }
660
        else if(m_lncvActive && m_onLNCVReadResponse &&
×
661
            longAck.ack1 == 0x7F &&
×
662
            Uhlenbrock::LNCVWrite::check(lastSentMessage()))
×
663
        {
664
          const auto& lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(lastSentMessage());
×
665
          if(lncvWrite.lncv() == 0)
×
666
          {
667
            m_lncvModuleAddress = lncvWrite.value();
×
668
          }
669

670
          EventLoop::call(
×
671
            [this, lncvWrite]()
×
672
            {
673
              m_onLNCVReadResponse(true, lncvWrite.lncv(), lncvWrite.value());
×
674
            });
×
675
        }
676
      }
677
      break;
×
678
    }
679
    case OPC_SLOT_STAT1:
×
680
      break; // unimplemented
×
681

682
    case OPC_CONSIST_FUNC:
×
683
      break; // unimplemented
×
684

685
    case OPC_UNLINK_SLOTS:
×
686
      break; // unimplemented
×
687

688
    case OPC_LINK_SLOTS:
×
689
      break; // unimplemented
×
690

691
    case OPC_MOVE_SLOTS:
×
692
      break; // unimplemented
×
693

694
    case OPC_RQ_SL_DATA:
×
695
      break; // unimplemented
×
696

697
    case OPC_SW_STATE:
×
698
      break; // unimplemented
×
699

700
    case OPC_SW_ACK:
×
701
      break; // unimplemented
×
702

703
    case OPC_LOCO_ADR:
×
704
      break; // unimplemented
×
705

706
    case OPC_MULTI_SENSE:
×
707
    {
708
      const auto& multiSense = static_cast<const MultiSense&>(message);
×
709
      if(multiSense.isTransponder())
×
710
      {
711
        EventLoop::call(
×
712
          [this, multiSenseTransponder=static_cast<const MultiSenseTransponder&>(multiSense)]()
×
713
          {
714
            m_identificationController->identificationEvent(
×
715
              IdentificationController::defaultIdentificationChannel,
716
              1 + multiSenseTransponder.sensorAddress(),
×
717
              multiSenseTransponder.isPresent() ? IdentificationEventType::Present : IdentificationEventType::Absent,
×
718
              multiSenseTransponder.transponderAddress(),
×
719
              Direction::Unknown,
720
              0);
721
          });
×
722
      }
723
      break;
×
724
    }
725
    case OPC_D4:
×
726
      if(m_decoderController)
×
727
      {
728
        const auto* bytes = reinterpret_cast<const uint8_t*>(&message);
×
729
        if(bytes[1] == 0x20)
×
730
        {
731
          switch(bytes[3])
×
732
          {
733
            case 0x08:
×
734
            {
735
              const auto& locoF13F19 = static_cast<const LocoF13F19&>(message);
×
736
              if(LocoSlot* slot = getLocoSlot(locoF13F19.slot))
×
737
              {
738
                if(!slot->isAddressValid())
×
739
                {
740
                  send(RequestSlotData(locoF13F19.slot));
×
741
                }
742
                updateFunctions<13, 19>(*slot, locoF13F19);
×
743
              }
744
              break;
×
745
            }
746
            case 0x05:
×
747
            {
748
              const auto& locoF12F20F28 = static_cast<const LocoF12F20F28&>(message);
×
749
              if(LocoSlot* slot = getLocoSlot(locoF12F20F28.slot))
×
750
              {
751
                if(!slot->isAddressValid())
×
752
                {
753
                  send(RequestSlotData(locoF12F20F28.slot));
×
754
                }
755

756
                const bool changed =
757
                  slot->functions[12] != locoF12F20F28.f12() ||
×
758
                  slot->functions[20] != locoF12F20F28.f20() ||
×
759
                  slot->functions[28] != locoF12F20F28.f28();
×
760

761
                if(changed)
×
762
                {
763
                  slot->functions[12] = toTriState(locoF12F20F28.f12());
×
764
                  slot->functions[20] = toTriState(locoF12F20F28.f20());
×
765
                  slot->functions[28] = toTriState(locoF12F20F28.f28());
×
766

767
                  EventLoop::call(
×
768
                    [this, address=slot->address, f12=locoF12F20F28.f12(), f20=locoF12F20F28.f20(), f28=locoF12F20F28.f28()]()
×
769
                    {
770
                      if(auto decoder = getDecoder(address))
×
771
                      {
772
                        decoder->setFunctionValue(12, f12);
×
773
                        decoder->setFunctionValue(20, f20);
×
774
                        decoder->setFunctionValue(28, f28);
×
775
                      }
×
776
                    });
×
777
                }
778
              }
779
              break;
×
780
            }
781
            case 0x09:
×
782
            {
783
              const auto& locoF21F27 = static_cast<const LocoF21F27&>(message);
×
784
              if(LocoSlot* slot = getLocoSlot(locoF21F27.slot))
×
785
              {
786
                if(!slot->isAddressValid())
×
787
                {
788
                  send(RequestSlotData(locoF21F27.slot));
×
789
                }
790
                updateFunctions<21, 27>(*slot, locoF21F27);
×
791
              }
792
              break;
×
793
            }
794
          }
795
        }
796
      }
797
      break;
×
798

799
    case OPC_MULTI_SENSE_LONG:
×
800
    {
801
      const auto& multiSense = static_cast<const MultiSenseLong&>(message);
×
802
      if(multiSense.code() == MultiSenseLong::Code::ReleaseTransponder || multiSense.code() == MultiSenseLong::Code::DetectTransponder)
×
803
      {
804
        EventLoop::call(
×
805
          [this, multiSense]()
×
806
          {
807
            m_identificationController->identificationEvent(
×
808
              IdentificationController::defaultIdentificationChannel,
809
              1 + multiSense.sensorAddress(),
×
810
              multiSense.code()  == MultiSenseLong::Code::DetectTransponder ? IdentificationEventType::Present : IdentificationEventType::Absent,
×
811
              multiSense.transponderAddress(),
×
812
              multiSense.transponderDirection(),
813
              0);
814
          });
×
815
      }
816
      break;
×
817
    }
818
    case OPC_E4:
×
819
      if(static_cast<const Uhlenbrock::Lissy&>(message).type() == Uhlenbrock::Lissy::Type::AddressCategoryDirection)
×
820
      {
821
        EventLoop::call(
×
822
          [this, lissy=static_cast<const Uhlenbrock::LissyAddressCategoryDirection&>(message)]()
×
823
          {
824
            m_identificationController->identificationEvent(
×
825
              IdentificationController::defaultIdentificationChannel,
826
              lissy.sensorAddress(),
×
827
              IdentificationEventType::Seen,
828
              lissy.decoderAddress(),
×
829
              lissy.direction(),
830
              lissy.category());
×
831
          });
×
832
      }
833
      break;
×
834

835
    case OPC_PEER_XFER:
×
836
      if(isResponse)
×
837
      {
838
        if(Uhlenbrock::ReadSpecialOptionReply::check(message))
×
839
        {
840
          [[maybe_unused]] const auto& readSpecialOptionReply = static_cast<const Uhlenbrock::ReadSpecialOptionReply&>(message);
×
841
        }
842
        else if(Uhlenbrock::LNCVReadResponse::check(message))
×
843
        {
844
          const auto& lncvReadResponse = static_cast<const Uhlenbrock::LNCVReadResponse&>(message);
×
845

846
          if(!m_lncvReads.empty() &&
×
847
              m_lncvReads.front().moduleId == lncvReadResponse.moduleId() &&
×
848
              m_lncvReads.front().address == m_pendingLNCVRead.address &&
×
849
              m_lncvReads.front().lncv == lncvReadResponse.lncv())
×
850
          {
851
            EventLoop::call(
×
852
              [callback=m_lncvReads.front().callback, value=lncvReadResponse.value()]()
×
853
              {
854
                callback(value, {});
×
855
              });
×
856
            m_lncvReads.pop();
×
857
          }
858
          else if(m_onLNCVReadResponse)
×
859
          {
860
            if(lncvReadResponse.lncv() == 0)
×
861
            {
862
              m_lncvActive = true;
×
863
              m_lncvModuleAddress = lncvReadResponse.value();
×
864
            }
865

866
            EventLoop::call(
×
867
              [this, lncvReadResponse]()
×
868
              {
869
                m_onLNCVReadResponse(true, lncvReadResponse.lncv(), lncvReadResponse.value());
×
870
              });
×
871
          }
872

873
          m_pendingLNCVRead.reset();
×
874
        }
875
      }
876
      break;
×
877

878
    case OPC_IMM_PACKET:
×
879
      if(m_decoderController)
×
880
      {
881
        if(isSignatureMatch<LocoF9F12IMMShortAddress>(message))
×
882
        {
883
          const auto& locoF9F12 = static_cast<const LocoF9F12IMMShortAddress&>(message);
×
884
          if(LocoSlot* slot = getLocoSlotByAddress(locoF9F12.address()))
×
885
            updateFunctions<9, 12>(*slot, locoF9F12);
×
886
          else
887
            send(LocoAdr(locoF9F12.address()));
×
888
        }
889
        else if(isSignatureMatch<LocoF9F12IMMLongAddress>(message))
×
890
        {
891
          const auto& locoF9F12 = static_cast<const LocoF9F12IMMLongAddress&>(message);
×
892
          if(LocoSlot* slot = getLocoSlotByAddress(locoF9F12.address()))
×
893
            updateFunctions<9, 12>(*slot, locoF9F12);
×
894
          else
895
            send(LocoAdr(locoF9F12.address()));
×
896
        }
897
        else if(isSignatureMatch<LocoF13F20IMMShortAddress>(message))
×
898
        {
899
          const auto& locoF13F20 = static_cast<const LocoF13F20IMMShortAddress&>(message);
×
900
          if(LocoSlot* slot = getLocoSlotByAddress(locoF13F20.address()))
×
901
            updateFunctions<13, 20>(*slot, locoF13F20);
×
902
          else
903
            send(LocoAdr(locoF13F20.address()));
×
904
        }
905
        else if(isSignatureMatch<LocoF13F20IMMLongAddress>(message))
×
906
        {
907
          const auto& locoF13F20 = static_cast<const LocoF13F20IMMLongAddress&>(message);
×
908
          if(LocoSlot* slot = getLocoSlotByAddress(locoF13F20.address()))
×
909
            updateFunctions<13, 20>(*slot, locoF13F20);
×
910
          else
911
            send(LocoAdr(locoF13F20.address()));
×
912
        }
913
        else if(isSignatureMatch<LocoF21F28IMMShortAddress>(message))
×
914
        {
915
          const auto& locoF21F28 = static_cast<const LocoF21F28IMMShortAddress&>(message);
×
916
          if(LocoSlot* slot = getLocoSlotByAddress(locoF21F28.address()))
×
917
            updateFunctions<21, 28>(*slot, locoF21F28);
×
918
          else
919
            send(LocoAdr(locoF21F28.address()));
×
920
        }
921
        else if(isSignatureMatch<LocoF21F28IMMLongAddress>(message))
×
922
        {
923
          const auto& locoF21F28 = static_cast<const LocoF21F28IMMLongAddress&>(message);
×
924
          if(LocoSlot* slot = getLocoSlotByAddress(locoF21F28.address()))
×
925
            updateFunctions<21, 28>(*slot, locoF21F28);
×
926
          else
927
            send(LocoAdr(locoF21F28.address()));
×
928
        }
929
      }
930
      break; // unimplemented
×
931

932
    case OPC_WR_SL_DATA:
×
933
      break; // unimplemented
×
934
  }
935

936
  if(m_waitingForResponse && isResponse)
×
937
  {
938
    m_waitingForResponse = false;
×
939
    m_waitingForResponseTimer.cancel();
×
940
    m_sendQueue[m_sentMessagePriority].pop();
×
941
    sendNextMessage();
×
942
  }
943
}
×
944

945
void Kernel::setState(bool powerOn, bool run)
×
946
{
947
  assert(isEventLoopThread());
×
948
  m_ioContext.post(
×
949
    [this, powerOn, run]()
×
950
    {
951
      if(!powerOn) // disable power
×
952
      {
953
        if(m_emergencyStop != TriState::True)
×
954
        {
955
          send(Idle(), HighPriority); // estop trains
×
956
        }
957
        if(m_globalPower != TriState::False)
×
958
        {
959
          send(GlobalPowerOff(), HighPriority);
×
960
        }
961
      }
962
      else
963
      {
964
        if(m_globalPower != TriState::True) // enable power
×
965
        {
966
          send(GlobalPowerOn(), HighPriority);
×
967
        }
968
        if(run && m_emergencyStop != TriState::False)
×
969
        {
970
          m_emergencyStop = TriState::False;
×
971
          EventLoop::call(std::bind_front(&Kernel::resume, this));
×
972
        }
973
        else if(!run && m_emergencyStop != TriState::True)
×
974
        {
975
          send(Idle(), HighPriority); // estop trains
×
976
        }
977
      }
978
    });
×
979
}
×
980

981
void Kernel::resume()
×
982
{
983
  assert(isEventLoopThread());
×
984

985
  auto& list = *m_decoderController->decoders.value();
×
986
  for(const auto& decoder : list)
×
987
  {
988
    decoderChanged(*decoder, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps, 0);
×
989

990
    if(decoder->hasFunction(5) ||
×
991
        decoder->hasFunction(6) ||
×
992
        decoder->hasFunction(7) ||
×
993
        decoder->hasFunction(8))
×
994
    {
995
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 5); // trigger F5-F8 update
×
996
    }
997
    if(decoder->hasFunction(9) ||
×
998
        decoder->hasFunction(10) ||
×
999
        decoder->hasFunction(11) ||
×
1000
        decoder->hasFunction(12))
×
1001
    {
1002
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 9); // trigger F9-F12 update
×
1003
    }
1004
    if(decoder->hasFunction(13) ||
×
1005
        decoder->hasFunction(14) ||
×
1006
        decoder->hasFunction(15) ||
×
1007
        decoder->hasFunction(16) ||
×
1008
        decoder->hasFunction(17) ||
×
1009
        decoder->hasFunction(18) ||
×
1010
        decoder->hasFunction(19) ||
×
1011
        decoder->hasFunction(20))
×
1012
    {
1013
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 13); // trigger F13-F20 update
×
1014
    }
1015
    if(decoder->hasFunction(21) ||
×
1016
        decoder->hasFunction(22) ||
×
1017
        decoder->hasFunction(23) ||
×
1018
        decoder->hasFunction(24) ||
×
1019
        decoder->hasFunction(25) ||
×
1020
        decoder->hasFunction(26) ||
×
1021
        decoder->hasFunction(27) ||
×
1022
        decoder->hasFunction(28))
×
1023
    {
1024
      decoderChanged(*decoder, DecoderChangeFlags::FunctionValue, 21); // trigger F21-F28 update
×
1025
    }
1026
  }
1027
}
×
1028

1029
bool Kernel::send(std::span<uint8_t> packet)
×
1030
{
1031
  assert(isEventLoopThread());
×
1032

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

1036
  std::vector<uint8_t> data(packet.data(), packet.data() + packet.size());
×
1037
  data.push_back(calcChecksum(*reinterpret_cast<Message*>(data.data())));
×
1038

1039
  if(!isValid(*reinterpret_cast<Message*>(data.data())))
×
1040
    return false;
×
1041

1042
  m_ioContext.post(
×
1043
    [this, message=std::move(data)]()
×
1044
    {
1045
      send(*reinterpret_cast<const Message*>(message.data()));
×
1046
    });
×
1047

1048
  return true;
×
1049
}
×
1050

1051
bool Kernel::immPacket(std::span<const uint8_t> dccPacket, uint8_t repeat)
×
1052
{
1053
  assert(isEventLoopThread());
×
1054

1055
  if(dccPacket.size() > ImmPacket::dccPacketSizeMax || repeat > ImmPacket::repeatMax)
×
1056
    return false;
×
1057

1058
  postSend(ImmPacket(dccPacket, repeat));
×
1059
  return true;
×
1060
}
1061

1062
void Kernel::readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback)
×
1063
{
1064
  assert(isEventLoopThread());
×
1065

1066
  m_ioContext.post(
×
1067
    [this, moduleId, address, lncv, callback]()
×
1068
    {
1069
      m_lncvReads.emplace(LNCVRead{moduleId, address, lncv, std::move(callback)});
×
1070
      send(Uhlenbrock::LNCVRead(moduleId, address, lncv), LocoNet::Kernel::LowPriority);
×
1071
    });
×
1072
}
×
1073

1074
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
1075
{
1076
  assert(isEventLoopThread());
×
1077

1078
  if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle))
×
1079
  {
1080
    const auto speedStep = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, SPEED_MAX - 1);
×
1081
    if(m_emergencyStop == TriState::False || decoder.emergencyStop || speedStep == SPEED_STOP)
×
1082
    {
1083
      // only send speed updates if bus estop isn't active, except for speed STOP and ESTOP
1084
      LocoSpd message{static_cast<uint8_t>(decoder.emergencyStop ? SPEED_ESTOP : (speedStep > 0 ? 1 + speedStep : SPEED_STOP))};
×
1085
      postSend(decoder.address, message);
×
1086
    }
1087
  }
1088

1089
  if(has(changes, DecoderChangeFlags::FunctionValue | DecoderChangeFlags::Direction))
×
1090
  {
1091
    if(functionNumber <= 4 || has(changes, DecoderChangeFlags::Direction))
×
1092
    {
1093
      LocoDirF message{
1094
        decoder.direction,
1095
        decoder.getFunctionValue(0),
×
1096
        decoder.getFunctionValue(1),
×
1097
        decoder.getFunctionValue(2),
×
1098
        decoder.getFunctionValue(3),
×
1099
        decoder.getFunctionValue(4)};
×
1100
      postSend(decoder.address, message);
×
1101
    }
1102
    else if(functionNumber <= 8)
×
1103
    {
1104
      LocoSnd message{
1105
        decoder.getFunctionValue(5),
×
1106
        decoder.getFunctionValue(6),
×
1107
        decoder.getFunctionValue(7),
×
1108
        decoder.getFunctionValue(8)};
×
1109
      postSend(decoder.address, message);
×
1110
    }
1111
    else if(functionNumber <= 28)
×
1112
    {
1113
      switch(m_config.f9f28)
×
1114
      {
1115
        case LocoNetF9F28::IMMPacket:
×
1116
        {
1117
          const bool longAddress = decoder.protocol == DecoderProtocol::DCCLong;
×
1118

1119
          if(functionNumber <= 12)
×
1120
          {
1121
            if(longAddress)
×
1122
            {
1123
              postSend(
×
1124
                LocoF9F12IMMLongAddress(
×
1125
                  decoder.address,
1126
                  decoder.getFunctionValue(9),
×
1127
                  decoder.getFunctionValue(10),
×
1128
                  decoder.getFunctionValue(11),
×
1129
                  decoder.getFunctionValue(12)));
×
1130
            }
1131
            else
1132
            {
1133
              postSend(
×
1134
                LocoF9F12IMMShortAddress(
×
1135
                  decoder.address,
1136
                  decoder.getFunctionValue(9),
×
1137
                  decoder.getFunctionValue(10),
×
1138
                  decoder.getFunctionValue(11),
×
1139
                  decoder.getFunctionValue(12)));
×
1140
            }
1141
          }
1142
          else if(functionNumber <= 20)
×
1143
          {
1144
            if(longAddress)
×
1145
            {
1146
              postSend(
×
1147
                LocoF13F20IMMLongAddress(
×
1148
                  decoder.address,
1149
                  decoder.getFunctionValue(13),
×
1150
                  decoder.getFunctionValue(14),
×
1151
                  decoder.getFunctionValue(15),
×
1152
                  decoder.getFunctionValue(16),
×
1153
                  decoder.getFunctionValue(17),
×
1154
                  decoder.getFunctionValue(18),
×
1155
                  decoder.getFunctionValue(19),
×
1156
                  decoder.getFunctionValue(20)));
×
1157
            }
1158
            else
1159
            {
1160
              postSend(
×
1161
                LocoF13F20IMMShortAddress(
×
1162
                  decoder.address,
1163
                  decoder.getFunctionValue(13),
×
1164
                  decoder.getFunctionValue(14),
×
1165
                  decoder.getFunctionValue(15),
×
1166
                  decoder.getFunctionValue(16),
×
1167
                  decoder.getFunctionValue(17),
×
1168
                  decoder.getFunctionValue(18),
×
1169
                  decoder.getFunctionValue(19),
×
1170
                  decoder.getFunctionValue(20)));
×
1171
            }
1172
          }
1173
          else if(functionNumber <= 28)
×
1174
          {
1175
            if(longAddress)
×
1176
            {
1177
              postSend(
×
1178
                LocoF21F28IMMLongAddress(
×
1179
                  decoder.address,
1180
                  decoder.getFunctionValue(21),
×
1181
                  decoder.getFunctionValue(22),
×
1182
                  decoder.getFunctionValue(23),
×
1183
                  decoder.getFunctionValue(24),
×
1184
                  decoder.getFunctionValue(25),
×
1185
                  decoder.getFunctionValue(26),
×
1186
                  decoder.getFunctionValue(27),
×
1187
                  decoder.getFunctionValue(28)));
×
1188
            }
1189
            else
1190
            {
1191
              postSend(
×
1192
                LocoF21F28IMMShortAddress(
×
1193
                  decoder.address,
1194
                  decoder.getFunctionValue(21),
×
1195
                  decoder.getFunctionValue(22),
×
1196
                  decoder.getFunctionValue(23),
×
1197
                  decoder.getFunctionValue(24),
×
1198
                  decoder.getFunctionValue(25),
×
1199
                  decoder.getFunctionValue(26),
×
1200
                  decoder.getFunctionValue(27),
×
1201
                  decoder.getFunctionValue(28)));
×
1202
            }
1203
          }
1204
          break;
×
1205
        }
1206
        case LocoNetF9F28::UhlenbrockExtended:
×
1207
          if(functionNumber <= 12)
×
1208
          {
1209
            LocoF9F12 message{
1210
              decoder.getFunctionValue(9),
×
1211
              decoder.getFunctionValue(10),
×
1212
              decoder.getFunctionValue(11),
×
1213
              decoder.getFunctionValue(12)};
×
1214
            postSend(decoder.address, message);
×
1215
          }
1216
          else if(functionNumber <= 19)
×
1217
          {
1218
            LocoF13F19 message{
1219
              decoder.getFunctionValue(13),
×
1220
              decoder.getFunctionValue(14),
×
1221
              decoder.getFunctionValue(15),
×
1222
              decoder.getFunctionValue(16),
×
1223
              decoder.getFunctionValue(17),
×
1224
              decoder.getFunctionValue(18),
×
1225
              decoder.getFunctionValue(19)};
×
1226
            postSend(decoder.address, message);
×
1227
          }
1228
          else if(functionNumber == 20 || functionNumber == 28)
×
1229
          {
1230
            LocoF12F20F28 message{
1231
              decoder.getFunctionValue(12),
×
1232
              decoder.getFunctionValue(20),
×
1233
              decoder.getFunctionValue(28)};
×
1234
            postSend(decoder.address, message);
×
1235
          }
×
1236
          else if(functionNumber <= 27)
×
1237
          {
1238
            LocoF21F27 message{
1239
              decoder.getFunctionValue(21),
×
1240
              decoder.getFunctionValue(22),
×
1241
              decoder.getFunctionValue(23),
×
1242
              decoder.getFunctionValue(24),
×
1243
              decoder.getFunctionValue(25),
×
1244
              decoder.getFunctionValue(26),
×
1245
              decoder.getFunctionValue(27)};
×
1246
            postSend(decoder.address, message);
×
1247
          }
1248
          break;
×
1249

1250
        default:
×
1251
          assert(false);
×
1252
          break;
1253
      }
1254
    }
1255
  }
1256
}
×
1257

1258
bool Kernel::setOutput(OutputChannel channel, uint16_t address, OutputValue value)
×
1259
{
1260
  assert(isEventLoopThread());
×
1261

1262
  switch(channel)
×
1263
  {
1264
    case OutputChannel::Accessory:
×
1265
      if(!inRange(address, accessoryOutputAddressMin, accessoryOutputAddressMax))
×
1266
        return false;
×
1267

1268
      m_ioContext.post(
×
1269
        [this, address, dir=std::get<OutputPairValue>(value) == OutputPairValue::Second]()
×
1270
        {
1271
          send(SwitchRequest(address, dir, true));
×
1272
        });
×
1273
      return true;
×
1274

1275
    case OutputChannel::DCCext:
×
1276
      return
1277
        inRange(address, DCC::Accessory::addressMin, DCC::Accessory::addressMax) &&
×
1278
        inRange<int16_t>(std::get<int16_t>(value), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()) &&
×
1279
        immPacket(DCC::SetAdvancedAccessoryValue(address, static_cast<uint8_t>(std::get<int16_t>(value))), 2);
×
1280

1281
    default: /*[[unlikely]]*/
×
1282
      assert(false);
×
1283
      break;
1284
  }
1285
  return false;
1286
}
1287

1288
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
×
1289
{
1290
  assert(isEventLoopThread());
×
1291
  assert(inRange(address, inputAddressMin, inputAddressMax));
×
1292
  if(m_simulation)
×
1293
    m_ioContext.post(
×
1294
      [this, fullAddress=address - 1, action]()
×
1295
      {
1296
        switch(action)
×
1297
        {
1298
            case SimulateInputAction::SetFalse:
×
1299
              if(m_inputValues[fullAddress] != TriState::False)
×
1300
                receive(InputRep(fullAddress, false));
×
1301
              break;
×
1302

1303
            case SimulateInputAction::SetTrue:
×
1304
              if(m_inputValues[fullAddress] != TriState::True)
×
1305
                receive(InputRep(fullAddress, true));
×
1306
              break;
×
1307

1308
            case SimulateInputAction::Toggle:
×
1309
              receive(InputRep(fullAddress, m_inputValues[fullAddress] != TriState::True));
×
1310
              break;
×
1311
        }
1312
      });
×
1313
}
×
1314

1315
void Kernel::lncvStart(uint16_t moduleId, uint16_t moduleAddress)
×
1316
{
1317
  assert(isEventLoopThread());
×
1318
  m_ioContext.post(
×
1319
    [this, moduleId, moduleAddress]()
×
1320
    {
1321
      if(m_lncvActive)
×
1322
        return;
×
1323

1324
      m_lncvActive = true;
×
1325
      m_lncvModuleId = moduleId;
×
1326
      m_lncvModuleAddress = moduleAddress;
×
1327
      send(Uhlenbrock::LNCVStart(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1328
    });
1329
}
×
1330

1331
void Kernel::lncvRead(uint16_t lncv)
×
1332
{
1333
  assert(isEventLoopThread());
×
1334
  m_ioContext.post(
×
1335
    [this, lncv]()
×
1336
    {
1337
      if(m_lncvActive)
×
1338
        send(Uhlenbrock::LNCVRead(m_lncvModuleId, m_lncvModuleAddress, lncv), HighPriority);
×
1339
    });
×
1340
}
×
1341

1342
void Kernel::lncvWrite(uint16_t lncv, uint16_t value)
×
1343
{
1344
  assert(isEventLoopThread());
×
1345
  m_ioContext.post(
×
1346
    [this, lncv, value]()
×
1347
    {
1348
      if(m_lncvActive)
×
1349
        send(Uhlenbrock::LNCVWrite(m_lncvModuleId, lncv, value), HighPriority);
×
1350
    });
×
1351
}
×
1352

1353
void Kernel::lncvStop()
×
1354
{
1355
  assert(isEventLoopThread());
×
1356
  m_ioContext.post(
×
1357
    [this]()
×
1358
    {
1359
      if(!m_lncvActive)
×
1360
        return;
×
1361

1362
      send(Uhlenbrock::LNCVStop(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1363
      m_lncvActive = false;
×
1364
    });
1365
}
×
1366

1367
void Kernel::setOnLNCVReadResponse(OnLNCVReadResponse callback)
×
1368
{
1369
  assert(isEventLoopThread());
×
1370
  assert(!m_started);
×
1371
  m_onLNCVReadResponse = std::move(callback);
×
1372
}
×
1373

1374
Kernel::LocoSlot* Kernel::getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew)
×
1375
{
1376
  assert(isKernelThread());
×
1377

1378
  if(!isLocoSlot(slot))
×
1379
    return nullptr;
×
1380

1381
  auto it = m_slots.find(slot);
×
1382
  if(it == m_slots.end())
×
1383
  {
1384
    if(sendSlotDataRequestIfNew)
×
1385
      send(RequestSlotData(slot));
×
1386
    it = m_slots.emplace(slot, LocoSlot()).first;
×
1387
  }
1388

1389
  return &it->second;
×
1390
}
1391

1392
Kernel::LocoSlot* Kernel::getLocoSlotByAddress(uint16_t address)
×
1393
{
1394
  assert(isKernelThread());
×
1395

1396
  auto it = std::find_if(m_slots.begin(), m_slots.end(),
×
1397
    [address](auto& item)
×
1398
    {
1399
      assert(address != LocoSlot::invalidAddress);
×
1400
      return item.second.address == address;
×
1401
    });
1402

1403
  return it != m_slots.end() ? &it->second : nullptr;
×
1404
}
1405

1406
void Kernel::clearLocoSlot(uint8_t slot)
×
1407
{
1408
  assert(isKernelThread());
×
1409

1410
  if(auto it = m_slots.find(slot); it != m_slots.end())
×
1411
    m_slots.erase(it);
×
1412

1413
  if(auto it = std::find_if(m_addressToSlot.begin(), m_addressToSlot.end(), [slot](const auto& item){ return item.second == slot; }); it != m_addressToSlot.end())
×
1414
    m_addressToSlot.erase(it);
×
1415
}
×
1416

1417
std::shared_ptr<Decoder> Kernel::getDecoder(uint16_t address)
×
1418
{
1419
  assert(isEventLoopThread());
×
1420
  return m_decoderController->getDecoder(DCC::getProtocol(address), address);
×
1421
}
1422

1423
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
1424
{
1425
  assert(isEventLoopThread());
×
1426
  assert(handler);
×
1427
  assert(!m_ioHandler);
×
1428
  m_ioHandler = std::move(handler);
×
1429
}
×
1430

1431
void Kernel::send(const Message& message, Priority priority)
×
1432
{
1433
  assert(isKernelThread());
×
1434

1435
  if(m_config.listenOnly)
×
1436
    return; // drop it
×
1437

1438
  if(!m_sendQueue[priority].append(message))
×
1439
  {
1440
    // TODO: log message
1441
    return;
×
1442
  }
1443

1444
  if(!m_waitingForEcho && !m_waitingForResponse)
×
1445
    sendNextMessage();
×
1446
}
1447

1448
void Kernel::send(uint16_t address, Message& message, uint8_t& slot)
×
1449
{
1450
  assert(isKernelThread());
×
1451

1452
  if(auto addressToSlot = m_addressToSlot.find(address); addressToSlot != m_addressToSlot.end())
×
1453
  {
1454
    slot = addressToSlot->second;
×
1455

1456
    if(message.opCode == OPC_LOCO_SPD &&
×
1457
        static_cast<LocoSpd&>(message).speed >= SPEED_MIN &&
×
1458
        static_cast<LocoSpd&>(message).speed == m_slots[slot].speed)
×
1459
      return; // same speed, don't send it (always send ESTOP and STOP)
×
1460

1461
    updateChecksum(message);
×
1462
    send(message);
×
1463
  }
1464
  else // try get a slot
1465
  {
1466
    auto* ptr = reinterpret_cast<std::byte*>(&message);
×
1467

1468
    auto pendingSlotMessage = m_pendingSlotMessages.find(address);
×
1469
    if(pendingSlotMessage == m_pendingSlotMessages.end())
×
1470
    {
1471
      m_pendingSlotMessages[address].assign(ptr, ptr + message.size());
×
1472
      send(LocoAdr{address}, HighPriority);
×
1473
    }
1474
    else
1475
      pendingSlotMessage->second.insert(pendingSlotMessage->second.end(), ptr, ptr + message.size());
×
1476
  }
1477
}
1478

1479
void Kernel::sendNextMessage()
×
1480
{
1481
  assert(isKernelThread());
×
1482

1483
  for(Priority priority = HighPriority; priority <= LowPriority; ++priority)
×
1484
  {
1485
    if(!m_sendQueue[priority].empty())
×
1486
    {
1487
      const Message& message = m_sendQueue[priority].front();
×
1488

1489
      if(m_config.debugLogRXTX)
×
1490
        EventLoop::call([this, msg=toString(message)](){ Log::log(logId, LogMessage::D2001_TX_X, msg); });
×
1491

1492
      if(m_ioHandler->send(message))
×
1493
      {
1494
        m_sentMessagePriority = priority;
×
1495

1496
        m_waitingForEcho = true;
×
1497
        m_waitingForEchoTimer.expires_after(boost::asio::chrono::milliseconds(m_config.echoTimeout));
×
1498
        m_waitingForEchoTimer.async_wait(std::bind(&Kernel::waitingForEchoTimerExpired, this, std::placeholders::_1));
×
1499

1500
        m_waitingForResponse = hasResponse(message);
×
1501
        m_waitingForLNCVReadResponse = m_waitingForResponse && Uhlenbrock::LNCVRead::check(message);
×
1502
        if(m_waitingForResponse)
×
1503
        {
1504
          if(m_waitingForLNCVReadResponse)
×
1505
          {
1506
            const auto& lncvRead = static_cast<const Uhlenbrock::LNCVRead&>(message);
×
1507
            m_pendingLNCVRead.moduleId = lncvRead.moduleId();
×
1508
            m_pendingLNCVRead.address = lncvRead.address();
×
1509
            m_pendingLNCVRead.lncv = lncvRead.lncv();
×
1510
          }
1511
          const auto timeout = m_waitingForLNCVReadResponse ? Config::lncvReadResponseTimeout : m_config.responseTimeout;
×
1512
          m_waitingForResponseTimer.expires_after(boost::asio::chrono::milliseconds(timeout));
×
1513
          m_waitingForResponseTimer.async_wait(std::bind(&Kernel::waitingForResponseTimerExpired, this, std::placeholders::_1));
×
1514
        }
1515
      }
1516
      else
1517
      {} // log message and go to error state
1518
      return;
×
1519
    }
1520
  }
1521
}
1522

1523
void Kernel::waitingForEchoTimerExpired(const boost::system::error_code& ec)
×
1524
{
1525
  assert(isKernelThread());
×
1526

1527
  if(ec)
×
1528
    return;
×
1529

1530
  EventLoop::call(
×
1531
    [this]()
×
1532
    {
1533
      Log::log(logId, LogMessage::W2018_TIMEOUT_NO_ECHO_WITHIN_X_MS, m_config.echoTimeout);
×
1534
    });
×
1535
}
1536

1537
void Kernel::waitingForResponseTimerExpired(const boost::system::error_code& ec)
×
1538
{
1539
  assert(isKernelThread());
×
1540

1541
  if(ec)
×
1542
    return;
×
1543

1544
  if(m_waitingForLNCVReadResponse)
×
1545
  {
1546
    if(!m_lncvReads.empty() &&
×
1547
        m_lncvReads.front().moduleId == m_pendingLNCVRead.moduleId &&
×
1548
        m_lncvReads.front().address == m_pendingLNCVRead.address &&
×
1549
        m_lncvReads.front().lncv == m_pendingLNCVRead.lncv)
×
1550
    {
1551
      m_pendingLNCVRead.reset();
×
1552
      EventLoop::call(
×
1553
        [callback=std::move(m_lncvReads.front().callback)]()
×
1554
        {
1555
          callback(0, LNCV::Error::noResponse());
×
1556
        });
×
1557
      m_lncvReads.pop();
×
1558
    }
1559

1560
    assert(Uhlenbrock::LNCVRead::check(lastSentMessage()));
×
1561
    m_sendQueue[m_sentMessagePriority].pop();
×
1562
    m_waitingForResponse = false;
×
1563
    sendNextMessage();
×
1564
  }
1565
  else if(m_lncvActive && Uhlenbrock::LNCVStart::check(lastSentMessage()))
×
1566
  {
1567
    EventLoop::call(
×
1568
      [this, lncvStart=static_cast<const Uhlenbrock::LNCVStart&>(lastSentMessage())]()
×
1569
      {
1570
        Log::log(logId, LogMessage::N2002_NO_RESPONSE_FROM_LNCV_MODULE_X_WITH_ADDRESS_X, lncvStart.moduleId(), lncvStart.address());
×
1571

1572
        if(m_onLNCVReadResponse && m_lncvModuleId == lncvStart.moduleId())
×
1573
          m_onLNCVReadResponse(false, lncvStart.address(), 0);
×
1574
      });
×
1575

1576
    assert(Uhlenbrock::LNCVStart::check(lastSentMessage()));
×
1577
    m_sendQueue[m_sentMessagePriority].pop();
×
1578
    m_waitingForResponse = false;
×
1579
    sendNextMessage();
×
1580
  }
1581
  else
1582
  {
1583
    EventLoop::call(
×
1584
      [this]()
×
1585
      {
1586
        Log::log(logId, LogMessage::E2019_TIMEOUT_NO_RESPONSE_WITHIN_X_MS, m_config.responseTimeout);
×
1587
        error();
×
1588
      });
×
1589
  }
1590
}
1591

1592
void Kernel::setFastClockMaster(bool enable)
×
1593
{
1594
  assert(isKernelThread());
×
1595
  if(enable)
×
1596
  {
1597
    const auto clock = m_fastClock.load();
×
1598
    FastClockSlotWriteData fastClock;
×
1599
    fastClock.clk_rate = clock.multiplier;
×
1600
    fastClock.setHour(clock.hour);
×
1601
    fastClock.setMinute(clock.minute);
×
1602
    fastClock.setValid(true);
×
1603
    fastClock.setId(idTraintastic);
×
1604
    updateChecksum(fastClock);
×
1605
    send(fastClock, HighPriority);
×
1606
  }
1607
  else
1608
    send(FastClockSlotWriteData(), HighPriority);
×
1609
}
×
1610

1611
void Kernel::disableClockEvents()
×
1612
{
1613
  m_clockChangeConnection.disconnect();
×
1614
}
×
1615

1616
void Kernel::enableClockEvents()
×
1617
{
1618
  if(m_clockChangeConnection.connected() || !m_clock)
×
1619
    return;
×
1620

1621
  m_clockChangeConnection = m_clock->onChange.connect(
×
1622
    [this](Clock::ClockEvent event, uint8_t multiplier, Time time)
×
1623
    {
1624
      m_fastClock.store(FastClock{event == Clock::ClockEvent::Freeze ? multiplierFreeze : multiplier, time.hour(), time.minute()});
×
1625
      if(event == Clock::ClockEvent::Freeze || event == Clock::ClockEvent::Resume)
×
1626
      {
1627
        m_ioContext.post(
×
1628
          [this]()
×
1629
          {
1630
            setFastClockMaster(true);
×
1631
          });
×
1632
      }
1633
    });
×
1634
}
1635

1636
void Kernel::startFastClockSyncTimer()
×
1637
{
1638
  assert(isKernelThread());
×
1639
  assert(m_config.fastClockSyncInterval > 0);
×
1640
  m_fastClockSyncTimer.expires_after(boost::asio::chrono::seconds(m_config.fastClockSyncInterval));
×
1641
  m_fastClockSyncTimer.async_wait(std::bind(&Kernel::fastClockSyncTimerExpired, this, std::placeholders::_1));
×
1642
}
×
1643

1644
void Kernel::stopFastClockSyncTimer()
×
1645
{
1646
  assert(isKernelThread());
×
1647
  m_fastClockSyncTimer.cancel();
×
1648
}
×
1649

1650
void Kernel::fastClockSyncTimerExpired(const boost::system::error_code& ec)
×
1651
{
1652
  assert(isKernelThread());
×
1653

1654
  if(ec || !m_config.fastClockSyncEnabled || !m_fastClockSupported)
×
1655
    return;
×
1656

1657
  send(RequestSlotData(SLOT_FAST_CLOCK));
×
1658

1659
  startFastClockSyncTimer();
×
1660
}
1661

1662
template<uint8_t First, uint8_t Last, class T>
1663
bool Kernel::updateFunctions(LocoSlot& slot, const T& message)
×
1664
{
1665
  assert(isKernelThread());
×
1666

1667
  bool changed = false;
×
1668
  for(uint8_t i = First; i <= Last; ++i)
×
1669
  {
1670
    if(slot.functions[i] != message.f(i))
×
1671
    {
1672
      slot.functions[i] = toTriState(message.f(i));
×
1673
      changed = true;
×
1674
    }
1675
  }
1676

1677
  EventLoop::call(
×
1678
    [this, address=slot.address, message]()
×
1679
    {
1680
      if(auto decoder = getDecoder(address))
×
1681
        for(uint8_t i = First; i <= Last; ++i)
×
1682
          decoder->setFunctionValue(i, message.f(i));
×
1683
    });
1684

1685
  return changed;
×
1686
}
1687

1688
void Kernel::startPCAP(PCAPOutput pcapOutput)
×
1689
{
1690
  assert(isKernelThread());
×
1691
  assert(!m_pcap);
×
1692

1693
  const uint32_t DLT_USER0 = 147; //! \todo change to LocoNet DLT once it is registered
×
1694

1695
  try
1696
  {
1697
    switch(pcapOutput)
×
1698
    {
1699
      case PCAPOutput::File:
×
1700
      {
1701
        const auto filename = m_debugDir / logId += dateTimeStr() += ".pcap";
×
1702
        EventLoop::call(
×
1703
          [this, filename]()
×
1704
          {
1705
            Log::log(logId, LogMessage::N2004_STARTING_PCAP_FILE_LOG_X, filename);
×
1706
          });
×
1707
        m_pcap = std::make_unique<PCAPFile>(filename, DLT_USER0);
×
1708
        break;
×
1709
      }
×
1710
      case PCAPOutput::Pipe:
×
1711
      {
1712
        std::filesystem::path pipe;
×
1713
#ifdef WIN32
1714
        return; //! \todo Implement
1715
#else // unix
1716
        pipe = std::filesystem::temp_directory_path() / "traintastic-server" / logId;
×
1717
#endif
1718
        EventLoop::call(
×
1719
          [this, pipe]()
×
1720
          {
1721
            Log::log(logId, LogMessage::N2005_STARTING_PCAP_LOG_PIPE_X, pipe);
×
1722
          });
×
1723
        m_pcap = std::make_unique<PCAPPipe>(std::move(pipe), DLT_USER0);
×
1724
        break;
×
1725
      }
×
1726
    }
1727
  }
1728
  catch(const std::exception& e)
×
1729
  {
1730
    EventLoop::call(
×
1731
      [this, what=std::string(e.what())]()
×
1732
      {
1733
        Log::log(logId, LogMessage::E2021_STARTING_PCAP_LOG_FAILED_X, what);
×
1734
      });
×
1735
  }
×
1736
}
×
1737

1738

1739
bool Kernel::SendQueue::append(const Message& message)
×
1740
{
1741
  const uint8_t messageSize = message.size();
×
1742
  if(m_bytes + messageSize > threshold())
×
1743
    return false;
×
1744

1745
  memcpy(m_front + m_bytes, &message, messageSize);
×
1746
  m_bytes += messageSize;
×
1747

1748
  return true;
×
1749
}
1750

1751
void Kernel::SendQueue::pop()
×
1752
{
1753
  const uint8_t messageSize = front().size();
×
1754
  m_front += messageSize;
×
1755
  m_bytes -= messageSize;
×
1756

1757
  if(static_cast<std::size_t>(m_front - m_buffer.data()) >= threshold())
×
1758
  {
1759
    memmove(m_buffer.data(), m_front, m_bytes);
×
1760
    m_front = m_buffer.data();
×
1761
  }
1762
}
×
1763

1764
void Kernel::SendQueue::clear()
×
1765
{
1766
  m_bytes = 0;
×
1767
}
×
1768

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