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

traintastic / traintastic / 26121453997

19 May 2026 07:52PM UTC coverage: 25.063% (-0.6%) from 25.624%
26121453997

Pull #221

github

web-flow
Merge 598936246 into 15e38bcf7
Pull Request #221: Xpressnet new messages

49 of 1129 new or added lines in 16 files covered. (4.34%)

782 existing lines in 21 files now uncovered.

8483 of 33847 relevant lines covered (25.06%)

172.88 hits per line

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

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

23
#include "kernel.hpp"
24
#include "messages.hpp"
25
#include "../../decoder/decoder.hpp"
26
#include "../../decoder/decoderchangeflags.hpp"
27
#include "../../protocol/dcc/dcc.hpp"
28
#include "../../output/outputcontroller.hpp"
29
#include "../../input/inputcontroller.hpp"
30
#include "../../../utils/setthreadname.hpp"
31
#include "../../../core/eventloop.hpp"
32
#include "../../../log/log.hpp"
33
#include "../../../log/logmessageexception.hpp"
34

35
namespace XpressNet {
36

37
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
×
38
  : KernelBase(std::move(logId_))
×
39
  , m_simulation{simulation}
×
40
  , m_decoderController{nullptr}
×
41
  , m_inputController{nullptr}
×
42
  , m_outputController{nullptr}
×
43
  , m_config{config}
×
NEW
44
  , m_pendingQueryTimeout{m_ioContext}
×
NEW
45
  , m_pollTimer{m_ioContext}
×
46
{
47
}
×
48

49
void Kernel::setConfig(const Config& config)
×
50
{
NEW
51
  assert(isEventLoopThread());
×
52

NEW
53
  boost::asio::post(m_ioContext,
×
UNCOV
54
    [this, newConfig=config]()
×
55
    {
56
      m_config = newConfig;
×
57
    });
×
58
}
×
59

60
void Kernel::start()
×
61
{
NEW
62
  assert(isEventLoopThread());
×
63

64
  assert(m_ioHandler);
×
65
  assert(!m_started);
×
66

67
  // reset all state values
68
  m_trackPowerOn = TriState::Undefined;
×
69
  m_emergencyStop = TriState::Undefined;
×
70
  m_inputValues.fill(TriState::Undefined);
×
71

72
  m_thread = std::thread(
×
73
    [this]()
×
74
    {
75
      setThreadName("xpressnet");
×
76
      boost::asio::executor_work_guard<decltype(m_ioContext.get_executor())> work{m_ioContext.get_executor()};
×
77
      m_ioContext.run();
×
78
    });
×
79

80
  boost::asio::post(m_ioContext, 
×
81
    [this]()
×
82
    {
83
      try
84
      {
85
        m_ioHandler->start();
×
86
      }
87
      catch(const LogMessageException& e)
×
88
      {
89
        EventLoop::call(
×
90
          [this, e]()
×
91
          {
92
            Log::log(logId, e.message(), e.args());
×
93
            error();
×
94
          });
×
95
        return;
×
96
      }
×
97
    });
98

99
#ifndef NDEBUG
100
  m_started = true;
×
101
#endif
102
}
×
103

104
void Kernel::stop()
×
105
{
NEW
106
  assert(isEventLoopThread());
×
107

NEW
108
  boost::asio::post(m_ioContext,
×
UNCOV
109
    [this]()
×
110
    {
NEW
111
      boost::system::error_code ec;
×
NEW
112
      m_pendingQueryTimeout.cancel(ec);
×
NEW
113
      m_pollTimer.cancel(ec);
×
114

UNCOV
115
      m_ioHandler->stop();
×
116

117
      m_ioContext.stop();
×
118
    });
×
119

120
  m_thread.join();
×
121

122
#ifndef NDEBUG
123
  m_started = false;
×
124
#endif
125
}
×
126

127
void Kernel::started()
×
128
{
129
  assert(isKernelThread());
×
130

131
  KernelBase::started();
×
132

NEW
133
  send(QueryCentralVersion());
×
134

NEW
135
  pollDecoders();
×
UNCOV
136
}
×
137

138
void Kernel::receive(const Message& message)
×
139
{
NEW
140
  assert(isKernelThread());
×
141

142
  if(m_config.debugLogRXTX)
×
143
  {
NEW
144
    Utils::PendingQuery optAddress;
×
NEW
145
    if(!m_pendingQueries.empty())
×
NEW
146
      optAddress = m_pendingQueries.at(0);
×
147
    EventLoop::call(
×
NEW
148
      [this, msg=toString(message, false, optAddress)]()
×
149
      {
150
        Log::log(logId, LogMessage::D2002_RX_X, msg);
×
151
      });
×
152
  }
153

154
  switch(message.identification())
×
155
  {
156
    case idFeedbackBroadcast:
×
157
    {
158
      const auto* feedback = static_cast<const FeedbackBroadcast*>(&message);
×
159

160
      for(uint8_t i = 0; i < feedback->pairCount(); i++)
×
161
      {
162
        const FeedbackBroadcast::Pair& pair = feedback->pair(i);
×
163
        switch(pair.type())
×
164
        {
165
          case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithoutFeedback:
×
166
          case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithFeedback:
167
          {
NEW
168
            if(m_outputController)
×
169
            {
NEW
170
              const uint16_t baseAddress = pair.groupAddress() * 2;
×
171

NEW
172
              for(uint16_t j = 0; j < 2; j++)
×
173
              {
NEW
174
                const uint16_t fullAddress = baseAddress + j;
×
NEW
175
                const uint8_t status = (pair.statusNibble() >> (j * 2)) & 0x03;
×
176

NEW
177
                OutputPairValue value = OutputPairValue::Undefined;
×
NEW
178
                if(status == 1)
×
NEW
179
                  value = OutputPairValue::First;
×
NEW
180
                else if(status == 2)
×
NEW
181
                  value = OutputPairValue::Second;
×
182

NEW
183
                EventLoop::call(
×
NEW
184
                  [this, address=1 + fullAddress, value]()
×
185
                  {
NEW
186
                    m_outputController->updateOutputValue(OutputChannel::Accessory, OutputAddress(address), value);
×
NEW
187
                  });
×
188
              }
189
            }
NEW
190
            break;
×
191
          }
192
          case FeedbackBroadcast::Pair::Type::FeedbackModule:
×
193
          {
UNCOV
194
            if(m_inputController)
×
195
            {
196
              const uint16_t baseAddress = pair.groupAddress() << 2;
×
197

198
              for(uint16_t j = 0; j < 4; j++)
×
199
              {
200
                const uint16_t fullAddress = baseAddress + j;
×
201
                const TriState value = toTriState((pair.statusNibble() & (1 << j)) != 0);
×
202
                if(m_inputValues[fullAddress] != value)
×
203
                {
204
                  if(m_config.debugLogInput)
×
205
                    EventLoop::call(
×
206
                      [this, address=1 + fullAddress, value]()
×
207
                      {
208
                        Log::log(logId, LogMessage::D2007_INPUT_X_IS_X, address, value == TriState::True ? std::string_view{"1"} : std::string_view{"0"});
×
209
                      });
×
210

211
                  m_inputValues[fullAddress] = value;
×
212

213
                  EventLoop::call(
×
214
                    [this, address=1 + fullAddress, value]()
×
215
                    {
216
                      m_inputController->updateInputValue(InputChannel::Input, InputAddress(address), value);
×
217
                    });
×
218
                }
219
              }
220
            }
221
            break;
×
222
          }
223
          case FeedbackBroadcast::Pair::Type::ReservedForFutureUse:
×
224
            break;
×
225
        }
226
      }
227

228
      break;
×
229
    }
230
    case 0x60:
×
231
    {
232
      if(message == TrackPowerOff())
×
233
      {
234
        EventLoop::call(
×
235
          [this]()
×
236
          {
237
            if(m_trackPowerOn != TriState::False)
×
238
            {
239
              m_trackPowerOn = TriState::False;
×
240
              m_emergencyStop = TriState::False;
×
241
              if(m_onTrackPowerChanged)
×
242
                m_onTrackPowerChanged(false, false);
×
243
            }
244
          });
×
245
      }
246
      else if(message == NormalOperationResumed())
×
247
      {
248
        EventLoop::call(
×
249
          [this]()
×
250
          {
251
            if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
×
252
            {
253
              m_trackPowerOn = TriState::True;
×
254
              m_emergencyStop = TriState::False;
×
255
              if(m_onTrackPowerChanged)
×
256
                m_onTrackPowerChanged(true, false);
×
257
            }
258
          });
×
259
      }
NEW
260
      else if(message.header == REPLY_VERSION_2_3)
×
261
      {
NEW
262
        const auto& reply = static_cast<const CentralVersionReplyV1&>(message);
×
NEW
263
        if(reply.db1 == idCentralVersion)
×
264
        {
NEW
265
          setCentralVersion(reply.versionHex, HardwareType::HWT_UNKNOWN);
×
266
        }
NEW
267
        else if(reply.db1 == idCentralStatusReply)
×
268
        {
NEW
269
          const auto& statusReply = static_cast<const CentralStatusReply&>(message);
×
NEW
270
          bool isEStop = has(statusReply.status, CentralStatusFlags::EStop);
×
NEW
271
          bool isPowerOn = !has(statusReply.status, CentralStatusFlags::PowerOff);
×
272

NEW
273
          EventLoop::call(
×
NEW
274
            [this, isEStop, isPowerOn]()
×
275
            {
NEW
276
              if(m_trackPowerOn != isPowerOn || m_emergencyStop != isEStop)
×
277
              {
NEW
278
                m_trackPowerOn = toTriState(isPowerOn);
×
NEW
279
                m_emergencyStop = toTriState(isEStop);
×
NEW
280
                if(m_onTrackPowerChanged)
×
NEW
281
                  m_onTrackPowerChanged(isPowerOn, isEStop);
×
282
              }
NEW
283
            });
×
284
        }
285
      }
NEW
286
      else if(message.header == REPLY_VERSION_3_0)
×
287
      {
NEW
288
        const auto& reply = static_cast<const CentralVersionReplyV3&>(message);
×
NEW
289
        if(reply.db1 == idCentralVersion)
×
290
        {
NEW
291
          setCentralVersion(reply.versionHex, reply.commandStationId());
×
292
        }
293
      }
UNCOV
294
      break;
×
295
    }
296
    case 0x80:
×
297
    {
298
      if(message == EmergencyStop())
×
299
      {
300
        EventLoop::call(
×
301
          [this]()
×
302
          {
303
            if(m_emergencyStop != TriState::True)
×
304
            {
305
              m_emergencyStop = TriState::True;
×
306
              m_trackPowerOn = TriState::True;
×
307

308
              if(m_onTrackPowerChanged)
×
309
                m_onTrackPowerChanged(true, true);
×
310
            }
311
          });
×
312
      }
313
      break;
×
314
    }
NEW
315
    case 0xE0:
×
316
    {
NEW
317
      switch(message.header)
×
318
      {
NEW
319
      case GET_LOCO_INFO:
×
320
      {
NEW
321
        const auto& locoInstr = static_cast<const LocomotiveInstruction&>(message);
×
NEW
322
        if(locoInstr.identification == idLocomotiveBusy)
×
323
        {
NEW
324
          auto loco = std::find_if(m_locomotives.begin(), m_locomotives.end(),
×
NEW
325
            [address=locoInstr.address()](const Locomotive &item) -> bool
×
326
            {
NEW
327
              return item.address == address;
×
328
            });
329

NEW
330
          if(loco == m_locomotives.end())
×
NEW
331
            break;
×
332

NEW
333
          loco->flags |= Locomotive::Flags::OwnedByXBus;
×
334

335
          // Immediately start querying
NEW
336
          postQuery({locoInstr.address(),
×
NEW
337
                     m_config.useRocoF13F20Command ?
×
338
                         Utils::PendingQuery::ROCOCumulativeLocoInfo :
339
                         Utils::PendingQuery::LocoInfoAndF0F12});
NEW
340
          break;
×
341
        }
NEW
342
        else if(locoInstr.identification == idReplyFuncF13F28)
×
343
        {
NEW
344
          const auto& funcInfo13 = static_cast<const FunctionInfoF13F28&>(message);
×
NEW
345
          const uint16_t replyAddress = popAddressQuerySendNext(Utils::PendingQuery::FuncInfoF13F28);
×
NEW
346
          if(!replyAddress)
×
NEW
347
            break; // We did not ask for function info, ignore it
×
348

349
          // After receiving basic loco info, query super-higher functions
NEW
350
          for(const Locomotive &loco : std::as_const(m_locomotives))
×
351
          {
NEW
352
            if(loco.address != replyAddress)
×
NEW
353
              continue;
×
354

NEW
355
            if((loco.flags & Locomotive::Flags::HasF29F68) == Locomotive::Flags::HasF29F68)
×
NEW
356
              postQuery({replyAddress, Utils::PendingQuery::FuncInfoF29F68});
×
NEW
357
            break;
×
358
          }
359

NEW
360
          EventLoop::call(
×
NEW
361
            [this, replyAddress, funcInfoCopy=funcInfo13]()
×
362
            {
363
              try
364
              {
NEW
365
                if(auto decoder = m_decoderController->getDecoder(DCC::getProtocol(replyAddress), replyAddress))
×
366
                {
367
                  //Function get always updated because we do not store a copy in cache
368
                  //so there is no way to tell in advance if they changed
NEW
369
                  for(int i = 13; i <= 28; i++)
×
370
                  {
NEW
371
                    decoder->setFunctionValue(i, funcInfoCopy.getFunction(i));
×
372
                  }
NEW
373
                }
×
374
              }
NEW
375
              catch(...)
×
376
              {
377

NEW
378
              }
×
NEW
379
            });
×
NEW
380
          break;
×
381
        }
NEW
382
        break;
×
383
      }
NEW
384
      case SET_LOCO:
×
385
      {
NEW
386
        const auto& locoInstr = static_cast<const LocomotiveInstruction&>(message);
×
NEW
387
        if((locoInstr.identification & LocomotiveInfo::identificationMask) == 0)
×
388
        {
NEW
389
          const auto& locoInfo = static_cast<const LocomotiveInfo&>(message);
×
NEW
390
          handleLocoInfoReply(locoInfo);
×
NEW
391
          break;
×
392
        }
393

NEW
394
        break;
×
395
      }
NEW
396
      case FUNC_INFO_V4:
×
397
      {
NEW
398
        const auto& funcInfo29 = static_cast<const FunctionInfoF29F68&>(message);
×
NEW
399
        if(funcInfo29.identification != idReplyFuncF29F68)
×
NEW
400
          break; // Not a F29F68 info message
×
401

NEW
402
        const uint16_t replyAddress = popAddressQuerySendNext(Utils::PendingQuery::FuncInfoF29F68);
×
NEW
403
        if(!replyAddress)
×
NEW
404
          break; // We did not ask for function info, ignore it
×
405

NEW
406
        EventLoop::call(
×
NEW
407
          [this, replyAddress, funcInfoCopy=funcInfo29]()
×
408
          {
409
            try
410
            {
NEW
411
              if(auto decoder = m_decoderController->getDecoder(DCC::getProtocol(replyAddress), replyAddress))
×
412
              {
413
                //Function get always updated because we do not store a copy in cache
414
                //so there is no way to tell in advance if they changed
NEW
415
                for(int i = 29; i <= 68; i++)
×
416
                {
NEW
417
                  decoder->setFunctionValue(i, funcInfoCopy.getFunction(i));
×
418
                }
NEW
419
              }
×
420
            }
NEW
421
            catch(...)
×
422
            {
423

NEW
424
            }
×
NEW
425
          });
×
426

NEW
427
        break;
×
428
      }
NEW
429
      case LOCO_INFO_CUMULATIVE:
×
430
      {
NEW
431
        const auto& locoInfo = static_cast<const RocoMultiMAUS::LocomotiveCumulativeInfo&>(message);
×
NEW
432
        if((locoInfo.identification & RocoMultiMAUS::LocomotiveCumulativeInfo::identificationMask) == 0)
×
433
        {
NEW
434
          handleLocoInfoReply(locoInfo);
×
NEW
435
          break;
×
436
        }
437

NEW
438
        break;
×
439
      }
440
      }
NEW
441
      break;
×
442
    }
443
  }
444
}
×
445

446
void Kernel::resumeOperations()
×
447
{
448
  assert(isEventLoopThread());
×
449

450
  if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
×
451
  {
452
    boost::asio::post(m_ioContext, 
×
453
      [this]()
×
454
      {
455
        send(ResumeOperationsRequest());
×
456
      });
×
457
  }
458
}
×
459

460
void Kernel::stopOperations()
×
461
{
462
  assert(isEventLoopThread());
×
463

464
  if(m_trackPowerOn != TriState::False || m_emergencyStop != TriState::False)
×
465
  {
466
    boost::asio::post(m_ioContext, 
×
467
      [this]()
×
468
      {
469
        send(StopOperationsRequest());
×
470
      });
×
471
  }
472
}
×
473

474
void Kernel::stopAllLocomotives()
×
475
{
476
  assert(isEventLoopThread());
×
477

478
  if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::True)
×
479
  {
480
    boost::asio::post(m_ioContext, 
×
481
      [this]()
×
482
      {
483
        send(StopAllLocomotivesRequest());
×
484
      });
×
485
  }
486
}
×
487

488
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
489
{
NEW
490
  assert(isEventLoopThread());
×
491

NEW
492
  if(m_isUpdatingDecoderFromKernel)
×
493
  {
494
    // This change was caused by Xpressnet message so there is not point
495
    // on informing back Xpressnet with another message
496
    // Reset the guard to allow Train and other parts of code
497
    // to react to this change and further edit decoder state
NEW
498
    m_isUpdatingDecoderFromKernel = false;
×
NEW
499
    return;
×
500
  }
501

UNCOV
502
  if(m_config.useEmergencyStopLocomotiveCommand && changes == DecoderChangeFlags::EmergencyStop && decoder.emergencyStop)
×
503
  {
504
    postSend(EmergencyStopLocomotive(decoder.address));
×
505
  }
506
  else if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps))
×
507
  {
508
    SpeedAndDirectionInstruction spd(
509
      decoder.address,
510
      decoder.emergencyStop,
NEW
511
      decoder.direction);
×
512

NEW
513
    spd.setSpeedSteps(decoder.speedSteps);
×
514
    //assert(spd.speedSteps() == decoder.speedSteps);
515

NEW
516
    if(!decoder.emergencyStop)
×
NEW
517
      spd.setSpeedStep(Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, decoder.speedSteps));
×
518

NEW
519
    if(decoder.speedSteps == 14)
×
NEW
520
      static_cast<SpeedAndDirectionInstruction14&>(spd).setFl(decoder.getFunctionValue(0));
×
521

NEW
522
    spd.updateChecksum();
×
NEW
523
    postSend(spd);
×
524
  }
525
  else if(has(changes, DecoderChangeFlags::FunctionValue))
×
526
  {
NEW
527
    uint8_t maxSupportedGroup = 3;
×
NEW
528
    if(m_centralVersionEventLoop >= XNet_4_0)
×
NEW
529
      maxSupportedGroup = 10;
×
NEW
530
    else if(m_centralVersionEventLoop >= XNet_3_6)
×
NEW
531
      maxSupportedGroup = 5;
×
NEW
532
    else if(m_config.useRocoF13F20Command)
×
NEW
533
      maxSupportedGroup = 4;
×
534

NEW
535
    for(uint8_t group = 1; group <= maxSupportedGroup; group++)
×
536
    {
NEW
537
      const uint8_t minIndex = FunctionInstructionGroup::getMinFunctionIndex(group);
×
NEW
538
      const uint8_t maxIndex = FunctionInstructionGroup::getMaxFunctionIndex(group);
×
539

NEW
540
      if(functionNumber < minIndex || functionNumber > maxIndex)
×
NEW
541
        continue;
×
542

NEW
543
      if(group == 4 && m_config.useRocoF13F20Command)
×
544
      {
545
        postSend(RocoMultiMAUS::FunctionInstructionF13F20(
×
546
          decoder.address,
547
          decoder.getFunctionValue(13),
×
548
          decoder.getFunctionValue(14),
×
549
          decoder.getFunctionValue(15),
×
550
          decoder.getFunctionValue(16),
×
551
          decoder.getFunctionValue(17),
×
552
          decoder.getFunctionValue(18),
×
553
          decoder.getFunctionValue(19),
×
554
          decoder.getFunctionValue(20)));
×
555
      }
556
      else
557
      {
NEW
558
        FunctionInstructionGroup setFunc(decoder.address, group);
×
NEW
559
        for(uint8_t i = minIndex; i <= maxIndex; i++)
×
NEW
560
          setFunc.setFunction(i, decoder.getFunctionValue(i));
×
NEW
561
        setFunc.updateChecksum();
×
NEW
562
        postSend(setFunc);
×
563
      }
NEW
564
      break;
×
565
    }
566
  }
567
}
568

569
bool Kernel::setOutput(uint16_t address, OutputPairValue value)
×
570
{
571
  assert(isEventLoopThread());
×
572
  assert(address >= accessoryOutputAddressMin && address <= accessoryOutputAddressMax);
×
573
  assert(value == OutputPairValue::First || value == OutputPairValue::Second);
×
574
  boost::asio::post(m_ioContext, 
×
575
    [this, address, value]()
×
576
    {
NEW
577
      if(m_centralVersion >= XNet_3_8)
×
578
      {
NEW
579
        send(
×
NEW
580
          AccessoryDecoderOperationRequestV3(
×
NEW
581
            m_config.useRocoAccessoryAddressing ? address + 4 : address,
×
582
            value == OutputPairValue::Second,
583
            true));
584
      }
NEW
585
      else if(address <= 1024)
×
586
      {
NEW
587
        send(
×
NEW
588
          AccessoryDecoderOperationRequestV1(
×
NEW
589
            m_config.useRocoAccessoryAddressing ? address + 4 : address,
×
590
            value == OutputPairValue::Second,
591
            true));
592
      }
593
    });
×
594
  return true;
×
595
}
596

597
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
×
598
{
NEW
599
  assert(isEventLoopThread());
×
600

601
  if(m_simulation)
×
602
    boost::asio::post(m_ioContext, 
×
603
      [this, address, action]()
×
604
      {
605
        if((action == SimulateInputAction::SetFalse && m_inputValues[address - 1] == TriState::False) ||
×
606
            (action == SimulateInputAction::SetTrue && m_inputValues[address - 1] == TriState::True))
×
607
          return; // no change
×
608

609
        const uint16_t groupAddress = (address - 1) >> 2;
×
610
        const auto index = static_cast<uint8_t>((address - 1) & 0x0003);
×
611

612
        std::byte message[sizeof(FeedbackBroadcast) + sizeof(FeedbackBroadcast::Pair) + 1];
613
        memset(message, 0, sizeof(message));
×
614
        auto* feedbackBroadcast = reinterpret_cast<FeedbackBroadcast*>(&message);
×
615
        feedbackBroadcast->header = idFeedbackBroadcast;
×
616
        feedbackBroadcast->setPairCount(1);
×
617
        auto& pair = feedbackBroadcast->pair(0);
×
618
        pair.setGroupAddress(groupAddress);
×
619
        pair.setType(FeedbackBroadcast::Pair::Type::FeedbackModule);
×
620
        for(uint8_t i = 0; i < 4; i++)
×
621
        {
622
          const uint16_t n = (groupAddress << 2) + i;
×
623
          if(i == index)
×
624
          {
625
            switch(action)
×
626
            {
627
              case SimulateInputAction::SetFalse:
×
628
                pair.setStatus(i, false);
×
629
                break;
×
630

631
              case SimulateInputAction::SetTrue:
×
632
                pair.setStatus(i, true);
×
633
                break;
×
634

635
              case SimulateInputAction::Toggle:
×
636
                pair.setStatus(i, m_inputValues[n] != TriState::True);
×
637
                break;
×
638
            }
639
          }
640
          else
641
            pair.setStatus(i, m_inputValues[n] == TriState::True);
×
642
        }
643
        updateChecksum(*feedbackBroadcast);
×
644

645
        receive(*feedbackBroadcast);
×
646
      });
647
}
×
648

NEW
649
void Kernel::setDecoderList(const std::vector<Locomotive> &locoVec)
×
650
{
NEW
651
  assert(isEventLoopThread());
×
652

NEW
653
  m_ioContext.post(
×
NEW
654
      [this, vec=locoVec]()
×
655
      {
NEW
656
        m_locomotives = vec;
×
NEW
657
      });
×
NEW
658
}
×
659

NEW
660
void Kernel::updateDecoder(uint16_t address, uint16_t newAddress, uint8_t decoderFunctions)
×
661
{
NEW
662
  assert(isEventLoopThread());
×
663

NEW
664
  m_ioContext.post(
×
NEW
665
      [this, address, newAddress, decoderFunctions]()
×
666
      {
NEW
667
        for(Locomotive &loco : m_locomotives)
×
668
        {
NEW
669
          if(loco.address != address)
×
NEW
670
            continue;
×
671

NEW
672
          if(newAddress != 0)
×
NEW
673
            loco.address = newAddress;
×
674

675
          // Do an initial poll of new functions
NEW
676
          loco.flags = decoderFunctions | Locomotive::OwnedByXBus;
×
NEW
677
          break;
×
678
        }
NEW
679
      });
×
NEW
680
}
×
681

UNCOV
682
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
683
{
684
  assert(handler);
×
685
  assert(!m_ioHandler);
×
686
  m_ioHandler = std::move(handler);
×
687
}
×
688

689
void Kernel::send(const Message& message)
×
690
{
NEW
691
  assert(isKernelThread());
×
692

UNCOV
693
  if(m_ioHandler->send(message))
×
694
  {
695
    if(m_config.debugLogRXTX)
×
696
      EventLoop::call(
×
697
        [this, msg=toString(message)]()
×
698
        {
699
          Log::log(logId, LogMessage::D2001_TX_X, msg);
×
700
        });
×
701
  }
702
  else
703
  {} // log message and go to error state
704
}
×
705

NEW
706
bool Kernel::send(std::vector<uint8_t> message)
×
707
{
NEW
708
  assert(isEventLoopThread());
×
709

710
  // At least Header + XOR, at most Header + 15 bytes + XOR
NEW
711
  if(!inRange<size_t>(message.size(), 2, 0xF + 2))
×
712
  {
NEW
713
    return false;
×
714
  }
715

NEW
716
  Message *msg = reinterpret_cast<Message*>(message.data());
×
NEW
717
  if(message.size() != msg->size())
×
NEW
718
    return false;
×
719

NEW
720
  msg->updateChecksum();
×
721

NEW
722
  boost::asio::post(m_ioContext,
×
NEW
723
    [this, msg_=std::move(message)]()
×
724
    {
NEW
725
      send(*reinterpret_cast<const Message*>(msg_.data()));
×
NEW
726
    });
×
727

NEW
728
  return true;
×
729
}
730

NEW
731
void Kernel::postQuery(const Utils::PendingQuery &query)
×
732
{
NEW
733
  assert(isKernelThread());
×
NEW
734
  assert(query.address >= shortAddressMin && query.address <= longAddressMax);
×
735

NEW
736
  if(query.type == Utils::PendingQuery::FuncInfoF29F68 && m_centralVersion < XNet_4_0)
×
NEW
737
    return;
×
738

NEW
739
  if(query.type == Utils::PendingQuery::FuncInfoF13F28 && m_centralVersion < XNet_3_6)
×
NEW
740
    return;
×
741

NEW
742
  if(query.type == Utils::PendingQuery::ROCOCumulativeLocoInfo
×
NEW
743
      && (m_centralVersion < XNet_3_0 || !m_config.useRocoF13F20Command))
×
NEW
744
    return;
×
745

NEW
746
  for(const Utils::PendingQuery& other : std::as_const(m_pendingQueries))
×
747
  {
NEW
748
    if(other.address == query.address && other.type == query.type)
×
NEW
749
      return; // Already pending
×
750
  }
751

NEW
752
  const bool wasEmpty = m_pendingQueries.empty();
×
NEW
753
  m_pendingQueries.push_back(query);
×
754

NEW
755
  if(wasEmpty)
×
NEW
756
    sendCurrentQuery();
×
757
}
758

NEW
759
void Kernel::sendCurrentQuery()
×
760
{
NEW
761
  assert(isKernelThread());
×
762

NEW
763
  if(m_pendingQueries.empty())
×
NEW
764
    return;
×
765

NEW
766
  switch (m_pendingQueries.at(0).type)
×
767
  {
NEW
768
  case Utils::PendingQuery::LocoInfoAndF0F12:
×
769
  {
NEW
770
    send(QueryLocomotiveV3(m_pendingQueries.at(0).address));
×
NEW
771
    break;
×
772
  }
NEW
773
  case Utils::PendingQuery::FuncInfoF13F28:
×
774
  {
NEW
775
    send(QueryLocomotiveFunctions(m_pendingQueries.at(0).address, 4));
×
NEW
776
    break;
×
777
  }
NEW
778
  case Utils::PendingQuery::FuncInfoF29F68:
×
779
  {
NEW
780
    send(QueryLocomotiveFunctions(m_pendingQueries.at(0).address, 6));
×
NEW
781
    break;
×
782
  }
NEW
783
  case Utils::PendingQuery::ROCOCumulativeLocoInfo:
×
784
  {
NEW
785
    send(RocoMultiMAUS::QueryLocomotiveCumulative(m_pendingQueries.at(0).address));
×
NEW
786
    break;
×
787
  }
NEW
788
  default:
×
NEW
789
    break;
×
790
  }
791

NEW
792
  boost::system::error_code ec;
×
NEW
793
  m_pendingQueryTimeout.cancel(ec);
×
NEW
794
  m_pendingQueryTimeout.expires_after(boost::asio::chrono::milliseconds(800));
×
NEW
795
  m_pendingQueryTimeout.async_wait(std::bind(&Kernel::onPendingQueryTimeout, this, std::placeholders::_1));
×
796
}
797

NEW
798
void Kernel::onPendingQueryTimeout(const boost::system::error_code& ec)
×
799
{
NEW
800
  assert(isKernelThread());
×
801

NEW
802
  if(ec == boost::asio::error::operation_aborted)
×
NEW
803
    return;
×
804

NEW
805
  if(m_pendingQueries.empty())
×
NEW
806
    return;
×
807

808
  // Remove first query which did not get reply
NEW
809
  m_pendingQueries.erase(m_pendingQueries.begin());
×
810

811
  // Go on to next query
NEW
812
  sendCurrentQuery();
×
813
}
814

NEW
815
uint16_t Kernel::popAddressQuerySendNext(Utils::PendingQuery::QueryType type)
×
816
{
NEW
817
  assert(isKernelThread());
×
818

NEW
819
  if(m_pendingQueries.empty() || m_pendingQueries.at(0).type != type)
×
NEW
820
    return 0;
×
821

NEW
822
  const uint16_t address = m_pendingQueries.at(0).address;
×
823

824
  // Remove first query which completed succesfully
NEW
825
  m_pendingQueries.erase(m_pendingQueries.begin());
×
826

827
  // Go on to next query
NEW
828
  sendCurrentQuery();
×
NEW
829
  return address;
×
830
}
831

NEW
832
void Kernel::pollDecoders()
×
833
{
NEW
834
  assert(isKernelThread());
×
835

NEW
836
  m_pollTimer.expires_after(boost::asio::chrono::milliseconds(1000));
×
NEW
837
  m_pollTimer.async_wait(
×
NEW
838
    [this](const boost::system::error_code& ec)
×
839
    {
NEW
840
      if(!ec)
×
NEW
841
        pollDecoders();
×
NEW
842
    });
×
843

NEW
844
  for(const Locomotive& loco : m_locomotives)
×
845
  {
NEW
846
    if(loco.address == 0)
×
NEW
847
      continue; // Skip invalid addresses
×
848

NEW
849
    if((loco.flags & Locomotive::Flags::OwnedByXBus) == Locomotive::Flags::OwnedByXBus)
×
NEW
850
      postQuery({loco.address,
×
NEW
851
        m_config.useRocoF13F20Command ?
×
852
          Utils::PendingQuery::ROCOCumulativeLocoInfo :
853
          Utils::PendingQuery::LocoInfoAndF0F12});
854
  }
NEW
855
}
×
856

NEW
857
void Kernel::setCentralVersion(uint8_t version, HardwareType commandStationId)
×
858
{
NEW
859
  assert(isKernelThread());
×
NEW
860
  m_centralVersion = XBusVersion(version);
×
NEW
861
  EventLoop::call([this, version, commandStationId]()
×
862
    {
NEW
863
      m_centralVersionEventLoop = XBusVersion(version);
×
864

NEW
865
      if(m_onHardwareInfoChanged)
×
NEW
866
        m_onHardwareInfoChanged(commandStationId,
×
NEW
867
          Utils::xbusVersionMajor(version),
×
NEW
868
          Utils::xbusVersionMinor(version));
×
NEW
869
    });
×
NEW
870
}
×
871

872
template<class T>
NEW
873
void Kernel::handleLocoInfoReply(const T &locoInfo)
×
874
{
NEW
875
  constexpr auto replyType = []() -> auto {
×
876
    if constexpr(std::is_same_v<T, LocomotiveInfo>)
877
      return Utils::PendingQuery::LocoInfoAndF0F12;
878
    else if constexpr(std::is_same_v<T, RocoMultiMAUS::LocomotiveCumulativeInfo>)
879
      return Utils::PendingQuery::ROCOCumulativeLocoInfo;
880
    else
881
      static_assert(false, "Wrong message type");
882
  }();
883

NEW
884
  const uint16_t replyAddress = popAddressQuerySendNext(replyType);
×
NEW
885
  if(!replyAddress)
×
NEW
886
    return; // We did not ask for locomotive info, ignore it
×
887

888
  // After receiving basic loco info, query higher functions
NEW
889
  auto loco = std::find_if(m_locomotives.begin(), m_locomotives.end(),
×
NEW
890
    [replyAddress](const Locomotive &item) -> bool
×
891
    {
NEW
892
      return item.address == replyAddress;
×
893
    });
894

NEW
895
  if(loco == m_locomotives.end())
×
NEW
896
    return;
×
897

NEW
898
  if((loco->flags & Locomotive::Flags::HasF13F28) == Locomotive::Flags::HasF13F28)
×
NEW
899
    postQuery({replyAddress, Utils::PendingQuery::FuncInfoF13F28});
×
NEW
900
  else if((loco->flags & Locomotive::Flags::HasF29F68) == Locomotive::Flags::HasF29F68)
×
NEW
901
    postQuery({replyAddress, Utils::PendingQuery::FuncInfoF29F68});
×
902

903
  // Enable/disable polling for this locomotive
904
  // When disabling, complete last poll cycle
NEW
905
  if(locoInfo.isBusy())
×
NEW
906
    loco->flags |= Locomotive::Flags::OwnedByXBus;
×
907
  else
NEW
908
    loco->flags &= ~Locomotive::Flags::OwnedByXBus;
×
909

NEW
910
  EventLoop::call(
×
NEW
911
    [this, replyAddress, locoInfoCopy=locoInfo]()
×
912
    {
913
      try
914
      {
NEW
915
        if(auto decoder = m_decoderController->getDecoder(DCC::getProtocol(replyAddress), replyAddress))
×
916
        {
NEW
917
          const float throttle = Decoder::speedStepToThrottle(locoInfoCopy.speedStep(), locoInfoCopy.speedSteps());
×
918

NEW
919
          m_isUpdatingDecoderFromKernel = true;
×
NEW
920
          decoder->emergencyStop = locoInfoCopy.isEmergencyStop();
×
921

NEW
922
          m_isUpdatingDecoderFromKernel = true;
×
NEW
923
          decoder->direction = locoInfoCopy.direction();
×
924

NEW
925
          m_isUpdatingDecoderFromKernel = true;
×
NEW
926
          decoder->throttle = throttle;
×
927

928
          //Reset flag guard at end
NEW
929
          m_isUpdatingDecoderFromKernel = false;
×
930

931
          //Function get always updated because we do not store a copy in cache
932
          //so there is no way to tell in advance if they changed
NEW
933
          for(int i = 0; i <= T::functionIndexMax; i++)
×
934
          {
NEW
935
            decoder->setFunctionValue(i, locoInfoCopy.getFunction(i));
×
936
          }
937
        }
938
      }
NEW
939
      catch(...)
×
940
      {
941

942
      }
943
    });
944
}
945

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