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

traintastic / traintastic / 20435179075

22 Dec 2025 02:24PM UTC coverage: 27.527% (-0.02%) from 27.55%
20435179075

push

github

reinder
[loconet] improved handling of power commands

0 of 22 new or added lines in 2 files covered. (0.0%)

147 existing lines in 4 files now uncovered.

7843 of 28492 relevant lines covered (27.53%)

192.37 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

55
  if(speed == SPEED_STOP || speed == SPEED_ESTOP)
×
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);
×
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
{
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}
×
83
  , m_debugDir{Traintastic::instance->debugDir()}
×
84
  , m_config{config}
×
85
{
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:
×
98
      disableClockEvents();
×
99
      break;
×
100

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

106
  m_ioContext.post(
×
107
    [this, newConfig=config]()
×
108
    {
109
      if(newConfig.pcap != m_config.pcap)
×
110
      {
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
      {
118
        m_pcap.reset();
×
119
        startPCAP(newConfig.pcapOutput);
×
120
      }
121

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

127
        EventLoop::call(
×
128
          [this]()
×
129
          {
130
            Log::log(logId, LogMessage::N2006_LISTEN_ONLY_MODE_ACTIVATED);
×
131
          });
×
132
      }
×
133
      else if(!newConfig.listenOnly && m_config.listenOnly)
×
134
      {
135
        EventLoop::call(
×
136
          [this]()
×
137
          {
138
            Log::log(logId, LogMessage::N2007_LISTEN_ONLY_MODE_DEACTIVATED);
×
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
      {
148
        setFastClockMaster(true);
×
149
      }
150

151
      if(!m_config.fastClockSyncEnabled && newConfig.fastClockSyncEnabled)
×
152
      {
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;
×
161
    });
×
162
}
×
163

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

268
  disableClockEvents();
×
269

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

280
  m_ioContext.stop();
×
281

282
  m_thread.join();
×
283

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

653
    case OPC_CONSIST_FUNC:
×
654
      break; // unimplemented
×
655

656
    case OPC_UNLINK_SLOTS:
×
657
      break; // unimplemented
×
658

659
    case OPC_LINK_SLOTS:
×
660
      break; // unimplemented
×
661

662
    case OPC_MOVE_SLOTS:
×
663
      break; // unimplemented
×
664

665
    case OPC_RQ_SL_DATA:
×
666
      break; // unimplemented
×
667

668
    case OPC_SW_STATE:
×
669
      break; // unimplemented
×
670

671
    case OPC_SW_ACK:
×
672
      break; // unimplemented
×
673

674
    case OPC_LOCO_ADR:
×
675
      break; // unimplemented
×
676

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

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

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

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

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

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

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

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

885
    case OPC_WR_SL_DATA:
×
886
      break; // unimplemented
×
887
  }
888

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

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

934
void Kernel::resume()
×
935
{
936
  assert(isEventLoopThread());
×
937

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

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

982
bool Kernel::send(std::span<uint8_t> packet)
×
983
{
984
  assert(isEventLoopThread());
×
985

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

989
  std::vector<uint8_t> data(packet.data(), packet.data() + packet.size());
×
990
  data.push_back(calcChecksum(*reinterpret_cast<Message*>(data.data())));
×
991

992
  if(!isValid(*reinterpret_cast<Message*>(data.data())))
×
993
    return false;
×
994

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

1001
  return true;
×
1002
}
×
1003

1004
bool Kernel::immPacket(std::span<const uint8_t> dccPacket, uint8_t repeat)
×
1005
{
1006
  assert(isEventLoopThread());
×
1007

1008
  if(dccPacket.size() > ImmPacket::dccPacketSizeMax || repeat > ImmPacket::repeatMax)
×
1009
    return false;
×
1010

1011
  postSend(ImmPacket(dccPacket, repeat));
×
1012
  return true;
×
1013
}
1014

1015
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
1016
{
1017
  assert(isEventLoopThread());
×
1018

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

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

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

1191
        default:
×
1192
          assert(false);
×
1193
          break;
1194
      }
1195
    }
1196
  }
1197
}
×
1198

1199
bool Kernel::setOutput(OutputChannel channel, uint16_t address, OutputValue value)
×
1200
{
1201
  assert(isEventLoopThread());
×
1202

1203
  switch(channel)
×
1204
  {
1205
    case OutputChannel::Accessory:
×
1206
      if(!inRange(address, accessoryOutputAddressMin, accessoryOutputAddressMax))
×
1207
        return false;
×
1208

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

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

1222
    default: /*[[unlikely]]*/
×
1223
      assert(false);
×
1224
      break;
1225
  }
1226
  return false;
1227
}
1228

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

1244
            case SimulateInputAction::SetTrue:
×
1245
              if(m_inputValues[fullAddress] != TriState::True)
×
1246
                receive(InputRep(fullAddress, true));
×
1247
              break;
×
1248

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

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

1265
      m_lncvActive = true;
×
1266
      m_lncvModuleId = moduleId;
×
1267
      m_lncvModuleAddress = moduleAddress;
×
1268
      send(Uhlenbrock::LNCVStart(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1269
    });
1270
}
×
1271

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

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

1294
void Kernel::lncvStop()
×
1295
{
1296
  assert(isEventLoopThread());
×
1297
  m_ioContext.post(
×
1298
    [this]()
×
1299
    {
1300
      if(!m_lncvActive)
×
1301
        return;
×
1302

1303
      send(Uhlenbrock::LNCVStop(m_lncvModuleId, m_lncvModuleAddress), HighPriority);
×
1304
      m_lncvActive = false;
×
1305
    });
1306
}
×
1307

1308
void Kernel::setOnLNCVReadResponse(OnLNCVReadResponse callback)
×
1309
{
1310
  assert(isEventLoopThread());
×
1311
  assert(!m_started);
×
1312
  m_onLNCVReadResponse = std::move(callback);
×
1313
}
×
1314

1315
Kernel::LocoSlot* Kernel::getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew)
×
1316
{
1317
  assert(isKernelThread());
×
1318

1319
  if(!isLocoSlot(slot))
×
1320
    return nullptr;
×
1321

1322
  auto it = m_slots.find(slot);
×
1323
  if(it == m_slots.end())
×
1324
  {
1325
    if(sendSlotDataRequestIfNew)
×
1326
      send(RequestSlotData(slot));
×
1327
    it = m_slots.emplace(slot, LocoSlot()).first;
×
1328
  }
1329

1330
  return &it->second;
×
1331
}
1332

1333
Kernel::LocoSlot* Kernel::getLocoSlotByAddress(uint16_t address)
×
1334
{
1335
  assert(isKernelThread());
×
1336

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

1344
  return it != m_slots.end() ? &it->second : nullptr;
×
1345
}
1346

1347
void Kernel::clearLocoSlot(uint8_t slot)
×
1348
{
1349
  assert(isKernelThread());
×
1350

1351
  if(auto it = m_slots.find(slot); it != m_slots.end())
×
1352
    m_slots.erase(it);
×
1353

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

1358
std::shared_ptr<Decoder> Kernel::getDecoder(uint16_t address)
×
1359
{
1360
  assert(isEventLoopThread());
×
1361
  return m_decoderController->getDecoder(DCC::getProtocol(address), address);
×
1362
}
1363

1364
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
1365
{
1366
  assert(isEventLoopThread());
×
1367
  assert(handler);
×
1368
  assert(!m_ioHandler);
×
1369
  m_ioHandler = std::move(handler);
×
1370
}
×
1371

1372
void Kernel::send(const Message& message, Priority priority)
×
1373
{
1374
  assert(isKernelThread());
×
1375

1376
  if(m_config.listenOnly)
×
1377
    return; // drop it
×
1378

1379
  if(!m_sendQueue[priority].append(message))
×
1380
  {
1381
    // TODO: log message
1382
    return;
×
1383
  }
1384

1385
  if(!m_waitingForEcho && !m_waitingForResponse)
×
1386
    sendNextMessage();
×
1387
}
1388

1389
void Kernel::send(uint16_t address, Message& message, uint8_t& slot)
×
1390
{
1391
  assert(isKernelThread());
×
1392

1393
  if(auto addressToSlot = m_addressToSlot.find(address); addressToSlot != m_addressToSlot.end())
×
1394
  {
1395
    slot = addressToSlot->second;
×
1396

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

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

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

1420
void Kernel::sendNextMessage()
×
1421
{
1422
  assert(isKernelThread());
×
1423

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

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

1433
      if(m_ioHandler->send(message))
×
1434
      {
1435
        m_sentMessagePriority = priority;
×
1436

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

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

1455
void Kernel::waitingForEchoTimerExpired(const boost::system::error_code& ec)
×
1456
{
1457
  assert(isKernelThread());
×
1458

1459
  if(ec)
×
1460
    return;
×
1461

1462
  EventLoop::call(
×
1463
    [this]()
×
1464
    {
1465
      Log::log(logId, LogMessage::W2018_TIMEOUT_NO_ECHO_WITHIN_X_MS, m_config.echoTimeout);
×
1466
    });
×
1467
}
1468

1469
void Kernel::waitingForResponseTimerExpired(const boost::system::error_code& ec)
×
1470
{
1471
  assert(isKernelThread());
×
1472

1473
  if(ec)
×
1474
    return;
×
1475

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

1483
        if(m_onLNCVReadResponse && m_lncvModuleId == lncvStart.moduleId())
×
1484
          m_onLNCVReadResponse(false, lncvStart.address(), 0);
×
1485
      });
×
1486

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

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

1519
void Kernel::disableClockEvents()
×
1520
{
1521
  m_clockChangeConnection.disconnect();
×
1522
}
×
1523

1524
void Kernel::enableClockEvents()
×
1525
{
1526
  if(m_clockChangeConnection.connected() || !m_clock)
×
1527
    return;
×
1528

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

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

1552
void Kernel::stopFastClockSyncTimer()
×
1553
{
1554
  assert(isKernelThread());
×
1555
  m_fastClockSyncTimer.cancel();
×
1556
}
×
1557

1558
void Kernel::fastClockSyncTimerExpired(const boost::system::error_code& ec)
×
1559
{
1560
  assert(isKernelThread());
×
1561

1562
  if(ec || !m_config.fastClockSyncEnabled || !m_fastClockSupported)
×
1563
    return;
×
1564

1565
  send(RequestSlotData(SLOT_FAST_CLOCK));
×
1566

1567
  startFastClockSyncTimer();
×
1568
}
1569

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

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

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

1593
  return changed;
×
1594
}
1595

1596
void Kernel::startPCAP(PCAPOutput pcapOutput)
×
1597
{
1598
  assert(isKernelThread());
×
1599
  assert(!m_pcap);
×
1600

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

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

1646

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

1653
  memcpy(m_front + m_bytes, &message, messageSize);
×
1654
  m_bytes += messageSize;
×
1655

1656
  return true;
×
1657
}
1658

1659
void Kernel::SendQueue::pop()
×
1660
{
1661
  const uint8_t messageSize = front().size();
×
1662
  m_front += messageSize;
×
1663
  m_bytes -= messageSize;
×
1664

1665
  if(static_cast<std::size_t>(m_front - m_buffer.data()) >= threshold())
×
1666
  {
1667
    memmove(m_buffer.data(), m_front, m_bytes);
×
1668
    m_front = m_buffer.data();
×
1669
  }
1670
}
×
1671

1672
void Kernel::SendQueue::clear()
×
1673
{
1674
  m_bytes = 0;
×
1675
}
×
1676

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