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

traintastic / traintastic / 23669027368

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

push

github

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

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

1 existing line in 1 file now uncovered.

8256 of 31514 relevant lines covered (26.2%)

182.55 hits per line

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

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

22
#include "kernel.hpp"
23
#include "iohandler/iohandler.hpp"
24
#include "lncv/lncvutils.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

NEW
106
  boost::asio::post(m_ioContext, 
×
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

164
void Kernel::setOnStateChanged(std::function<void(bool, bool)> callback)
×
165
{
166
  assert(isEventLoopThread());
×
167
  assert(!m_started);
×
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");
×
NEW
230
      boost::asio::executor_work_guard<decltype(m_ioContext.get_executor())> work{m_ioContext.get_executor()};
×
231
      m_ioContext.run();
×
232
    });
×
233

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

NEW
237
  boost::asio::post(m_ioContext, 
×
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

NEW
270
  boost::asio::post(m_ioContext, 
×
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

316
  const bool isEcho = (message == lastSentMessage());
×
317
  bool isResponse = false;
×
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:
×
336
      if(!isEcho) // sent by another LocoNet device, not by Traintastic
×
337
      {
338
        m_globalPower = TriState::True;
×
339
        m_emergencyStop = TriState::False;
×
340
        if(m_onStateChanged) [[likely]]
×
341
        {
342
          EventLoop::call(
×
343
            [this]()
×
344
            {
345
              m_onStateChanged(true, true);
×
346
            });
×
347
        }
348
        EventLoop::call(std::bind_front(&Kernel::resume, this));
×
349
      }
350
      break;
×
351

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

367
    case OPC_IDLE:
×
368
      if(!isEcho) // sent by another LocoNet device, not by Traintastic
×
369
      {
370
        m_emergencyStop = TriState::True;
×
371
        if(m_onStateChanged) [[likely]]
×
372
        {
373
          EventLoop::call(
×
374
            [this]()
×
375
            {
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, InputAddress(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, OutputAddress(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(longAck.respondingOpCode() == OPC_IMM_PACKET)
×
633
      {
634
        if(m_waitingForLNCVReadResponse)
×
635
        {
636
          if(!m_lncvReads.empty() &&
×
637
              m_lncvReads.front().moduleId == m_pendingLNCVRead.moduleId &&
×
638
              m_lncvReads.front().address == m_pendingLNCVRead.address &&
×
639
              m_lncvReads.front().lncv == m_pendingLNCVRead.lncv)
×
640
          {
641
            m_pendingLNCVRead.reset();
×
642
            auto ec = LNCV::parseLongAck(longAck.ack1);
×
643
            if(!ec)
×
644
            {
645
              // 0x7F indicates a successful LONG_ACK, but for a read operation
646
              // we expect a proper read response message instead. Receiving
647
              // only a LONG_ACK is unexpected, so we treat it as
648
              // unexpected response until proven otherwise.
649
              ec = LNCV::Error::unexpectedResponse();
×
650
            }
651
            EventLoop::call(
×
652
              [callback=std::move(m_lncvReads.front().callback), ec]()
×
653
              {
654
                callback(0, ec);
×
655
              });
×
656
            m_lncvReads.pop();
×
657
          }
658
        }
659
        else if(m_lncvActive && m_onLNCVReadResponse &&
×
660
            longAck.ack1 == 0x7F &&
×
661
            Uhlenbrock::LNCVWrite::check(lastSentMessage()))
×
662
        {
663
          const auto& lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(lastSentMessage());
×
664
          if(lncvWrite.lncv() == 0)
×
665
          {
666
            m_lncvModuleAddress = lncvWrite.value();
×
667
          }
668

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1047
  return true;
×
1048
}
×
1049

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1526
  if(ec)
×
1527
    return;
×
1528

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

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

1540
  if(ec)
×
1541
    return;
×
1542

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

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

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

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

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

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

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

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

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

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

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

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

1656
  send(RequestSlotData(SLOT_FAST_CLOCK));
×
1657

1658
  startFastClockSyncTimer();
×
1659
}
1660

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

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

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

1684
  return changed;
×
1685
}
1686

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

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

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

1737

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

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

1747
  return true;
×
1748
}
1749

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

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

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

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